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 |
|