ThearchyHelios
|
cd7ced7673
|
不是每句话都值得跑一遍库:模型自己选要不要执行
以前必执行——final 一到就无条件 launch。库里那条会话 7 轮全建了图、全跑了
一遍 153 张表的库,其中:
- 「重试」「继续找找」这种对过程说的话,各自重建了一整张图去查
- 一句明确要「设计一个工作流,使得每次执行的时候都给出管理员账户信息」的,
建完也直接跑了——用户要的是那张图,不是这一次的结果
流协议给模型两个新出口,三条路径二选一:
- {"op":"reply","text":"…"} 不建图不跑。问的是前几轮已经查出来的东西
(口径、怎么算的),或者是对过程说的话,或者需要先反问清楚
- done 里 run:false 建图但停住,界面上给「跑一下」
- done 里 run:true(默认) 不变
reply 一到服务端就收流:让它走到后面那套排版校验上,空图会撞出"图校验未
通过:图是空的,先拖一个节点进来"——上一个提交刚治过同类的坑。
放开「直接回答」等于绕开了这条路径上最硬的一条约定(涉及数据必须真查),
所以两头都上:prompt 里写死 reply 只能复述或解释前面几轮已有的结果、不得
新断言任何数据事实,凡是要动到库才知道的哪怕觉得上一轮见过也重新查;界面
上那一轮的结论**前面**挂一条声明,说清楚这一条没有查库。check-stream 里有
一条断言专门盯它排在结论前面——排在后面等于先让人读完再说来源不可靠。
需要留意:这依赖模型照约定表态,不是硬规则。判断跑偏的话收紧那几句约定。
|
2026-09-18 22:30:45 +08:00 |
|
ThearchyHelios
|
152264b29c
|
长报告不再被咬掉一口:折叠只在行边界切
用户看到的是一份读起来完整、末尾却挂着半行原始 markdown 的报告:
userID 用户名 邮箱 昵称 providerID 商家名称 联系人邮箱
| 1 | ThearchyHelios | user@example.com | … | Test Marchant By Shop
表头是制表符(已渲染成表格),数据行是竖线(还是原始文本)——折叠是
text.slice(0, 3000) 按字符硬切的,切点落进表格中间,前面几行渲染成表格、
最后半行没能成行。报告本身是完整的,只是被折叠了,但界面上看不出来。
改成只在行边界切;切点落进表格时整块表格都不要——宁可少显示一段,也不给
一张半截的表。另外原来只有一个"展开全部"按钮,看上去像报告就这么长,
现在先说"…后面还有,这里先折叠了"。
顺带治了落库那一处:answerText 在 4000 字处无声截断,刷新页面后那份就永久
少一截,断点同样常常落在表格中间。上限提到 20000,真切到了会写明已截断、
完整内容见运行记录。喂给下一轮的那份另有更狠的截断,两回事。
check-stream 多一节,盯的就是"折叠后不许留下没渲染的原始表格行"。
把切法改回硬切,它会立刻抓到残留的 `| 24 | user_23 | …`。
|
2026-09-18 22:29:24 +08:00 |
|
ThearchyHelios
|
dcadc0c48b
|
对话有上下文了,左边也能选会话
三条 run(d983c4e6 / 14103db5 / 08c3de1c)在用户看来是一次对话,系统里却
毫无关联。查下来不是哪里传漏了,是整层缺失:
- 每轮 base_graph=None 从零建图,Copilot 不知道上一轮问过什么
- 每轮 thread_id = run.id,三条独立的 checkpoint 线程
- 前端 turns 只活在 zustand 里,刷新页面就全没了——那三轮当时在界面上
已经找不回来,只剩 runs 表里三条互不相干的记录
- 后端 13 张表里没有会话这个概念
加两张表(conversations / conversation_turns)。失败的轮次也记:只记成功的,
历史就成了一份美化过的记录,而人回来最想搞清楚的恰恰是上次卡在哪。
Copilot 的上下文带三样:前几轮问答、上一轮那张图(走 _slim 去掉坐标噪音)。
只给问答文字不够——"再按月份拆一下"会让模型重新设计一张图,实测经常接到另
一张表上,答案对不上前一轮。运行时另外注入 {{ input.history }},管的是指代:
用户问"它们平均多少",图里的 agent 得知道"它们"是上一轮那 12 家店。
左侧会话栏只在问数据页出现,可折叠、可改名删除,当前会话存 localStorage。
轮次按会话分桶缓存——切走再切回来要看得到原来的步骤,回源只拿得到问答。
历史轮次的执行过程不预加载,点「看执行过程」才取事件、走现成的 decodeRun。
画布右栏的 Copilot 依附工作流各有一条会话,连说两次"再加一个节点"能接上了
(base_graph 带的是图的结果,带不出"你刚才要我干什么"),但不进左侧列表。
过程中修掉的两个自己引入的坑:
- 第一版在列表为空时自动建一条会话。create 是异步的、StrictMode 把 effect
跑两遍,于是每次进页面都留下两三条"新对话"。改成用户开口那一刻按需建
- 加了"在上一轮的图上改"这条指令之后,模型照改图协议只发 update_node,
而问数据页这条路径的累加器从空图起步,操作全部落空,用户收到的是
"图校验未通过:图是空的,先拖一个节点进来"(会话 f5db9809 的第二轮,
而它的 explanation 证明上下文其实完全读懂了)。指令改成沿用 id 和 config
但完整重发,另加一道兜底:空图且上一轮有图时把操作重放到那张图上
check-ui 多一节「会话」:切换、刷新还在、逛一圈不多出空会话。
|
2026-09-18 17:58:04 +08:00 |
|
ThearchyHelios
|
105120f22f
|
成果区渲染 Markdown:「1+1 等于 **2**」不再原样带着星号
模型几乎总是用 Markdown 组织回答,而成果区是 whitespace-pre-wrap 的纯文本,
于是它的结构全丢了:标题、列表、表格、粗体一律变成满屏符号。
**手写而不是装库。** 依赖只有七个、全是必需的,react-markdown 那一串
(unified / mdast / micromark)比这个文件大一个数量级。而要渲染的东西是
有限的——统计了库里 252 段真实输出,出现过的构造只有:
有序列表 40% · 无序列表 15% · 粗体 13% · 标题 3% · 行内代码 3%
表格 3% · 分隔线 2% · 代码块 1% · 引用 1% · 斜体 1% · 链接 0.8%
所以只做这些,**不声称支持完整 CommonMark**。认不出的语法原样当文本显示
——那正是现在的行为,下限不会比今天差。包大小 +37KB(gzip +12KB)。
安全:全程构造 React 元素,一处 dangerouslySetInnerHTML 都没有。模型输出
是不可信内容,走 innerHTML 等于给自己开个 XSS 口子。链接只放行
http/https/mailto,`javascript:` 退回纯文本。
一个只有拿真实输出才会撞上的 bug:模型很爱写 **`table_name`**(粗体套
代码),而粗体在扫描顺序上先命中,把那对反引号当普通文本吃掉了——渲染出来
是加粗的 `v_demo_table` 带着反引号。强调内部要再解析一层(深度卡在
三层,每层文本严格更短)。另外把模块级正则的 g 标志去掉了:lastIndex 有
状态,递归调用时会从上一层留下的位置接着扫。
审批卡里的待审草稿也走同一个渲染——要人家判断"这条能不能发",却让他对着
一堆星号看,是在给审批添难度。
check-stream 补 12 条,测例是从库里导出的真实输出 + 一段构造的恶意输入
(真实输出不会自带攻击,但渲染层要么天生免疫,要么就是个口子):残留标记、
粗体/代码/列表/表格、粗体套代码、javascript: 链接不可点、正常链接可点、
原始 HTML 不变成真标签且当文本显示、注入脚本没执行。
|
2026-09-18 12:51:55 +08:00 |
|
ThearchyHelios
|
1444c774cf
|
模板字段加 {{ }} 补全:从源头消灭那类静默失败
模板取不到值时渲染成空字符串、不报错,所以拼错一个变量名在运行时完全静默。
上一个 commit 是事后校验(标红+建议),这个是事前:打 {{ 就弹出这张图里
实际有的变量,每条带说明和产出它的节点。
**只挂在真正过模板渲染的字段上。** 这一条是整件事最容易做错的地方:
这套里有五个字段长得像但走的是表达式求值——skip_if、loop.condition、
cases[].condition、transform.expression、metrics[].expression——那里要写
`vars.count == 0` 这样的裸路径,不带大括号。在那些字段上挂 {{ 触发器等于
手把手教用户写错。同理 supervisor 的 agents[].system 长得和 llm.system
一模一样,但后端是 cfg.get("system") 直接取值、根本不渲染,那里弹补全会
是个谎;它走的是 AgentList 里的裸 textarea,天然不受影响。
几个不写就会坏的细节:
- 这是完全受控组件。插入后 onChange 会一路走到 store 再把 value 灌回来,
DOM 光标会跳到末尾——必须等那一帧过去再自己 setSelectionRange 放回去。
测试里专门验了"插入后继续打字,字落在后面而不是中间"。
- 候选框用文档流里的块,不是绝对定位浮层:属性面板是 overflow-y-auto、
外层 InspectorSheet 是 overflow-hidden,浮层到边缘直接被切掉。
- 用 onMouseDown 不是 onClick——onClick 在 blur 之后才触发,那时候弹层
已经关了。
- Escape 要 stopPropagation:外面 InspectorSheet 也在听 Escape,不拦的话
按一下会连整个属性面板一起关掉。
- 光标前已经打了 | 或 [ 就不再弹——那时候是在写过滤器或下标,不是在选变量。
check-ui 补一组:变量表列得出来、抽屉挤压后画布没被压没(React Flow 容器
高度归零会直接报 error 004)、打 {{ 弹候选、插入成完整写法、光标位置正确。
|
2026-09-18 10:55:56 +08:00 |
|
ThearchyHelios
|
f43f56abb7
|
把 Copilot 的思考显示出来:模型一直在想,只是被丢掉了
用户问能不能看到"正在理解需求、规划步骤"里面发生了什么。查下来是这样:
模型确实在想,reasoning 花了实打实的 token,但一路上被丢了三次。
1. langchain 的 ChatOpenAI **故意**不保留第三方的 reasoning_content——它的
文档原话是 "Non-standard response fields added by third-party providers
are not extracted or preserved",建议换 ChatDeepSeek 之类的专用包。可
"用哪个供应商"在这个产品里是用户在设置页配的(DeepSeek / Qianfan /
vLLM / OpenRouter 都可能),为每种装一个专用包不现实。在 factory 里
子类化捞回 additional_kwargs,只加字段,不动 langchain 对正文的处理。
2. thinking_text 只认 Anthropic 的 thinking 块。补上兼容端点那一种。
3. 前端把一段思考的标题固定成开头 60 个字。思考是流式的,开头往往是
"用户想要一个……"这类复述,之后几十秒里它一直在推进——固定显示开头
等于把一段活的叙述冻在起点上,看着就像卡住了。改成:还在想的时候显示
最新一句,想完了换回开头那句(那时它是一条历史记录)。
实测一次真实生成,60 秒里侧栏逐句变化:
3s 可用数据源 bi
9s 人工节点 approve 模式,审核
18s 我们可以用 db_query__sales 执行 SELECT COUNT(*) FROM orders 等
36s 更合理:rejected -> query_agent(重新
45s 对于 bi 有 53 个对象,靠
57s (plan 到达,思考收尾,换回开头那句)
顺带运行期的 LLM 节点也跟着有了思考——走的是同一个 thinking_text,之前
同样只有 Anthropic 系才有。已验证流式合并后思考连成完整一段,不是只剩
最后一片。
tests/test_thinking.py 守两种形态、合并、以及"不产思考的模型不硬凑"。
check-decode 补 5 条守"还在想显示最新一句、想完了换开头"。
|
2026-09-18 09:55:42 +08:00 |
|
ThearchyHelios
|
aa18904af0
|
运行和 Copilot 分成两层;顺带修了一个会丢事件的时序缺陷
用户:「点击右上角运行后,会跳到流程;然后下方就又有 Copilot 对话框,把
结果和 Copilot 又放在一块儿了」。说得对,而且我上一版的理由是错的。
我当时把建图和跑图排进同一条时间轴,说它们是"一件事的两半"。它们在时间上
先后发生,但不是同一种东西——最直接的证据在我自己的代码里:运行那一轮的
question 只能填 undefined。一个没有提问的"对话轮次",本来就不是对话。
更实际的问题是那个输入框:它贴在运行结果下面,暗示你可以回复这次运行。
但你不能——在那儿打字改的是画布上的图。这是个假的可供性。两者的生命周期
也对不上:Copilot 的轮次会累积,运行永远只有最新一次。
现在右栏是一个常驻的家(Copilot 对话)加两层临时盖上来的东西(节点属性、
运行过程)。运行那一层没有输入框,有自己的头部和停止按钮;对话那一层底部
留一条运行进度,点一下过去。
---
顺着这条线查下去,发现一个更严重的问题:**后端在丢事件**。
浏览器侧连跑 5 次,2 次丢了中间一条。抓 WebSocket 原始帧才看清:所有帧的
replay 都是 false——历史读回来是空的,全部走实时;丢的是 run.started,
它在客户端订阅之前就广播了,而那时还没落库,所以历史里也没有。
根因是 _emit 的顺序:先广播、后落库。订阅者的完整性靠"先订阅 → 读历史 →
接实时"拼起来,它需要这条保证——提交早于我这次 SELECT 的,在历史里;提交
晚于我这次 SELECT 的,广播也晚于我订阅,在实时流里。后半句只有在"提交先于
广播"时才成立。交换顺序后,6 次 WebSocket 探测 + 5 次浏览器运行全部无缺口。
(上一轮我修过这个文件的另一个窗口——先读历史后订阅。那个修复是对的,但
只堵住了两步之间的缝,堵不住订阅建立之前的那一段。)
tests/test_emit_order.py 守这条不变式:广播的那一刻,这条事件必须已经能从
另一个 session 查到。去掉修复后它确实失败。
---
还修了三处:
- 停在人工介入时,"在不在等人"原先取自审批列表,而那是 4 秒轮询一次的;
中断后会有几秒钟界面说"这次运行已结束",而它正等着你点通过。改成从事件
推导(isAwaitingHuman),即时且和显示的内容一致。
- 新增 tests/conftest.py 把整个测试会话关进临时目录。test_emit_order 第一版
直接用 SessionLocal 往开发库里写,几行假运行排到了运行列表最前面。已清掉
那 12 行。这该是全局保障,不是只修一个文件。
- 开发期把 store 挂到 window.__studio,好让 playwright 直接读状态——"事件
收全了没有"在界面上看不出来,只能问 store。已确认不进生产包。
|
2026-09-18 09:39:24 +08:00 |
|
ThearchyHelios
|
3de7567377
|
右栏重构:助手常驻,属性改为盖上去的一层
两个问题都源于同一件事——一栏 360px 被要求同时干两件活。
**没地方引导写问题。** Copilot 输入框是个 38px 高的框,挤在一条长长的流
底下,比它上面任何一块都不起眼;例句被截断成 16 个字的 chip,示范"该说到
什么颗粒度"这个作用就没了,只剩装饰。改成一个组件两种体量:没说过话时它
就是这一栏的主体(竖直居中、三行高、自动聚焦、完整句子的例句),并且告诉
用户它能够到什么(N 个数据源 / M 个工具)——不然"查销售库"这种话有没有意义
只能靠猜。有了对话之后它退到底部两行。不做成两个组件:那就是两个实例,
切换时会卸载其中一个,草稿、光标、输入法候选词全没。
**Copilot 和属性挤在一起。** 它们原是 [助手|属性] 两个 tab,而 tab 是互斥的
——跑图跑到一半点开节点看配置,整条执行过程就从眼前消失,切回来滚动位置和
输入草稿也没了(切 tab 会卸载组件)。改成属性滑出来盖在助手上:助手一直在
下面活着,属性是临时造访者。头部给一个明确的"‹ 助手",Esc 关得掉;盖住
期间底部留一条执行进度,点一下回去——不能因为换了实现就把原来最难受的
那个场景原样留着。
运行控件搬去工具栏。它原先和 Copilot 输入框一起常驻助手栏,两个都是"主要
动作"互相压着;而它们是两种意图(跑这张图 / 改这张图)。运行是对整张图的
动作,和保存、发布同类。有输入字段就弹表单,没有就一键直接跑。
顺带修了两处状态说反的地方:
- 停在人工介入时,承载的节点还在转蓝圈。转圈说的是"它在忙,你等着",而
实际正相反——它在等你。改成待办色 + 手势图标。
- "继续执行"的去重原先靠"界面上还有没有 waiting 步骤",上一条改动多了一处
产生 waiting 的地方就失效了。改用事件相邻性判断——那是这两条事件的固有
性质,不会因为别处改了展示而变。
动效:面板滑入、例句依次入场、正在出字的那一步有一道光扫过(比转圈多说
一件事:它在产出,不只是在等)、空态标记轻微呼吸。全部 240ms 以内,缓动
统一 cubic-bezier(.32,.72,0,1)。另外补上了此前完全没有的
prefers-reduced-motion——这套界面里动的东西不少(边在流动、节点在脉冲、
面板在滑),系统里关了还照播,对前庭敏感的人是实打实的难受。
验证:check-ui 重写了助手栏那一节,核心断言是"选中节点再回来,输入的字
还在"——那正是 tab 方案做不到的。新增一条用 emulateMedia 验证关掉动效后
动画确实停了。真跑了一次带人工审批的图,期间打开属性面板确认进度条还在。
四套检查 + 82 个后端测试全过,生产构建通过。
|
2026-09-18 00:05:22 +08:00 |
|
ThearchyHelios
|
3ff6840b99
|
多 agent 节点不再永远转圈,调度决策也说出来了
运行 #6038f5:supervisor 节点跑完 31.7s 之后,researcher 那一行还在转圈。
agent.step.start / agent.step.end 是这套事件里**唯一**一对没做配对的
(node、llm、tool、sandbox、human 都配了)。start 那条建出来就是 running,
end 另起一行,于是没人去关它。这正是 decode.ts 自己文档里的第 2 条原则
——成对事件合并成一行——我写了却没用在 agent 上。
配对键用 (node_id, agent, round):光用 agent 名不够,supervisor 会在多轮里
反复派给同一个 agent,第二轮的 end 会错配到第一轮的 start 上。
顺带把 supervisor 的调度决策捞出来。后端把它标成 info 日志,而 info 是整类
丢弃的,结果"为什么派给 researcher"、"为什么只跑一轮就结束"在界面上完全
没有交代——那个节点就是一个跑了 31 秒的黑盒。带 round 字段的 info 才是调度
决策,和普通排查日志区分得开。FINISH 译成"结束协作"而不是"交给 FINISH",
后者会让人以为还有个叫 FINISH 的成员。
真实运行已收进 fixtures,check-decode 加 9 条(含"普通 info 日志仍然不进
主流程"和"warn 日志照常显示"这两条反向约束)。
|
2026-09-17 19:14:06 +08:00 |
|
ThearchyHelios
|
f0fd63f0b9
|
UI 重构:Copilot 进侧栏,三个页面共用一条助手流
用户的原话是「copilot(包括问答)可以是侧边栏的效果」「显示他的简要思考节点,
使得用户知道他大致在想什么,以及用了哪些工具」。
画布右栏从「运行 / 属性 → 时间线 / 成果 / 事件」(两层嵌套 5 个视图)变成
「助手 / 属性」一层。助手栏里是一条流:你让它做什么、它做了什么、结果是什么。
Copilot 建图和跑图在协议上完全无关(SSE 操作流 vs 事件 WebSocket,不共享
seq 和 node_id),但对用户是一件事的两半,decode.ts 把两种流译成同一种 Step
之后才能排进同一条时间轴。
Copilot 不再是弹窗。弹窗的问题不是样式:每改一次图都要重开一次,关掉之后
需求文本就没了,想微调只能重打;而画布左下角那条浮条(z-30)和 Toast
(z-100)、Modal(z-50)三层各自为政,本身也是重复的一份 Copilot 状态渲染。
两个都删了,输入框就在栏里,说完一句接着说下一句。右栏没删——它是发起运行
和处理审批的唯一入口,RunLauncher 原样保留,只是跑起来会自动收起给执行过程
让位。tab 用 hidden 切换而不是卸载,不然点一下「属性」看个节点再切回来,
输入框草稿和展开状态全丢。
问数据页和运行页换成同一个 AssistantStream + 同一个 decode.ts。RunPanel 里
那套平行的 EVENT_META + describe() 删了:留着两套翻译,同一次运行在不同页面
会读出不同的故事,而用户没法判断哪个是真的。同理合并了两个耗时格式化函数
(画布节点显示"0ms",右边同一步什么都不显示,两块还并排放着)。运行页列表
加了内容摘要——原本每行只有"临时图 · 1.2s · 340 tok",十条长得一模一样,
要分清哪条是哪条只能挨个点开。
跑真实运行时发现两个缺陷:
1. 后端丢事件(app/core/bus.py)。WebSocket 先读历史、再订阅,两步之间图
正好跑出来的事件既不在历史里也不在实时流里,永远丢了。而 subscribe() 是
异步生成器,真正注册还要等到第一次迭代,窗口比看上去更大。实测一次跑图
开头的 node.finished/node.started 消失,界面上表现为某个节点永远在转圈、
它的子步骤跑到了顶层。改成先 attach 占位、再读历史,重叠部分按 seq 去重。
tests/test_bus_attach.py 里有一条刻画旧行为的测试,说明这个窗口确实存在。
2. 循环里的多轮审批被算错。我在 S1 按内容去重,但三轮审批的 title 一模一样,
和 LangGraph 的重放无法区分——结果第 2、3 次真实审批被当成重放吞掉,而
三次"你驳回了/你放行了"各自成行。改成开/闭配对:开着就是同一次,闭了才是
新一次;决定折进那条审批行而不是另起一行。真实运行(驳回 → 改写 → 再驳回
→ 改写 → 放行)已收进 fixtures。
验证:三层各守一件事,共 76 条断言全过。check-decode 守翻译(含新的多轮审批
和截断结果集),check-stream 守渲染,新增 check-ui 守"接起来还对不对"——真
浏览器真后端,包括「切走再切回来草稿还在」「唯一的运行入口还在」「原始事件
切得到也切得回」。另外真跑了一次带三轮审批的图和两次 Copilot 生成(60s,
deepseek-v4-pro),截图逐帧看过。后端 78 个测试全过。
|
2026-09-17 18:29:16 +08:00 |
|
ThearchyHelios
|
c1c13fad41
|
助手流组件:一份 Step,宽屏和 360px 窄栏共用
AssistantStream 是纯展示的,不连 store。这样画布右栏和问数据页能用同一个
组件,而不是"一个组件三种停靠模式"——后者要求同一个实例出现在三个 DOM
父节点里,React 里除非 portal 否则不成立,硬做就变成跨路由 remount,
输入草稿和展开状态全丢。差别只在 dense 一个开关。
渲染真实事件时发现的几个问题,顺手一起修了:
- 取数运行的最终成果是一个**被截断的双重转义 JSON**(后端按字符数硬切,
切点落在 JSON 中间是常态),严格 JSON.parse 必然失败,于是这个产品最
典型的一次运行,成果区呈现为满屏 \"attribute01\", \"attribute02\"。
parseQueryResult 改为解析失败时截到最后一条完整记录再解析——78 列 1 行
的表格,比一屏转义文本有用得多。并区分"查询撞了行数上限"和"预览被切断":
前者该去调查询,后者该去看完整工件,混成一句"已截断"等于没说。
- 审批恢复过的运行有"开始执行"+"继续执行"两条生命周期,run.finished 只
收了第一条(find 而非 forEach),"继续执行"永远转圈——明明已经跑完。
- 处理完的审批保留着 warn 级别,一直是橙色,看着像还有事没办。
- 一屏 0ms:那些纯赋值节点确实不花时间,把没信息量的数字摆出来反而把真正
慢的那一步淹了。低于 10ms 不显示,同时空耗时给 undefined 而不是空串。
- output 里 {"result": ""} 很常见(图跑通但出口没接上),会渲染出一条什么
都没有的分隔线。
验证:新增 scripts/check-stream.mjs,真浏览器跑真组件——5 类真实运行 ×
(无报错/无转义 JSON/无 0ms/不横向溢出)+ 展开看 SQL 和结果表 + 空态。
check-decode.mjs 补 11 条,两个脚本共 68 条断言全过。frontend/preview.html
是离线预览页,只有 dev server 加载(已确认不进 vite build 产物)。
|
2026-09-17 17:00:50 +08:00 |
|
ThearchyHelios
|
ca6e672dfa
|
事件解码器:全站唯一的"事件 → 人话"翻译层
UI 重构的地基。现有的 describe()(RunPanel.tsx:392)是工程师视角——
db_query__warehouse({"sql":...}) 是函数调用语法、1234+567 tok · $0.0021 是账单、
exit=0 · microvm 是退出码,而且 25 个事件类型里一半直接 return '' 没翻译。
做成独立模块而不是散在各页面,是因为画布时间线、助手栏、运行页要读同一个
故事。两套翻译的话同一次运行在不同地方讲得不一样,用户没法判断哪个是真的。
## 三条原则
- **不回写事件**:run 终态时对全部已落库事件算 manifest_hash,事后增删改会
对不上。翻译只在前端内存里发生。
- **成对事件合并成一行**:tool.start + tool.end 是同一件事的两个时刻,拆两行
等于把"查询数据"和"查完了"当成两件事。按 call_id 配对(并发调同名工具时
按名字会错位),独立 Tool 节点没有 call_id 则回退到名字。
- **节点是天然的分组单位**:一个图节点内部可能有好几轮模型和工具调用,
它们属于同一个步骤——这正是"简要思考节点"该有的结构。
## 用真实事件调出来的五个坑
从库里导出 5 类真实运行(带查询/带审批/带出具/失败/带思考)当 fixture,
构造的样本永远不会长成那样:
- **审批重复三次**。human.requested 没有 interrupt_id、run.interrupted 才有,
两个 key 算不到一起;而审批恢复后 LangGraph 重放节点又发一遍 human.requested。
改用两者共有的 node_id|mode|title 做键,三条收敛成一条。
- **节点重放让同名节点出现两次**。node.started 第二次来时复用原来那条。
- **"继续执行"说两遍**。run.resumed 和 run.started(resumed=true) 是同一件事。
- **标题退化成裸 id**。没起过名字的节点 label 等于 id(后端兜底),"in"/"h"/"out"
对用户毫无意义——退到节点类型的中文名。
- **空 preview 当详情显示**。{} 不是详情,是噪音。
## 刻意不做的
- 不显示 token 数和美元:那是账单视角,不是"它干了什么"
- 不依赖 canvas/nodeDefs:解码器是纯翻译层,画布只是消费者之一。
16 行的类型名映射重复得起,反向依赖会让运行页被迫加载整套节点定义
- 未知事件不静默吞掉:宁可显示一条原始的,也比让人觉得"什么都没发生"好
## 验证
scripts/check-decode.mjs 用真实 fixture 跑 21 条断言(转译借 vite dev server,
测的就是应用实际运行的那份)。守的是"审批只出现一次""没有裸 id""没有事件名
泄漏到标题"这类会悄悄退化的性质。
|
2026-09-17 16:25:33 +08:00 |
|
ThearchyHelios
|
76837d4ef0
|
dev.sh:端口被占时挪开,并确认答应健康检查的是自己
踩到的坑:8000 被另一个项目长期占着(绑 *:8000),uvicorn 起不来直接退出,
而健康检查去探 8000 又被那个项目答应了,脚本以为后端就绪继续起前端——
于是前端代理连着别人的服务,界面上全是陈年旧数据,还很难看出哪里不对。
三处收紧:
- 端口被占就自动往后挪(显式指定 AGENTLAB_PORT 时则明确报错并列出占用方)
- 健康检查要看到 service 是 agentlab 才算数,顺便看住后端进程别悄悄死了
- vite proxy 的目标端口跟着 AGENTLAB_PORT 走,不再硬编码 8000
|
2026-09-14 14:20:20 +08:00 |
|
ThearchyHelios
|
4e901be0ec
|
改用 conda 管理后端环境,移除 uv
environment.yml 只负责把解释器和依赖装进具名环境 agentlab,依赖清单仍以
backend/pyproject.toml 为准,不在两处各写一遍。dev.sh 通过 conda run 解析出
真实解释器路径再启动 uvicorn,环境不存在时自动按 environment.yml 创建。
uv.lock 一并删掉:留着会和 conda 装出来的环境慢慢漂移,两套锁反而更难查问题。
|
2026-09-14 14:09:51 +08:00 |
|
ThearchyHelios
|
964eeaa829
|
AgentLab:可视化 Agent 编排实验台
在画布上拖拽编排 agent 工作流,并实时看着它执行。
后端 FastAPI + LangGraph:把画布 JSON 编译成 StateGraph,15 种节点
(模型/Agent/多Agent、工具/沙箱、分支/循环/子图、记忆/检索/整形、
人工介入/结构校验)。选 LangGraph 是因为可视化编排真正难的是暂停、
恢复和回放,而 checkpointer + interrupt() 正好把这几件事做在了底层。
前端 React + React Flow:节点元数据驱动属性面板与连接桩,运行时
节点高亮、边流动画、卡片内流式 token 全部由同一份事件流驱动。
关键实现:
- Agent 内工具审批用 @task 缓存模型调用,interrupt 重放时不重复计费
- 沙箱保持只读根文件系统,代码经 exec+base64 写入(put_archive 会被
只读容器拒绝),断网、非 root、内存/CPU/超时限额
- 检索为向量与 BM25 混合,本地哈希 embedding 零配置可用
- 分支条件走 AST 白名单求值器;HTTP 工具逐 IP 拦截内网与元数据地址
- Mock provider 让无 API Key 也能跑通完整链路
7 个内置模板全部冒烟通过,含真实模型端到端验证。
|
2026-09-13 17:27:00 +08:00 |
|