docs: add technical architecture guide
@@ -0,0 +1,221 @@
|
||||
# 01. 基座 Runtime 架构
|
||||
|
||||

|
||||
|
||||
## 1. 项目定位
|
||||
|
||||
ZK Data Agent 的定位不是通用聊天机器人,也不是单人本地 IDE Agent,而是面向团队业务流程的 Web Agent 工作台。
|
||||
|
||||
它基于已有的本地工程 Agent 能力继续往上搭:
|
||||
|
||||
- `LocalCodingAgent` 提供多轮 Agent Loop。
|
||||
- OpenAI-compatible client 提供模型调用适配。
|
||||
- Tool registry 和 handler 提供可控执行能力。
|
||||
- Session workspace 提供每个会话独立的输入、中间文件和产物目录。
|
||||
- Skill system 把流程协议、业务知识、脚本和样例打包成可复用能力。
|
||||
- Memory worker 把用户偏好和 Skill 使用经验异步整理为可编辑记忆。
|
||||
|
||||
这个项目要解决的核心痛点是:团队里的数据开发、线上挖掘、标签判断等流程,往往散在口头经验、临时 prompt、个人脚本、聊天记录和本地文件里。每个人都能临时做一次,但很难让别人稳定复用、持续维护、形成可审计的产物链。
|
||||
|
||||
当前已经验证过的主要业务场景包括:
|
||||
|
||||
- `product-data`:从产品定义、标签边界、样例 query 或 badcase 出发,生成 canonical records,并导出流转 CSV、训练 JSONL、评测 CSV。
|
||||
- `online-mining-v2`:基于 ELK 日志挖掘线上样本,保留 request_id、timestamp、session、模型 prompt、模型输出、domain 等信息,并接入后续数据规范。
|
||||
- `label-master`:把复杂度、多指令、自动任务、标签定义、function 输出和边界经验整理为可检索知识体系。
|
||||
- 外部系统 Skill:ELK、SQL、模型迭代、飞书在线文档转换等能力以 Skill 方式接入,不侵入基座。
|
||||
|
||||
因此,基座 Runtime 的目标不是把某一条业务链路写死,而是提供一套稳定的承载层,让不同业务能力都能以 Skill 的方式运行、交付、沉淀和更新。
|
||||
|
||||
## 2. 基座解决的问题
|
||||
|
||||
基座不绑定某一个业务流程。它提供的是通用 Agent runtime:
|
||||
|
||||
- 多用户 Web 入口。
|
||||
- 多会话状态管理。
|
||||
- 模型选择和 OpenAI-compatible 调用。
|
||||
- Tool registry 和 tool handler 执行。
|
||||
- Skill 发现、启用和提示词注入。
|
||||
- 当前会话工作区。
|
||||
- 运行态、事件流、活动区和刷新恢复。
|
||||
- 用户记忆和 Skill 记忆后台。
|
||||
|
||||
业务能力例如 `product-data`、`online-mining-v2`、`label-master` 都跑在这个基座上。
|
||||
|
||||
## 3. 文字架构图
|
||||
|
||||
```text
|
||||
浏览器 / Web UI
|
||||
|
|
||||
| 用户消息、模型选择、Skill 启用、文件面板、停止运行
|
||||
v
|
||||
frontend/app
|
||||
|
|
||||
| /api/chat、/api/claw/*、/admin
|
||||
v
|
||||
backend/api/server.py
|
||||
|
|
||||
| 账号配置、会话目录、run manager、run_state_store、memory_manager
|
||||
v
|
||||
LocalCodingAgent
|
||||
|
|
||||
| build_session、prompt sections、tool_specs、Agent loop
|
||||
v
|
||||
OpenAICompatClient
|
||||
|
|
||||
| messages + tools -> model backend
|
||||
v
|
||||
模型
|
||||
|
|
||||
| assistant text 或 tool_calls
|
||||
v
|
||||
Tool Runtime
|
||||
|
|
||||
| read/write/python_exec/data_agent/MCP/search/bash
|
||||
v
|
||||
session workspace
|
||||
|
|
||||
| input / scratchpad / output / session.json
|
||||
v
|
||||
前端活动区和文件面板
|
||||
|
||||
运行结束后:
|
||||
|
||||
AgentRunResult
|
||||
-> session_store 持久化
|
||||
-> memory_manager.enqueue_interaction
|
||||
-> memory worker 异步整理
|
||||
-> 下一轮 render_injection 注入
|
||||
```
|
||||
|
||||
## 4. 关键代码入口
|
||||
|
||||
### 4.1 Web 后端入口
|
||||
|
||||
主要文件:
|
||||
|
||||
```text
|
||||
backend/api/server.py
|
||||
```
|
||||
|
||||
关键职责:
|
||||
|
||||
- 维护账号和会话配置。
|
||||
- 创建或复用 `LocalCodingAgent`。
|
||||
- 给 Agent 注入 `runtime_context`、记忆、Jupyter 上下文。
|
||||
- 记录 run event,并通过 NDJSON streaming 返回前端。
|
||||
- 运行结束后写入 elapsed、标题、memory event。
|
||||
|
||||
关键函数和位置:
|
||||
|
||||
```text
|
||||
backend/api/server.py:691 enabled_skill_names(...)
|
||||
backend/api/server.py:822 _build_agent(...)
|
||||
backend/api/server.py:853 agent_for(...)
|
||||
backend/api/server.py:866 run_lock_for(...)
|
||||
backend/api/server.py:1720 /api/chat 运行链路开始
|
||||
backend/api/server.py:1739 memory_manager.render_injection(...)
|
||||
backend/api/server.py:1751 emit_agent_event(...)
|
||||
backend/api/server.py:1897 序列化运行结果和 elapsed
|
||||
backend/api/server.py:1920 memory_manager.enqueue_interaction(...)
|
||||
backend/api/server.py:2034 StreamingResponse NDJSON
|
||||
```
|
||||
|
||||
### 4.2 Agent Runtime
|
||||
|
||||
主要文件:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py
|
||||
```
|
||||
|
||||
关键职责:
|
||||
|
||||
- 初始化 tool registry、plugin runtime、MCP runtime、search runtime 等。
|
||||
- 新会话用 `run(...)`,旧会话用 `resume(...)`。
|
||||
- 在 `_run_prompt(...)` 中执行完整 Agent loop。
|
||||
- 维护 usage、cost、tool_calls、events、file_history。
|
||||
- 在结束时持久化 session。
|
||||
|
||||
关键函数和位置:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:158 class LocalCodingAgent
|
||||
src/agent_runtime.py:195 __post_init__
|
||||
src/agent_runtime.py:413 run(...)
|
||||
src/agent_runtime.py:441 resume(...)
|
||||
src/agent_runtime.py:520 _run_prompt 主链路
|
||||
src/agent_runtime.py:552 tool_specs = [tool.to_openai_tool() ...]
|
||||
src/agent_runtime.py:619 for turn_index in range(...)
|
||||
src/agent_runtime.py:1008 遍历模型返回的 tool_calls
|
||||
src/agent_runtime.py:1490 _query_model(...)
|
||||
```
|
||||
|
||||
### 4.3 系统提示词
|
||||
|
||||
主要文件:
|
||||
|
||||
```text
|
||||
src/agent_prompting.py
|
||||
```
|
||||
|
||||
关键职责:
|
||||
|
||||
- 定义中控 Agent 身份。
|
||||
- 注入工具使用策略。
|
||||
- 注入工作空间边界。
|
||||
- 注入 Skill 列表。
|
||||
- 注入 ask_user 等 runtime 指导。
|
||||
|
||||
关键函数和位置:
|
||||
|
||||
```text
|
||||
src/agent_prompting.py:135 get_intro_section
|
||||
src/agent_prompting.py:155 get_doing_tasks_section
|
||||
src/agent_prompting.py:182 get_actions_section
|
||||
src/agent_prompting.py:196 get_workspace_boundary_section
|
||||
src/agent_prompting.py:210 get_using_your_tools_section
|
||||
src/agent_prompting.py:274 get_skill_guidance_section
|
||||
src/agent_prompting.py:414 get_ask_user_guidance_section
|
||||
```
|
||||
|
||||
## 5. 基座的分层职责
|
||||
|
||||
```text
|
||||
Web UI
|
||||
负责交互、展示、文件面板、活动区、Skill 勾选、管理后台。
|
||||
|
||||
Backend API
|
||||
负责账号、会话、模型配置、运行态、服务端事件流。
|
||||
|
||||
Agent Runtime
|
||||
负责 Agent loop、模型调用、工具调用、预算和持久化。
|
||||
|
||||
Prompting
|
||||
负责把规则、工具、公约、Skill 列表转成模型可见上下文。
|
||||
|
||||
Tool Runtime
|
||||
负责稳定执行动作,并把结果结构化回传给模型。
|
||||
|
||||
Skill System
|
||||
负责让业务流程、知识、脚本可被 Agent 发现和使用。
|
||||
|
||||
Workspace
|
||||
负责隔离每个用户和每个会话的输入、临时文件和交付产物。
|
||||
|
||||
Memory Worker
|
||||
负责从交互历史中异步整理长期偏好和 Skill 使用经验。
|
||||
```
|
||||
|
||||
## 6. 设计边界
|
||||
|
||||
基座只应该承载跨业务复用的稳定能力,例如:
|
||||
|
||||
- 工具执行。
|
||||
- 会话状态。
|
||||
- 运行态事件。
|
||||
- 工作区路径。
|
||||
- Skill 发现和启用。
|
||||
- 模型适配。
|
||||
- 记忆后台。
|
||||
|
||||
业务流程不应该写死在基座里。数据生成、线上挖掘、标签判断等变化快的流程应该沉到 Skill;确定性脚本应该放在对应 Skill 的 `scripts/` 下。
|
||||
@@ -0,0 +1,246 @@
|
||||
# 02. Agent Loop 执行机制
|
||||
|
||||

|
||||
|
||||
## 1. Agent Loop 的基本形态
|
||||
|
||||
Agent 每轮不是只调用一次模型,而是一个循环:
|
||||
|
||||
```text
|
||||
用户输入
|
||||
-> 构造 session 和 prompt
|
||||
-> 调模型
|
||||
-> 模型返回 assistant text 或 tool_calls
|
||||
-> 如果没有 tool_calls:输出最终回复,结束
|
||||
-> 如果有 tool_calls:执行工具
|
||||
-> 工具结果写回 session
|
||||
-> 下一轮模型继续读取工具结果
|
||||
-> 直到最终回复、预算超限、取消、max_turns 或 review 停止
|
||||
```
|
||||
|
||||
这个循环允许 Agent 做“观察-执行-再观察”的任务,例如:
|
||||
|
||||
- 先读文件,再决定是否需要抽取。
|
||||
- 先生成 plan,等待用户 review。
|
||||
- 先运行脚本,再根据校验结果修复。
|
||||
- 先查询线上数据,再抽样展示。
|
||||
|
||||
## 2. 新会话和恢复会话
|
||||
|
||||
新会话入口:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:413 run(...)
|
||||
```
|
||||
|
||||
关键动作:
|
||||
|
||||
```text
|
||||
1. 清理当前 managed_agent_id 和 resume_source_session_id。
|
||||
2. 创建 session_id。
|
||||
3. 创建 scratchpad_directory。
|
||||
4. 绑定 plan_runtime/task_runtime 到 scratchpad。
|
||||
5. 调 _run_prompt(...)
|
||||
6. 累计 usage。
|
||||
```
|
||||
|
||||
恢复会话入口:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:441 resume(...)
|
||||
```
|
||||
|
||||
关键动作:
|
||||
|
||||
```text
|
||||
1. 从 StoredAgentSession 恢复 AgentSessionState。
|
||||
2. 回放 file_history 和 compaction 信息。
|
||||
3. 设置 active_session_id 和 last_session_path。
|
||||
4. 恢复 plugin state。
|
||||
5. 复用已有 scratchpad_directory。
|
||||
6. 调 _run_prompt(...)
|
||||
```
|
||||
|
||||
这解释了为什么刷新页面、回到旧会话后,Agent 理论上可以继续同一个 session 的上下文,而不是新建一个任务。
|
||||
|
||||
## 3. `_run_prompt` 主链路
|
||||
|
||||
核心位置:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:520 _run_prompt 主体
|
||||
```
|
||||
|
||||
主链路关键步骤:
|
||||
|
||||
```text
|
||||
1. slash command 预处理。
|
||||
2. hook policy / plugin hook 修改 prompt。
|
||||
3. agent_manager.start_agent(...) 记录运行。
|
||||
4. 新建或复用 AgentSessionState。
|
||||
5. runtime_context prepend 到模型可见的用户消息。
|
||||
6. session.append_user(...)。
|
||||
7. tool_context 注入 scratchpad、plan_runtime、task_runtime。
|
||||
8. 生成 tool_specs。
|
||||
9. 初始化 usage、cost、tool_calls、events。
|
||||
10. 进入 turn loop。
|
||||
```
|
||||
|
||||
对应代码点:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:523 agent_manager.start_agent(...)
|
||||
src/agent_runtime.py:531 session = base_session or build_session(...)
|
||||
src/agent_runtime.py:539 _prepend_runtime_context(...)
|
||||
src/agent_runtime.py:543 session.append_user(...)
|
||||
src/agent_runtime.py:546 replace(self.tool_context, scratchpad_directory=...)
|
||||
src/agent_runtime.py:552 tool_specs = [tool.to_openai_tool() ...]
|
||||
src/agent_runtime.py:582 stream_events = _RuntimeEventBuffer(event_sink)
|
||||
src/agent_runtime.py:619 for turn_index in range(...)
|
||||
```
|
||||
|
||||
## 4. 模型调用和 tool_calls 判断
|
||||
|
||||
模型调用入口:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1490 _query_model(...)
|
||||
```
|
||||
|
||||
非流式路径中:
|
||||
|
||||
```text
|
||||
turn = self.client.complete(
|
||||
session.to_openai_messages(),
|
||||
tool_specs,
|
||||
output_schema=...
|
||||
)
|
||||
```
|
||||
|
||||
模型能否返回 `tool_calls` 取决于:
|
||||
|
||||
- 当前 `messages`。
|
||||
- 系统提示词。
|
||||
- Skill 列表。
|
||||
- 工具描述和参数 schema。
|
||||
- 模型自身 tool calling 能力。
|
||||
|
||||
基座不会强制某个工具被调用。基座只把工具能力暴露给模型,并在模型返回 `tool_calls` 后负责执行。
|
||||
|
||||
## 5. 没有 tool_calls 时
|
||||
|
||||
如果模型本轮没有工具调用:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:763 if not turn.tool_calls
|
||||
```
|
||||
|
||||
后续可能有三种情况:
|
||||
|
||||
1. 直接输出最终回复。
|
||||
2. 如果模型输出被截断,自动追加 continuation prompt。
|
||||
3. 如果达到 continuation 限制,则追加提示并结束。
|
||||
|
||||
最终会:
|
||||
|
||||
```text
|
||||
session.append_assistant(...)
|
||||
_append_final_text_stream_events(...)
|
||||
AgentRunResult(...)
|
||||
_persist_session(...)
|
||||
```
|
||||
|
||||
## 6. 有 tool_calls 时
|
||||
|
||||
如果模型返回工具调用:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1008 for tool_call in turn.tool_calls
|
||||
```
|
||||
|
||||
每个工具调用会:
|
||||
|
||||
```text
|
||||
1. tool_calls 计数 +1。
|
||||
2. 检查预算。
|
||||
3. session.start_tool(...) 写入工具开始消息。
|
||||
4. stream_events 追加 tool_start。
|
||||
5. 根据工具名执行 handler。
|
||||
6. 工具结果写回 session。
|
||||
7. stream_events 追加工具结果。
|
||||
8. 下一轮模型读取工具结果继续判断。
|
||||
```
|
||||
|
||||
关键代码点:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1054 session.start_tool(...)
|
||||
src/agent_runtime.py:1060 stream_events.append(type='tool_start')
|
||||
src/agent_tools.py:60 execute_tool(...)
|
||||
src/agent_tools.py:76 execute_tool_streaming(...)
|
||||
```
|
||||
|
||||
## 7. 为什么 review 可以暂停流程
|
||||
|
||||
review 门禁不是特殊 UI 魔法,而是 Agent loop 的自然结果:
|
||||
|
||||
1. Skill 要求模型在某一步调用 review 工具。
|
||||
2. 工具创建 pending state,并返回需要展示的信息。
|
||||
3. Agent 生成回复,告诉用户需要确认。
|
||||
4. 当前 run 结束。
|
||||
5. 用户下一轮回复“确认”。
|
||||
6. Agent resume 旧 session,调用 confirm 工具。
|
||||
7. 后续流程继续。
|
||||
|
||||
例如 `product-data`:
|
||||
|
||||
```text
|
||||
data_agent_prepare_generation_goal
|
||||
-> pending goal
|
||||
-> 停止等待用户确认
|
||||
|
||||
data_agent_confirm_generation_goal
|
||||
-> confirmed_goal_id
|
||||
-> data_agent_prepare_generation_plan
|
||||
-> pending plan
|
||||
-> 停止等待用户确认
|
||||
|
||||
data_agent_confirm_generation_plan
|
||||
-> confirmed_plan_id
|
||||
-> 允许生成 draft 和转换 records
|
||||
```
|
||||
|
||||
## 8. 预算、取消和 max_turns
|
||||
|
||||
基座会在模型调用前后、工具请求前检查预算:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:592 initial_budget = self._check_budget(...)
|
||||
src/agent_runtime.py:732 budget_after_model = self._check_budget(...)
|
||||
src/agent_runtime.py:1014 budget_after_tool_request = self._check_budget(...)
|
||||
```
|
||||
|
||||
如果达到最大轮次:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1440 _build_max_turns_output(...)
|
||||
```
|
||||
|
||||
输出会包含最后一次阶段说明,提醒用户可以继续补充指令。
|
||||
|
||||
取消由后端 run manager 和 tool process registry 处理。Web 点停止后,后端将 run 标记取消,并让工具执行上下文感知 cancel_event。
|
||||
|
||||
## 9. 事件流和前端活动区
|
||||
|
||||
Agent loop 内会不断追加 `stream_events`,后端用 `event_sink` 把事件推给前端。
|
||||
|
||||
后端关键代码:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1751 emit_agent_event(event)
|
||||
backend/api/server.py:1756 state.run_manager.record_event(...)
|
||||
backend/api/server.py:1758 state.run_state_store.record_event(...)
|
||||
backend/api/server.py:2034 StreamingResponse(..., media_type='application/x-ndjson')
|
||||
```
|
||||
|
||||
前端活动区展示的阶段说明、工具开始、工具完成、最终文本,本质上都来自这些 runtime events 或 session replay。
|
||||
@@ -0,0 +1,298 @@
|
||||
# 03. 工具体系和 Tool Handler
|
||||
|
||||

|
||||
|
||||
## 1. 工具体系的职责
|
||||
|
||||
Tool 是执行层。模型只能“请求调用工具”,不能直接执行工具。
|
||||
后端根据工具名找到 handler,校验参数,执行动作,并把结果写回 session。
|
||||
|
||||
工具适合承载:
|
||||
|
||||
- 稳定文件读写。
|
||||
- Python 执行。
|
||||
- Python 包安装。
|
||||
- 数据格式转换和校验。
|
||||
- review 状态机。
|
||||
- 外部系统访问。
|
||||
- 需要权限、取消、路径约束或审计的动作。
|
||||
|
||||
## 2. Tool registry 构成
|
||||
|
||||
入口:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:105 default_tool_registry()
|
||||
```
|
||||
|
||||
它会合并多类工具:
|
||||
|
||||
```text
|
||||
build_file_tools(...)
|
||||
build_execution_tools(...)
|
||||
LSP / web_fetch / search / account / config / task / team / workflow 等
|
||||
Skill 工具
|
||||
data_agent 工具
|
||||
```
|
||||
|
||||
关键片段:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:107 build_file_tools
|
||||
src/agent_tools.py:116 build_execution_tools
|
||||
src/agent_tools.py:1012 AgentTool(name='Skill')
|
||||
src/agent_tools.py:1033 build_data_agent_tools
|
||||
```
|
||||
|
||||
返回结果是:
|
||||
|
||||
```python
|
||||
return {tool.name: tool for tool in tools}
|
||||
```
|
||||
|
||||
## 3. Tool spec 注入模型
|
||||
|
||||
在 Agent loop 中:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:552
|
||||
tool_specs = [tool.to_openai_tool() for tool in self.tool_registry.values()]
|
||||
```
|
||||
|
||||
每个工具包含:
|
||||
|
||||
```text
|
||||
name
|
||||
description
|
||||
parameters JSON schema
|
||||
handler
|
||||
```
|
||||
|
||||
模型看到的是 name、description、parameters。handler 不暴露给模型,只在后端执行。
|
||||
|
||||
## 4. Tool handler 执行路径
|
||||
|
||||
工具执行入口:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:60 execute_tool(...)
|
||||
src/agent_tools.py:76 execute_tool_streaming(...)
|
||||
```
|
||||
|
||||
执行逻辑:
|
||||
|
||||
```text
|
||||
1. 从 tool_registry 取 tool。
|
||||
2. 找不到则返回 Unknown tool。
|
||||
3. bash 走 streaming。
|
||||
4. 其他工具调用 tool.execute(arguments, context)。
|
||||
5. 工具结果序列化回模型和前端。
|
||||
```
|
||||
|
||||
## 5. 文件工具
|
||||
|
||||
定义位置:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/files.py
|
||||
```
|
||||
|
||||
主要工具:
|
||||
|
||||
- `list_dir`
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `notebook_edit`
|
||||
- `glob_search`
|
||||
- `grep_search`
|
||||
|
||||
### 5.1 `write_file` 的边界
|
||||
|
||||
`write_file` 当前明确定位为“小文件工具”:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/files.py
|
||||
Write or append a SMALL UTF-8 file inside the workspace.
|
||||
```
|
||||
|
||||
适合:
|
||||
|
||||
- 短 Markdown。
|
||||
- 少量配置。
|
||||
- 小 JSON。
|
||||
- 少量 JSONL。
|
||||
- 小 CSV。
|
||||
|
||||
不适合:
|
||||
|
||||
- 长 Python 脚本。
|
||||
- 大 JSON。
|
||||
- JSONL 数据集。
|
||||
- 长 CSV。
|
||||
- 大段包含引号和换行的内容。
|
||||
|
||||
原因是模型生成工具参数时,需要把内容嵌入 JSON 参数。长文本、引号、换行会显著增加非法 JSON 参数概率。
|
||||
|
||||
因此系统提示词也要求:
|
||||
|
||||
```text
|
||||
复杂写文件优先用 python_exec 通过 pathlib/json/csv 写入。
|
||||
```
|
||||
|
||||
## 6. Python 执行工具
|
||||
|
||||
定义位置:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/execution.py
|
||||
```
|
||||
|
||||
主要工具:
|
||||
|
||||
- `python_exec`
|
||||
- `python_package`
|
||||
- `bash`
|
||||
- `sleep`
|
||||
|
||||
### 6.1 `python_exec`
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
结构化文件分析、JSON/JSONL 处理、批量校验、数据抽样、快速计算。
|
||||
```
|
||||
|
||||
支持两种模式:
|
||||
|
||||
```text
|
||||
code
|
||||
一次性 Python 代码。
|
||||
|
||||
script_path
|
||||
调用项目或 Skill 内已有脚本。
|
||||
```
|
||||
|
||||
重要约束:
|
||||
|
||||
- 不用 `bash python ...`。
|
||||
- 不在 `python_exec.code` 里用 subprocess 二次调用 Python 脚本。
|
||||
- 临时文件写入 `PYTHON_EXEC_SCRATCHPAD`。
|
||||
- 交付产物写入 session/output。
|
||||
|
||||
### 6.2 `python_package`
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
给当前用户独立 Python venv 安装缺失包。
|
||||
```
|
||||
|
||||
典型场景:
|
||||
|
||||
- `pandas`
|
||||
- `pyarrow`
|
||||
- `openpyxl`
|
||||
- `elasticsearch`
|
||||
- `urllib3`
|
||||
|
||||
设计上避免安装到项目 `.venv` 或系统 Python。
|
||||
|
||||
### 6.3 `bash`
|
||||
|
||||
`bash` 是兜底工具,不是默认 Python 执行方式。
|
||||
|
||||
适合:
|
||||
|
||||
- git 只读检查。
|
||||
- 系统命令。
|
||||
- 进程控制。
|
||||
- 用户确认后的依赖安装或服务管理。
|
||||
|
||||
不适合:
|
||||
|
||||
- Python 数据分析。
|
||||
- JSON/CSV 转换。
|
||||
- 包安装。
|
||||
|
||||
## 7. 路径解析和 session 路由
|
||||
|
||||
关键实现:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1589 _data_agent_output_path(...)
|
||||
src/agent_tools.py:1624 _data_agent_session_output_root(...)
|
||||
src/agent_tools.py:1630 _resolve_path(...)
|
||||
src/agent_tools.py:1658 _session_logical_path(...)
|
||||
src/agent_tools.py:1685 _execution_cwd(...)
|
||||
```
|
||||
|
||||
逻辑路径会被映射到当前 session:
|
||||
|
||||
```text
|
||||
output/... -> session/output/...
|
||||
outputs/... -> session/output/...
|
||||
scratchpad/... -> session/scratchpad/...
|
||||
scratch/... -> session/scratchpad/...
|
||||
input/... -> session/input/...
|
||||
inputs/... -> session/input/...
|
||||
```
|
||||
|
||||
`python_exec` 的执行 cwd 优先是当前会话 scratchpad:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1685 _execution_cwd(...)
|
||||
```
|
||||
|
||||
## 8. data_agent 工具
|
||||
|
||||
声明位置:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/data_agent.py
|
||||
```
|
||||
|
||||
handler 注册位置:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1033 build_data_agent_tools(...)
|
||||
```
|
||||
|
||||
主要工具分两类:
|
||||
|
||||
### 8.1 前链路和 review 状态机
|
||||
|
||||
- `data_agent_load_input_sources`
|
||||
- `data_agent_render_source_context`
|
||||
- `data_agent_extract_case_evidence`
|
||||
- `data_agent_prepare_generation_goal`
|
||||
- `data_agent_confirm_generation_goal`
|
||||
- `data_agent_prepare_generation_plan`
|
||||
- `data_agent_show_generation_plan`
|
||||
- `data_agent_update_generation_plan`
|
||||
- `data_agent_confirm_generation_plan`
|
||||
|
||||
这类工具当前仍属于平台工具,因为它们维护 pending/confirmed 状态,以及产品数据生成的 review 门禁。
|
||||
|
||||
### 8.2 历史格式转换工具
|
||||
|
||||
- `data_agent_normalize_dataset_draft`
|
||||
- `data_agent_validate_dataset_records`
|
||||
- `data_agent_export_dataset_records`
|
||||
- `data_agent_export_training_jsonl`
|
||||
- `data_agent_export_planning_eval_csv`
|
||||
|
||||
这部分能力已经逐步迁移到 `skills/product-data/scripts/`,平台工具更多是历史兼容和适配层。
|
||||
|
||||
## 9. 工具设计原则
|
||||
|
||||
当前工具体系的技术取舍:
|
||||
|
||||
```text
|
||||
模型负责决策。
|
||||
工具负责执行。
|
||||
Skill 负责流程经验。
|
||||
脚本负责可迁移确定性能力。
|
||||
```
|
||||
|
||||
工具描述要足够明确,否则模型会选错工具;参数 schema 要尽量简单,否则不同模型后端可能不兼容。
|
||||
@@ -0,0 +1,256 @@
|
||||
# 04. Skill 体系和能力包约定
|
||||
|
||||

|
||||
|
||||
## 1. Skill 的定位
|
||||
|
||||
Skill 是经验层。它不是单纯 prompt,也不是单纯脚本。
|
||||
|
||||
一个 Skill 应该回答:
|
||||
|
||||
- 什么场景触发。
|
||||
- 输入材料是什么。
|
||||
- Agent 应该按什么流程做。
|
||||
- 哪些地方必须让用户 review。
|
||||
- 应该调用哪些工具或脚本。
|
||||
- 产物应该写到哪里。
|
||||
- 哪些做法是禁止的。
|
||||
|
||||
稳定可执行逻辑不应该长期写在 Skill 文本里,而应该进入:
|
||||
|
||||
```text
|
||||
skills/<skill-name>/scripts/
|
||||
```
|
||||
|
||||
或者平台级工具。
|
||||
|
||||
## 2. Skill loader 实现
|
||||
|
||||
关键文件:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py
|
||||
```
|
||||
|
||||
项目级 Skill 目录:
|
||||
|
||||
```text
|
||||
skills/<skill-name>/SKILL.md
|
||||
```
|
||||
|
||||
核心数据结构:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:28 BundledSkill
|
||||
```
|
||||
|
||||
字段:
|
||||
|
||||
```text
|
||||
name
|
||||
description
|
||||
when_to_use
|
||||
aliases
|
||||
allowed_tools
|
||||
user_invocable
|
||||
source
|
||||
path
|
||||
get_prompt
|
||||
```
|
||||
|
||||
## 3. SKILL.md 解析
|
||||
|
||||
解析逻辑:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:147 _parse_front_matter
|
||||
src/bundled_skills.py:176 _load_directory_skill
|
||||
```
|
||||
|
||||
`SKILL.md` 必须有 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: product-data
|
||||
description: 从产品/标签定义、手写边界规则或示例 query 中提取标签边界...
|
||||
when_to_use: 当用户提供产品定义、标签规则...
|
||||
aliases: definition-data, label-data
|
||||
allowed_tools: read_file, write_file, python_exec
|
||||
---
|
||||
```
|
||||
|
||||
解析后:
|
||||
|
||||
- frontmatter 进入 `BundledSkill` 元数据。
|
||||
- body 作为真正的 Skill prompt。
|
||||
- 如果调用 Skill 时带 args,会追加到 `## Invocation Arguments`。
|
||||
|
||||
对应实现:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:138 _directory_skill_prompt
|
||||
```
|
||||
|
||||
## 4. Skill 发现顺序
|
||||
|
||||
项目 Skill 发现入口:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:199 load_directory_skills
|
||||
src/bundled_skills.py:215 load_project_skills
|
||||
```
|
||||
|
||||
系统提示词中可见 Skill 列表由:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:270 format_skills_for_system_prompt
|
||||
```
|
||||
|
||||
生成。
|
||||
|
||||
Web 后端会按当前账号和 session 配置计算启用 Skill:
|
||||
|
||||
```text
|
||||
backend/api/server.py:691 enabled_skill_names
|
||||
backend/api/server.py:706 set_skill_enabled
|
||||
backend/api/server.py:738 set_all_skills_enabled
|
||||
```
|
||||
|
||||
这意味着:
|
||||
|
||||
- Skill 可以存在于项目中,但不一定对某个 session 启用。
|
||||
- 启用状态影响系统提示词里的 Skill 列表。
|
||||
- 被禁用的 Skill 不应该被模型主动选择。
|
||||
|
||||
## 5. Skill 工具
|
||||
|
||||
Skill 本身也是一个工具:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1012 AgentTool(name='Skill')
|
||||
```
|
||||
|
||||
模型调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "product-data",
|
||||
"args": "用户原始需求或显式参数"
|
||||
}
|
||||
```
|
||||
|
||||
执行后,Skill body 会被加入对话,让模型按 Skill 中的流程继续做任务。
|
||||
|
||||
## 6. Skill 和 Agent Loop 的关系
|
||||
|
||||
Skill 不会替代 Agent loop,而是改变 Agent loop 的下一步决策依据。
|
||||
|
||||
典型模式:
|
||||
|
||||
```text
|
||||
用户提出任务
|
||||
-> 模型从 Skill 列表中选择某个 Skill
|
||||
-> 调用 Skill 工具
|
||||
-> Skill.md 正文进入上下文
|
||||
-> 模型按 Skill 指令调用 read_file/python_exec/data_agent 等工具
|
||||
-> 工具结果进入上下文
|
||||
-> 模型继续按 Skill 流程推进
|
||||
```
|
||||
|
||||
因此 Skill 的好坏直接影响:
|
||||
|
||||
- 模型能否召回正确能力。
|
||||
- 是否会过早执行。
|
||||
- 是否能在 review 门禁停下来。
|
||||
- 是否能使用正确工具而不是手写不稳定逻辑。
|
||||
|
||||
## 7. 推荐 Skill 目录结构
|
||||
|
||||
```text
|
||||
skills/<skill-name>/
|
||||
SKILL.md
|
||||
README.md
|
||||
knowledge/
|
||||
scripts/
|
||||
examples/
|
||||
schemas/
|
||||
tools.yaml
|
||||
requirements.txt
|
||||
```
|
||||
|
||||
各部分职责:
|
||||
|
||||
```text
|
||||
SKILL.md
|
||||
运行时入口,写流程、门禁、工具调用方式和禁止事项。
|
||||
|
||||
README.md
|
||||
给维护者看的说明,不一定进入模型上下文。
|
||||
|
||||
knowledge/
|
||||
业务知识、标签规则、字段说明、边界案例。
|
||||
|
||||
scripts/
|
||||
确定性脚本,优先通过 python_exec.script_path 执行。
|
||||
|
||||
examples/
|
||||
示例输入输出,用于回归和讲解。
|
||||
|
||||
schemas/
|
||||
JSON schema 或字段约定。
|
||||
|
||||
tools.yaml
|
||||
描述 portable scripts 如何注册为工具,便于迁移到其他 Agent。
|
||||
|
||||
requirements.txt
|
||||
Skill 脚本的 Python 依赖。
|
||||
```
|
||||
|
||||
## 8. Skill 更新
|
||||
|
||||
Web 后端提供 Skill 更新能力:
|
||||
|
||||
```text
|
||||
backend/api/server.py:765 sync_skills_from_git
|
||||
```
|
||||
|
||||
核心行为:
|
||||
|
||||
```text
|
||||
1. 检查当前目录是否是 git 仓库。
|
||||
2. 检查 tracked 文件是否干净。
|
||||
3. git fetch origin。
|
||||
4. git pull --ff-only origin 当前分支。
|
||||
5. 清理当前账号 agent cache。
|
||||
6. 重新读取 get_bundled_skills。
|
||||
```
|
||||
|
||||
这让新增或修改 Skill 后,不一定需要重启服务才能让 Skill 列表刷新。
|
||||
|
||||
## 9. Skill 设计边界
|
||||
|
||||
适合写在 Skill:
|
||||
|
||||
- 工作流。
|
||||
- 何时提问。
|
||||
- 何时 review。
|
||||
- 哪些工具优先。
|
||||
- 输出位置约定。
|
||||
- 常见失败经验。
|
||||
|
||||
不适合长期写在 Skill:
|
||||
|
||||
- 大段可检索知识。
|
||||
- 复杂代码。
|
||||
- 格式转换。
|
||||
- 查询外部系统的具体实现。
|
||||
- 需要校验的稳定数据结构。
|
||||
|
||||
这些应该分别放到:
|
||||
|
||||
```text
|
||||
knowledge/
|
||||
scripts/
|
||||
schemas/
|
||||
platform tools
|
||||
```
|
||||
@@ -0,0 +1,243 @@
|
||||
# 05. 会话工作区、运行态和记忆
|
||||
|
||||

|
||||
|
||||
## 1. 会话工作区
|
||||
|
||||
每个用户、每个会话都有独立目录:
|
||||
|
||||
```text
|
||||
.port_sessions/accounts/<account_id>/sessions/<session_id>/
|
||||
input/
|
||||
scratchpad/
|
||||
output/
|
||||
session.json
|
||||
```
|
||||
|
||||
目录职责:
|
||||
|
||||
```text
|
||||
input/
|
||||
用户上传或明确提供的输入资料。
|
||||
|
||||
scratchpad/
|
||||
临时脚本、中间文件、抽样缓存、断点记录。
|
||||
|
||||
output/
|
||||
最终交付产物。
|
||||
|
||||
session.json
|
||||
会话消息、工具调用、usage、events、file_history、runtime metadata。
|
||||
```
|
||||
|
||||
## 2. 工作区路径路由
|
||||
|
||||
路径解析实现:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1658 _session_logical_path
|
||||
```
|
||||
|
||||
逻辑路径:
|
||||
|
||||
```text
|
||||
output/... -> session/output/...
|
||||
outputs/... -> session/output/...
|
||||
scratchpad/... -> session/scratchpad/...
|
||||
scratch/... -> session/scratchpad/...
|
||||
input/... -> session/input/...
|
||||
inputs/... -> session/input/...
|
||||
```
|
||||
|
||||
Python 执行 cwd:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1685 _execution_cwd
|
||||
```
|
||||
|
||||
优先使用当前 session scratchpad。这是为了避免临时脚本和缓存污染项目根目录。
|
||||
|
||||
## 3. 平台代码写保护
|
||||
|
||||
相关实现:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1118 _PLATFORM_READONLY_DIRS
|
||||
src/agent_tools.py:1698 _is_platform_code_path
|
||||
src/agent_tools.py:1708 _ensure_not_platform_code_write
|
||||
```
|
||||
|
||||
当前平台目录:
|
||||
|
||||
```text
|
||||
src
|
||||
backend
|
||||
frontend
|
||||
scripts
|
||||
```
|
||||
|
||||
在数据 Agent 会话中默认视为只读。除非用户明确进入平台开发任务,否则业务任务不应修改平台代码。
|
||||
|
||||
## 4. Run 状态和活动区
|
||||
|
||||
后端在 `/api/chat` 运行时创建 run record:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1720 run_record = state.run_manager.start(...)
|
||||
backend/api/server.py:1722 state.run_state_store.start(...)
|
||||
```
|
||||
|
||||
运行事件通过 `emit_agent_event` 记录:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1751 emit_agent_event
|
||||
backend/api/server.py:1756 run_manager.record_event
|
||||
backend/api/server.py:1758 run_state_store.record_event
|
||||
```
|
||||
|
||||
返回前端:
|
||||
|
||||
```text
|
||||
backend/api/server.py:2034 StreamingResponse
|
||||
```
|
||||
|
||||
前端活动区看到的内容主要来自:
|
||||
|
||||
- `run_started`
|
||||
- `tool_start`
|
||||
- `tool_result`
|
||||
- 模型阶段说明
|
||||
- final text stream events
|
||||
- run finish/error/cancel 状态
|
||||
|
||||
## 5. 会话持久化
|
||||
|
||||
运行结束后,后端序列化结果:
|
||||
|
||||
```text
|
||||
backend/api/server.py:2056 _serialize_run_result
|
||||
backend/api/server.py:2069 _normalize_transcript_entry
|
||||
```
|
||||
|
||||
会话读取:
|
||||
|
||||
```text
|
||||
backend/api/server.py:2094 _serialize_stored_session
|
||||
```
|
||||
|
||||
`agent_runtime` 在多个结束路径都会调用:
|
||||
|
||||
```text
|
||||
_persist_session(session, result)
|
||||
```
|
||||
|
||||
这让刷新后可以恢复:
|
||||
|
||||
- 用户消息。
|
||||
- assistant 文本。
|
||||
- tool_calls。
|
||||
- tool result。
|
||||
- elapsed。
|
||||
- file_history。
|
||||
|
||||
## 6. 记忆体系
|
||||
|
||||
实现位置:
|
||||
|
||||
```text
|
||||
src/personal_memory.py
|
||||
```
|
||||
|
||||
### 6.1 文件和数据库
|
||||
|
||||
```text
|
||||
memory.db
|
||||
user.md
|
||||
skills/<skill-name>.md
|
||||
```
|
||||
|
||||
常量:
|
||||
|
||||
```text
|
||||
src/personal_memory.py:26 MEMORY_DB_FILENAME
|
||||
src/personal_memory.py:27 USER_MEMORY_FILENAME
|
||||
src/personal_memory.py:28 SKILL_MEMORY_DIRNAME
|
||||
```
|
||||
|
||||
### 6.2 注入逻辑
|
||||
|
||||
```text
|
||||
src/personal_memory.py:102 render_injection
|
||||
```
|
||||
|
||||
注入规则:
|
||||
|
||||
```text
|
||||
1. 读取 user.md。
|
||||
2. 根据 enabled_skill_names 读取对应 skills/<skill>.md。
|
||||
3. 拼成 # 个性化记忆。
|
||||
4. 如果用户本轮要求冲突,以本轮要求为准。
|
||||
```
|
||||
|
||||
后端调用:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1739 memory_manager.render_injection(...)
|
||||
```
|
||||
|
||||
### 6.3 入队逻辑
|
||||
|
||||
运行结束后:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1920 memory_manager.enqueue_interaction(...)
|
||||
```
|
||||
|
||||
记忆模块内:
|
||||
|
||||
```text
|
||||
src/personal_memory.py:138 enqueue_interaction
|
||||
src/personal_memory.py:635 detect_memory_signals
|
||||
```
|
||||
|
||||
会检测:
|
||||
|
||||
- 显式记忆词:记住、以后、下次、默认、总是、不要、应该、固定。
|
||||
- 纠错词:不对、不是这样、格式错、之前说过、还是不行。
|
||||
- Skill/工具/流程/格式相关表述。
|
||||
- 工具参数非法 JSON 等工具经验。
|
||||
|
||||
### 6.4 后台整理
|
||||
|
||||
核心逻辑:
|
||||
|
||||
```text
|
||||
src/personal_memory.py:378 _consolidate_events
|
||||
src/personal_memory.py:419 _generate_memory_updates
|
||||
src/personal_memory.py:471 _fallback_memory_updates
|
||||
```
|
||||
|
||||
设计取舍:
|
||||
|
||||
- 主链路不直接生成记忆。
|
||||
- 事件先进入 SQLite 队列。
|
||||
- 后台 worker 批量整理。
|
||||
- LLM 失败时有 fallback 规则。
|
||||
- Markdown 是最终可编辑记忆正文。
|
||||
|
||||
## 7. 可观测性设计
|
||||
|
||||
当前可观测性来自三个层次:
|
||||
|
||||
```text
|
||||
运行态
|
||||
run_manager + run_state_store,支持运行中刷新、停止、恢复活动区。
|
||||
|
||||
会话态
|
||||
session.json,保存完整消息和工具调用。
|
||||
|
||||
产物态
|
||||
session/input、scratchpad、output,文件面板可查看和下载。
|
||||
```
|
||||
|
||||
这个设计让用户不仅看到最终回复,也能看到 Agent 做了什么、文件在哪里、失败在哪个工具或阶段。
|
||||
@@ -0,0 +1,292 @@
|
||||
# 06. product-data Skill 实现
|
||||
|
||||

|
||||
|
||||
## 1. 定位
|
||||
|
||||
`product-data` 是数据开发链路的核心 Skill,负责:
|
||||
|
||||
```text
|
||||
产品定义 / 标签规则 / 手写边界 / 示例 query / badcase
|
||||
-> 输入文本化
|
||||
-> generation goal
|
||||
-> 用户 review
|
||||
-> generation plan
|
||||
-> 用户确认数量、标签、边界、路径
|
||||
-> dataset draft text
|
||||
-> canonical records
|
||||
-> validate
|
||||
-> export records / table / training / eval
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/product-data/SKILL.md
|
||||
skills/product-data/knowledge/
|
||||
skills/product-data/scripts/
|
||||
```
|
||||
|
||||
## 2. Skill 目录结构
|
||||
|
||||
当前 `SKILL.md` 中定义的能力组织:
|
||||
|
||||
```text
|
||||
skills/product-data/
|
||||
SKILL.md
|
||||
tools.yaml
|
||||
requirements.txt
|
||||
knowledge/
|
||||
dataset_draft_v1.md
|
||||
canonical_record_v1.md
|
||||
portable_skill_contract.md
|
||||
schemas/
|
||||
scripts/
|
||||
normalize_dataset_draft.py
|
||||
validate_dataset_records.py
|
||||
export_dataset_records.py
|
||||
export_dataset_table.py
|
||||
export_training_jsonl.py
|
||||
export_planning_eval_csv.py
|
||||
```
|
||||
|
||||
分工:
|
||||
|
||||
```text
|
||||
SKILL.md
|
||||
流程协议、门禁、工具调用顺序、禁止事项。
|
||||
|
||||
knowledge/
|
||||
数据草稿、canonical record 和 portable skill contract。
|
||||
|
||||
scripts/
|
||||
确定性转换、校验和导出。
|
||||
|
||||
data_agent_* 平台工具
|
||||
负责输入文本化和 review 状态机。
|
||||
```
|
||||
|
||||
## 3. 输入类型
|
||||
|
||||
Skill 将输入分成三类:
|
||||
|
||||
```text
|
||||
文件定义型
|
||||
产品定义、标签定义、路由规则、表格、Markdown、CSV。
|
||||
|
||||
手写规则型
|
||||
用户直接描述边界,例如“找附近美食给餐饮服务,导航去某地给地图导航”。
|
||||
|
||||
示例归纳型
|
||||
用户只给 query/example/badcase,需要先归纳边界和标签倾向。
|
||||
```
|
||||
|
||||
三类输入最后统一整理成:
|
||||
|
||||
```text
|
||||
dataset_label
|
||||
target / target_definitions
|
||||
plan_hint
|
||||
coverage
|
||||
exclusions
|
||||
open_questions
|
||||
source_refs
|
||||
complex 规则
|
||||
```
|
||||
|
||||
这一步称为 `generation_goal`。
|
||||
|
||||
## 4. Review 门禁
|
||||
|
||||
`product-data` 有两个门禁模式。
|
||||
|
||||
### 4.1 一次确认模式
|
||||
|
||||
适用:
|
||||
|
||||
- 用户直接给出清晰手写规则。
|
||||
- target 表达明确。
|
||||
- 用户已经希望生成数据。
|
||||
|
||||
链路:
|
||||
|
||||
```text
|
||||
data_agent_prepare_generation_plan(direct_review=true)
|
||||
-> 展示目标 + 数量 + 路径
|
||||
-> 等待“确认,开始生成”
|
||||
```
|
||||
|
||||
### 4.2 两段确认模式
|
||||
|
||||
适用:
|
||||
|
||||
- 用户提供文件、表格、badcase、长文档。
|
||||
- 标签、边界、字段有歧义。
|
||||
- 需要先从资料中抽取 generation goal。
|
||||
|
||||
链路:
|
||||
|
||||
```text
|
||||
data_agent_load_input_sources
|
||||
-> data_agent_render_source_context
|
||||
-> Agent 整理 generation_goal
|
||||
-> data_agent_prepare_generation_goal
|
||||
-> 用户确认目标
|
||||
-> data_agent_confirm_generation_goal
|
||||
-> data_agent_prepare_generation_plan
|
||||
-> 用户确认计划
|
||||
-> data_agent_confirm_generation_plan
|
||||
```
|
||||
|
||||
## 5. 和 Agent Loop 的关系
|
||||
|
||||
`product-data` 明确利用 Agent loop 做分阶段控制。
|
||||
|
||||
```text
|
||||
第一轮:
|
||||
模型选择 product-data
|
||||
调输入工具
|
||||
调 prepare_generation_goal 或 prepare_generation_plan
|
||||
输出 review 信息
|
||||
停止
|
||||
|
||||
第二轮:
|
||||
用户确认目标或计划
|
||||
Agent resume session
|
||||
调 confirm 工具
|
||||
继续下一阶段
|
||||
|
||||
第三轮:
|
||||
用户确认计划
|
||||
Agent 生成 dataset draft
|
||||
每批调用 normalize 脚本
|
||||
调 validate 脚本
|
||||
调 export 脚本
|
||||
输出最终文件路径
|
||||
```
|
||||
|
||||
重点是:确认状态不是靠自由文本记忆,而是由工具维护 `confirmed_goal_id` 和 `confirmed_plan_id`。
|
||||
|
||||
## 6. 数据格式设计
|
||||
|
||||
### 6.1 dataset draft text
|
||||
|
||||
面向模型生成,要求模型用较低结构负担描述:
|
||||
|
||||
- 用户 / 小爱 对话。
|
||||
- 当前 query。
|
||||
- target。
|
||||
- complex。
|
||||
- 场景说明。
|
||||
|
||||
设计目标是降低模型直接写 JSONL 的难度。
|
||||
|
||||
### 6.2 canonical record
|
||||
|
||||
面向工具处理,字段稳定。
|
||||
|
||||
用于:
|
||||
|
||||
- 校验结构。
|
||||
- 导出流转 CSV。
|
||||
- 导出训练 JSONL。
|
||||
- 导出评测 planningPrompt CSV。
|
||||
|
||||
### 6.3 complex 独立维度
|
||||
|
||||
`complex` 不属于 target。
|
||||
|
||||
内部保存:
|
||||
|
||||
```text
|
||||
complex: false
|
||||
target: Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
训练输出组合:
|
||||
|
||||
```text
|
||||
complex=false
|
||||
Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
评测 CSV 里:
|
||||
|
||||
```text
|
||||
code标签: Agent(tag="地图导航")
|
||||
complex: FALSE
|
||||
```
|
||||
|
||||
## 7. 脚本链路
|
||||
|
||||
生成式数据确认后,Skill 要求使用 `python_exec.script_path` 调脚本。
|
||||
|
||||
典型顺序:
|
||||
|
||||
```text
|
||||
normalize_dataset_draft.py
|
||||
输入 draft + confirmed_plan_id
|
||||
输出/追加 scratchpad/normalized_records.jsonl
|
||||
|
||||
validate_dataset_records.py
|
||||
读取 normalized_records.jsonl
|
||||
校验字段、target、complex、时间戳、多轮上下文
|
||||
|
||||
export_dataset_records.py
|
||||
导出 output/records.jsonl
|
||||
同时生成 output/records.csv
|
||||
|
||||
export_training_jsonl.py
|
||||
导出 output/training.jsonl
|
||||
|
||||
export_planning_eval_csv.py
|
||||
导出 output/eval_planning.csv
|
||||
```
|
||||
|
||||
为什么不用 `write_file`:
|
||||
|
||||
- records、CSV、JSONL 都是强格式数据。
|
||||
- 模型手写容易出错。
|
||||
- 脚本能统一 timestamp、prev_session、context、complex/target 组合。
|
||||
|
||||
## 8. 输出约束
|
||||
|
||||
固定逻辑输出:
|
||||
|
||||
```text
|
||||
output/records.jsonl
|
||||
output/records.csv
|
||||
output/training.jsonl
|
||||
output/eval_planning.csv
|
||||
```
|
||||
|
||||
通过路径路由,实际写入:
|
||||
|
||||
```text
|
||||
.port_sessions/accounts/<account>/sessions/<session>/output/
|
||||
```
|
||||
|
||||
Skill 明确禁止:
|
||||
|
||||
- 按数据集名创建随机子目录。
|
||||
- 把 records 写到项目根目录。
|
||||
- 用 `write_file` 手写最终 JSONL/CSV。
|
||||
- 在未确认 plan 前生成数据。
|
||||
|
||||
## 9. 设计边界
|
||||
|
||||
`product-data` 负责:
|
||||
|
||||
- 数据目标对齐。
|
||||
- 生成计划 review。
|
||||
- dataset draft 生成协议。
|
||||
- canonical records 转换和导出。
|
||||
|
||||
不负责:
|
||||
|
||||
- 判断所有标签知识。
|
||||
- 查询线上数据。
|
||||
- ELK 检索。
|
||||
- 模型训练或 git 数据仓库提交。
|
||||
|
||||
标签知识应由 `label-master` 辅助,线上数据由 `online-mining-v2` 或相关 Skill 获取。
|
||||
@@ -0,0 +1,177 @@
|
||||
# 07. online-mining-v2 Skill 实现
|
||||
|
||||

|
||||
|
||||
## 1. 定位
|
||||
|
||||
`online-mining-v2` 用于从线上 ELK 日志中挖掘 case,并把候选样本转换成 `product-data` 兼容的标准数据。
|
||||
|
||||
典型链路:
|
||||
|
||||
```text
|
||||
线上挖掘需求
|
||||
-> 明确目标标签、complex、筛选特征
|
||||
-> 探索 ELK 表字段
|
||||
-> 搜索候选
|
||||
-> 抽样 review
|
||||
-> 调整策略
|
||||
-> 直接转 canonical records
|
||||
-> 或转 product-data 生成补数
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/online-mining-v2/SKILL.md
|
||||
skills/online-mining-v2/knowledge/
|
||||
skills/online-mining-v2/scripts/
|
||||
```
|
||||
|
||||
## 2. 能力组织
|
||||
|
||||
`online-mining-v2` 不新增平台注册工具。它把能力放在 Skill 目录下,通过 `python_exec.script_path` 执行。
|
||||
|
||||
```text
|
||||
scripts/
|
||||
online_mining_common.py
|
||||
elk_profile_index.py
|
||||
elk_search_cases.py
|
||||
elk_fetch_by_request_ids.py
|
||||
elk_join_request_logs.py
|
||||
build_dataset_draft.py
|
||||
build_online_records.py
|
||||
```
|
||||
|
||||
这样做的原因:
|
||||
|
||||
- ELK 查询逻辑属于该 Skill 的业务能力。
|
||||
- 迁移到其他 Agent 时可以直接跑脚本。
|
||||
- 平台只需要提供通用 `python_exec` 和 `python_package`。
|
||||
|
||||
## 3. 默认数据源
|
||||
|
||||
当前重点支持两类索引:
|
||||
|
||||
```text
|
||||
pre-processing*
|
||||
前处理日志。
|
||||
适合拿模型 prompt、模型输出、候选 domain、excellent_domains_result。
|
||||
|
||||
arch-flat-nlp-log-f-*
|
||||
主 NLP 日志。
|
||||
适合拿 query、domain、func、request_id、device_id、device、tts/text/to_speak。
|
||||
```
|
||||
|
||||
Skill 文档中会引导 Agent:
|
||||
|
||||
- 先用 `elk_profile_index.py` 看字段和样本。
|
||||
- 再用 `elk_search_cases.py` 根据 query/domain/model output 搜索。
|
||||
- 必要时用 request_id 做双表 join。
|
||||
|
||||
## 4. 和 Agent Loop 的关系
|
||||
|
||||
这个 Skill 的交互不是“一次查询结束”,而是策略迭代:
|
||||
|
||||
```text
|
||||
用户描述需求
|
||||
-> Agent 整理目标标签和筛选特征
|
||||
-> 如缺目标标签或字段,先问用户
|
||||
-> python_exec 调 elk_profile_index.py
|
||||
-> 模型观察字段和样本
|
||||
-> python_exec 调 elk_search_cases.py
|
||||
-> 模型观察候选质量
|
||||
-> 抽样展示给用户 review
|
||||
-> 用户说哪里不对
|
||||
-> 调整 filters / regex / domain / model output 条件
|
||||
-> 再搜索
|
||||
```
|
||||
|
||||
Agent loop 的价值在这里很明显:每次工具结果都会进入上下文,模型可以基于真实候选调整策略。
|
||||
|
||||
## 5. 两条分支
|
||||
|
||||
`online-mining-v2` 最重要的设计是强制区分两个分支。
|
||||
|
||||
### 5.1 直接线上样本分支
|
||||
|
||||
适用:
|
||||
|
||||
```text
|
||||
用户想把线上筛出来的真实 case 作为评测集或专项集。
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
```text
|
||||
不生成新 query。
|
||||
不进入 product-data generation plan。
|
||||
用 build_online_records.py 直接转 canonical records。
|
||||
```
|
||||
|
||||
典型输出:
|
||||
|
||||
```text
|
||||
output/records.jsonl
|
||||
output/records.csv
|
||||
```
|
||||
|
||||
### 5.2 补充生成分支
|
||||
|
||||
适用:
|
||||
|
||||
```text
|
||||
用户想围绕线上问题扩写更多类似 case。
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
```text
|
||||
先分析线上 badcase。
|
||||
整理错误类型和覆盖目标。
|
||||
再转 product-data 的 generation goal / plan / draft / records 流程。
|
||||
```
|
||||
|
||||
这条边界避免了旧流程里出现的错误:用户只是想“把候选转样本”,Agent 却误走“生成新数据”。
|
||||
|
||||
## 6. 和 product-data 的关系
|
||||
|
||||
`online-mining-v2` 后处理复用 `product-data` 的标准:
|
||||
|
||||
```text
|
||||
canonical record v1
|
||||
records.jsonl
|
||||
records.csv
|
||||
training.jsonl
|
||||
eval_planning.csv
|
||||
```
|
||||
|
||||
复用脚本:
|
||||
|
||||
```text
|
||||
skills/product-data/scripts/normalize_dataset_draft.py
|
||||
skills/product-data/scripts/validate_dataset_records.py
|
||||
skills/product-data/scripts/export_dataset_records.py
|
||||
skills/product-data/scripts/export_training_jsonl.py
|
||||
skills/product-data/scripts/export_planning_eval_csv.py
|
||||
```
|
||||
|
||||
因此线上挖掘和产品定义生成最终可以进入同一套数据格式。
|
||||
|
||||
## 7. 设计边界
|
||||
|
||||
`online-mining-v2` 负责:
|
||||
|
||||
- ELK 字段探索。
|
||||
- ELK 条件搜索。
|
||||
- request_id 补全。
|
||||
- 候选样本 review。
|
||||
- 线上候选转 canonical records。
|
||||
|
||||
不负责:
|
||||
|
||||
- 定义 canonical record 标准。
|
||||
- 生成全新补数。
|
||||
- 判断复杂标签知识。
|
||||
- 训练数据提交。
|
||||
|
||||
这些分别交给 `product-data`、`label-master` 或后续数据仓库 Skill。
|
||||
@@ -0,0 +1,231 @@
|
||||
# 08. label-master Skill 实现
|
||||
|
||||

|
||||
|
||||
## 1. 定位
|
||||
|
||||
`label-master` 是标签知识和边界分析 Skill。
|
||||
|
||||
它不把“给 query 打标签”做成一个黑盒工具,而是让 Agent 逐步读取知识、比较候选、解释依据,并在必要时调用脚本校验最终输出格式。
|
||||
|
||||
典型链路:
|
||||
|
||||
```text
|
||||
query / 标签边界问题 / target 校验需求
|
||||
-> 读取决策流程
|
||||
-> 读取候选召回索引
|
||||
-> 找 2-5 个候选标签
|
||||
-> 读取候选标签卡片
|
||||
-> 命中混淆时读取边界卡
|
||||
-> 判断 complex / 多指令 / 自动任务等结构维度
|
||||
-> 判断输出形态
|
||||
-> 给出推荐、候选、依据、排除项和不确定点
|
||||
-> 如要落数据,调用 validate_label_output.py
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/label-master/SKILL.md
|
||||
skills/label-master/knowledge/
|
||||
skills/label-master/scripts/
|
||||
```
|
||||
|
||||
## 2. 知识组织
|
||||
|
||||
核心目录:
|
||||
|
||||
```text
|
||||
knowledge/
|
||||
标签总览.md
|
||||
决策流程.md
|
||||
索引/
|
||||
候选召回索引.md
|
||||
标签索引.md
|
||||
维度索引.md
|
||||
label_manifest.json
|
||||
判断维度/
|
||||
复杂度/
|
||||
多指令/
|
||||
自动任务/
|
||||
标注输出形态.md
|
||||
输出能力/
|
||||
标签/
|
||||
边界/
|
||||
边界索引.md
|
||||
高频混淆/
|
||||
领域概览/
|
||||
迁移记录.md
|
||||
```
|
||||
|
||||
设计原则:
|
||||
|
||||
```text
|
||||
索引先行
|
||||
不直接读全量标签卡。
|
||||
|
||||
维度分离
|
||||
complex、多指令、自动任务不是业务标签。
|
||||
|
||||
标签卡片中文维护
|
||||
方便人工编辑。
|
||||
|
||||
输出能力独立
|
||||
function、intent、object、Agent 包装放在输出能力层。
|
||||
|
||||
边界卡优先人工维护
|
||||
高频混淆写清楚,不只依赖自动迁移总结。
|
||||
```
|
||||
|
||||
## 3. Agent 使用方式
|
||||
|
||||
`label-master` 的关键在于利用 Agent 的多轮阅读和规划能力。
|
||||
|
||||
典型 Agent loop:
|
||||
|
||||
```text
|
||||
模型选择 label-master
|
||||
-> read_file knowledge/决策流程.md
|
||||
-> read_file knowledge/索引/候选召回索引.md
|
||||
-> read_file 候选标签卡片
|
||||
-> read_file 高频混淆边界卡
|
||||
-> 必要时 read_file 判断维度/复杂度/*
|
||||
-> 必要时 read_file 输出能力/*
|
||||
-> 输出可 review 判断
|
||||
```
|
||||
|
||||
为什么不做成单个 `classify_query(query) -> label` 工具:
|
||||
|
||||
- 标签判断常常需要比较候选和排除项。
|
||||
- function / intent / Agent 包装要看迁移状态。
|
||||
- complex、多指令、自动任务是独立维度。
|
||||
- 人类 review 需要看到依据,而不是只看到最终标签。
|
||||
|
||||
因此脚本只做索引和校验,不替代语义判断。
|
||||
|
||||
## 4. 输出形态
|
||||
|
||||
标签知识中存在多种输出形态:
|
||||
|
||||
```text
|
||||
Agent(tag="xxx")
|
||||
function program
|
||||
intent
|
||||
object + function 组合
|
||||
多指令 JSON
|
||||
自动任务 JSON
|
||||
complex=true/false
|
||||
```
|
||||
|
||||
`complex` 是独立维度,不应该混在 target 里。
|
||||
|
||||
例如训练输出可以组合成:
|
||||
|
||||
```text
|
||||
complex=false
|
||||
Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
但内部判断要拆成:
|
||||
|
||||
```text
|
||||
complex: false
|
||||
target: Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
## 5. 脚本能力
|
||||
|
||||
### 5.1 build_label_manifest.py
|
||||
|
||||
用途:
|
||||
|
||||
```text
|
||||
把 Markdown 知识生成机器索引。
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/label-master/scripts/build_label_manifest.py
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```text
|
||||
skills/label-master/knowledge/索引/label_manifest.json
|
||||
```
|
||||
|
||||
使用场景:
|
||||
|
||||
- 新增标签卡。
|
||||
- 修改输出能力。
|
||||
- 修改判断维度。
|
||||
- 修改边界知识。
|
||||
|
||||
### 5.2 validate_label_output.py
|
||||
|
||||
用途:
|
||||
|
||||
```text
|
||||
校验 target/function/intent/Agent/多指令/自动任务输出格式。
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/label-master/scripts/validate_label_output.py
|
||||
```
|
||||
|
||||
典型调用:
|
||||
|
||||
```text
|
||||
python_exec(script_path="skills/label-master/scripts/validate_label_output.py", args=["--target", "Agent(tag=\"地图导航\")"])
|
||||
```
|
||||
|
||||
批量校验:
|
||||
|
||||
```text
|
||||
--file output/records.jsonl --field target
|
||||
```
|
||||
|
||||
## 6. 和 product-data 的关系
|
||||
|
||||
`product-data` 在需要确认 target 或生成边界数据时,可以读取 `label-master` 的知识。
|
||||
|
||||
分工:
|
||||
|
||||
```text
|
||||
label-master
|
||||
判断标签知识、边界、输出形态、target 合法性。
|
||||
|
||||
product-data
|
||||
做数据生成 review、draft、canonical records、导出。
|
||||
```
|
||||
|
||||
当用户给出模糊标签名时,应该先用 `label-master` 辅助确认:
|
||||
|
||||
```text
|
||||
标签是否存在
|
||||
使用 Agent 包装还是 function
|
||||
complex 默认是什么
|
||||
是否有高频混淆边界
|
||||
```
|
||||
|
||||
再进入 `product-data` 的生成计划。
|
||||
|
||||
## 7. 设计边界
|
||||
|
||||
`label-master` 负责:
|
||||
|
||||
- 标签知识检索。
|
||||
- 候选标签比较。
|
||||
- 边界解释。
|
||||
- 输出形态判断。
|
||||
- target 格式校验。
|
||||
|
||||
不负责:
|
||||
|
||||
- 生成数据集。
|
||||
- 线上日志挖掘。
|
||||
- 导出训练/评测格式。
|
||||
- 直接替用户确认争议边界。
|
||||
@@ -0,0 +1,186 @@
|
||||
# 09. 外部系统 Skill:ELK、SQL、模型迭代
|
||||
|
||||

|
||||
|
||||
## 1. 这类 Skill 的共同特征
|
||||
|
||||
外部系统 Skill 的核心不是复杂 prompt,而是把某个内部系统的调用方式、参数约定、依赖和输出格式打包起来。
|
||||
|
||||
典型形态:
|
||||
|
||||
```text
|
||||
SKILL.md
|
||||
写清楚什么时候用、参数怎么选、哪些动作需要确认。
|
||||
|
||||
scripts/
|
||||
执行真实外部系统调用。
|
||||
|
||||
python_exec
|
||||
作为统一执行入口。
|
||||
|
||||
python_package
|
||||
处理依赖缺失。
|
||||
|
||||
output/
|
||||
查询结果或报表写入当前 session output。
|
||||
```
|
||||
|
||||
## 2. elk-fetch
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/elk-fetch/SKILL.md
|
||||
skills/elk-fetch/elk_query.py
|
||||
```
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
按 request id 或查询条件读取小米内网 ELK 日志。
|
||||
```
|
||||
|
||||
实现方式:
|
||||
|
||||
- `SKILL.md` 写明支持的 profile 和查询方式。
|
||||
- 实际执行必须使用 `python_exec.script_path`。
|
||||
- 缺 `elasticsearch`、`urllib3` 时用 `python_package`。
|
||||
- 不让模型用 bash 手写临时 Python 查询脚本。
|
||||
|
||||
典型价值:
|
||||
|
||||
```text
|
||||
把“怎么查 ELK”这类个人经验变成团队共享能力。
|
||||
```
|
||||
|
||||
和 `online-mining-v2` 的区别:
|
||||
|
||||
```text
|
||||
elk-fetch
|
||||
更偏单次日志查询和调试。
|
||||
|
||||
online-mining-v2
|
||||
更偏批量挖掘、策略迭代、样本转换。
|
||||
```
|
||||
|
||||
## 3. data-factory-sql
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/data-factory-sql/SKILL.md
|
||||
skills/data-factory-sql/run_sql.py
|
||||
```
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
通过 Kyuubi HTTP API 执行数据工场 SQL,轮询状态并下载 CSV 结果。
|
||||
```
|
||||
|
||||
实现方式:
|
||||
|
||||
- 用户直接给 SQL 时,可以确认后执行。
|
||||
- 用户只给分析需求时,Agent 可以先生成 SQL 草稿,再让用户确认。
|
||||
- 执行必须通过 `python_exec.script_path`。
|
||||
- 输出默认写入当前 session output。
|
||||
|
||||
这个 Skill 的门禁重点是:
|
||||
|
||||
```text
|
||||
SQL 执行前确认。
|
||||
控制查询范围和 limit。
|
||||
输出路径清晰。
|
||||
失败时展示错误和可调整建议。
|
||||
```
|
||||
|
||||
## 4. model-iteration
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/model-iteration/SKILL.md
|
||||
skills/model-iteration/scripts/
|
||||
```
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
模型训练、评估、数据准备和迭代流程辅助。
|
||||
```
|
||||
|
||||
这类 Skill 属于混合工程型:
|
||||
|
||||
- 有流程。
|
||||
- 有脚本。
|
||||
- 有配置。
|
||||
- 可能调用外部训练平台。
|
||||
- 高风险动作较多。
|
||||
|
||||
因此它更需要明确:
|
||||
|
||||
```text
|
||||
哪些步骤只是分析。
|
||||
哪些步骤会启动训练。
|
||||
哪些步骤需要用户确认。
|
||||
输出目录和实验记录在哪里。
|
||||
```
|
||||
|
||||
## 5. 外部 Skill 的通用约定
|
||||
|
||||
### 5.1 调用方式
|
||||
|
||||
优先:
|
||||
|
||||
```json
|
||||
{
|
||||
"script_path": "skills/<skill-name>/scripts/run.py",
|
||||
"stdin": "{...}",
|
||||
"timeout_seconds": 120
|
||||
}
|
||||
```
|
||||
|
||||
或者脚本在 Skill 根目录时:
|
||||
|
||||
```json
|
||||
{
|
||||
"script_path": "skills/elk-fetch/elk_query.py",
|
||||
"args": ["..."]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 依赖处理
|
||||
|
||||
依赖缺失时:
|
||||
|
||||
```text
|
||||
python_package(action="install", packages=[...])
|
||||
```
|
||||
|
||||
不要:
|
||||
|
||||
```text
|
||||
bash pip install ...
|
||||
bash python ...
|
||||
```
|
||||
|
||||
### 5.3 输出处理
|
||||
|
||||
外部系统查询结果应该:
|
||||
|
||||
- stdout 给结构化摘要。
|
||||
- 大结果写入 `output/` 或 `scratchpad/`。
|
||||
- 返回实际文件路径。
|
||||
- 避免把大量数据直接塞进最终回复。
|
||||
|
||||
### 5.4 安全和确认
|
||||
|
||||
需要确认的动作:
|
||||
|
||||
- 执行大范围 SQL。
|
||||
- 启动训练。
|
||||
- 写外部路径。
|
||||
- 推送代码或数据仓库。
|
||||
- 导出可能包含敏感字段的线上数据。
|
||||
|
||||
只读、小范围、用户明确指定 rid 或 SQL 的查询,可以直接执行,但仍要控制结果规模。
|
||||
@@ -0,0 +1,59 @@
|
||||
# ZK Data Agent 技术架构说明
|
||||
|
||||
这组文档先服务于技术 review 和后续讲解材料沉淀,不做宣传表达,不做产品比较。
|
||||
每个章节尽量对应当前代码里的真实模块、函数和 Skill 实现,后续 `/doc` 页面可以从这里抽取内容做视觉化呈现。
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
1. [基座 Runtime 架构](01-base-runtime.md)
|
||||
2. [Agent Loop 执行机制](02-agent-loop.md)
|
||||
3. [工具体系和 Tool Handler](03-tools.md)
|
||||
4. [Skill 体系和能力包约定](04-skills.md)
|
||||
5. [会话工作区、运行态和记忆](05-workspace-memory-observability.md)
|
||||
6. [product-data Skill 实现](06-product-data.md)
|
||||
7. [online-mining-v2 Skill 实现](07-online-mining-v2.md)
|
||||
8. [label-master Skill 实现](08-label-master.md)
|
||||
9. [外部系统 Skill:ELK、SQL、模型迭代](09-external-skills.md)
|
||||
|
||||
## 一句话定位
|
||||
|
||||
ZK Data Agent 的基座是一个 Web 化、多用户、可观测的 Agent runtime。
|
||||
业务能力通过 `skills/<skill-name>/SKILL.md`、`knowledge/` 和 `scripts/` 组织;稳定执行能力通过 Tool handler 或 Skill 内 portable scripts 承载;每次运行通过 Agent loop 让模型在“判断、调用工具、观察结果、继续判断”之间循环。
|
||||
|
||||
## 当前技术主线
|
||||
|
||||
```text
|
||||
Web UI
|
||||
-> backend/api/server.py
|
||||
-> LocalCodingAgent
|
||||
-> agent_prompting 组装系统提示词和 Skill 列表
|
||||
-> OpenAICompatClient 调模型
|
||||
-> 模型返回文本或 tool_calls
|
||||
-> agent_tools 执行 handler
|
||||
-> session workspace 保存 input/scratchpad/output/session.json
|
||||
-> run_state_store / event stream 推给前端活动区
|
||||
-> personal_memory 后台异步整理用户记忆和 Skill 记忆
|
||||
```
|
||||
|
||||
## 代码入口速查
|
||||
|
||||
| 主题 | 主要代码 |
|
||||
|------|----------|
|
||||
| Agent runtime | `src/agent_runtime.py` |
|
||||
| 系统提示词 | `src/agent_prompting.py` |
|
||||
| Tool registry / handler | `src/agent_tools.py`、`src/agent_tool_specs/` |
|
||||
| Skill loader | `src/bundled_skills.py` |
|
||||
| 模型兼容层 | `src/openai_compat.py` |
|
||||
| Web 后端 | `backend/api/server.py` |
|
||||
| 会话持久化 | `src/agent_session.py`、`src/session_store.py` |
|
||||
| 记忆后台 | `src/personal_memory.py` |
|
||||
| 数据生成 Skill | `skills/product-data/` |
|
||||
| 线上挖掘 Skill | `skills/online-mining-v2/` |
|
||||
| 标签知识 Skill | `skills/label-master/` |
|
||||
|
||||
## 后续维护原则
|
||||
|
||||
- 如果是在讲“基座怎么运行”,优先改 01-05。
|
||||
- 如果是在讲“某个 Skill 怎么做事”,优先改 06-09。
|
||||
- 如果代码实现发生变化,先更新对应章节,再考虑同步 README 或 `/doc` 页面。
|
||||
- 页面表达、图形素材、演示脚本不写在这里;这里只保留技术事实和设计边界。
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,368 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import { notFound } from "next/navigation";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
const technicalDocs = {
|
||||
"01-base-runtime": {
|
||||
title: "基座 Runtime",
|
||||
file: "01-base-runtime.md",
|
||||
image: "/doc-assets/technical/01-base-runtime.png",
|
||||
},
|
||||
"02-agent-loop": {
|
||||
title: "Agent Loop",
|
||||
file: "02-agent-loop.md",
|
||||
image: "/doc-assets/technical/02-agent-loop.png",
|
||||
},
|
||||
"03-tools": {
|
||||
title: "工具体系",
|
||||
file: "03-tools.md",
|
||||
image: "/doc-assets/technical/03-tools.png",
|
||||
},
|
||||
"04-skills": {
|
||||
title: "Skill 体系",
|
||||
file: "04-skills.md",
|
||||
image: "/doc-assets/technical/04-skills.png",
|
||||
},
|
||||
"05-workspace-memory-observability": {
|
||||
title: "工作区、运行态和记忆",
|
||||
file: "05-workspace-memory-observability.md",
|
||||
image: "/doc-assets/technical/05-workspace-memory-observability.png",
|
||||
},
|
||||
"06-product-data": {
|
||||
title: "product-data",
|
||||
file: "06-product-data.md",
|
||||
image: "/doc-assets/technical/06-product-data.png",
|
||||
},
|
||||
"07-online-mining-v2": {
|
||||
title: "online-mining-v2",
|
||||
file: "07-online-mining-v2.md",
|
||||
image: "/doc-assets/technical/07-online-mining-v2.png",
|
||||
},
|
||||
"08-label-master": {
|
||||
title: "标签大师",
|
||||
file: "08-label-master.md",
|
||||
image: "/doc-assets/technical/08-label-master.png",
|
||||
},
|
||||
"09-external-skills": {
|
||||
title: "外部系统 Skill",
|
||||
file: "09-external-skills.md",
|
||||
image: "/doc-assets/technical/09-external-skills.png",
|
||||
},
|
||||
} as const;
|
||||
|
||||
type TechnicalDocSlug = keyof typeof technicalDocs;
|
||||
|
||||
function getDoc(slug: string) {
|
||||
if (slug in technicalDocs) {
|
||||
return technicalDocs[slug as TechnicalDocSlug];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function markdownPath(file: string) {
|
||||
return path.join(
|
||||
process.cwd(),
|
||||
"public",
|
||||
"doc-assets",
|
||||
"technical-docs",
|
||||
file,
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeMarkdown(markdown: string) {
|
||||
return markdown
|
||||
.replace(/^\s*#\s+.+\n+/, "")
|
||||
.replaceAll("](assets/", "](/doc-assets/technical/");
|
||||
}
|
||||
|
||||
function parseInline(text: string): ReactNode[] {
|
||||
const nodes: ReactNode[] = [];
|
||||
const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\[([^\]]+)\]\(([^)]+)\))/g;
|
||||
let cursor = 0;
|
||||
|
||||
while (true) {
|
||||
const match = pattern.exec(text);
|
||||
if (match === null) break;
|
||||
if (match.index > cursor) {
|
||||
nodes.push(text.slice(cursor, match.index));
|
||||
}
|
||||
|
||||
const token = match[0];
|
||||
const key = `${match.index}-${token}`;
|
||||
if (token.startsWith("`")) {
|
||||
nodes.push(<code key={key}>{token.slice(1, -1)}</code>);
|
||||
} else if (token.startsWith("**")) {
|
||||
nodes.push(<strong key={key}>{token.slice(2, -2)}</strong>);
|
||||
} else {
|
||||
const href = match[3] ?? "";
|
||||
const isExternal = /^https?:\/\//.test(href);
|
||||
nodes.push(
|
||||
<a
|
||||
href={href}
|
||||
key={key}
|
||||
rel={isExternal ? "noopener" : undefined}
|
||||
target={isExternal ? "_blank" : undefined}
|
||||
>
|
||||
{match[2]}
|
||||
</a>,
|
||||
);
|
||||
}
|
||||
|
||||
cursor = pattern.lastIndex;
|
||||
}
|
||||
|
||||
if (cursor < text.length) {
|
||||
nodes.push(text.slice(cursor));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function splitTableRow(line: string) {
|
||||
return line
|
||||
.trim()
|
||||
.replace(/^\|/, "")
|
||||
.replace(/\|$/, "")
|
||||
.split("|")
|
||||
.map((cell) => cell.trim());
|
||||
}
|
||||
|
||||
function isTableDivider(line: string) {
|
||||
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
|
||||
}
|
||||
|
||||
function startsBlock(line: string, nextLine?: string) {
|
||||
return (
|
||||
line.startsWith("```") ||
|
||||
/^#{1,6}\s+/.test(line) ||
|
||||
/^[-*]\s+/.test(line) ||
|
||||
/^\d+\.\s+/.test(line) ||
|
||||
/^>\s?/.test(line) ||
|
||||
/^!\[[^\]]*]\([^)]+\)\s*$/.test(line) ||
|
||||
(line.includes("|") && nextLine !== undefined && isTableDivider(nextLine))
|
||||
);
|
||||
}
|
||||
|
||||
function renderHeading(depth: number, text: string, key: string) {
|
||||
if (depth === 1) return <h1 key={key}>{parseInline(text)}</h1>;
|
||||
if (depth === 2) return <h2 key={key}>{parseInline(text)}</h2>;
|
||||
if (depth === 3) return <h3 key={key}>{parseInline(text)}</h3>;
|
||||
if (depth === 4) return <h4 key={key}>{parseInline(text)}</h4>;
|
||||
if (depth === 5) return <h5 key={key}>{parseInline(text)}</h5>;
|
||||
return <h6 key={key}>{parseInline(text)}</h6>;
|
||||
}
|
||||
|
||||
function renderMarkdown(markdown: string) {
|
||||
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
||||
const blocks: ReactNode[] = [];
|
||||
let index = 0;
|
||||
|
||||
while (index < lines.length) {
|
||||
const line = lines[index];
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("```")) {
|
||||
const language = trimmed.slice(3).trim() || "text";
|
||||
const codeLines: string[] = [];
|
||||
index += 1;
|
||||
while (index < lines.length && !lines[index].trim().startsWith("```")) {
|
||||
codeLines.push(lines[index]);
|
||||
index += 1;
|
||||
}
|
||||
if (index < lines.length) index += 1;
|
||||
blocks.push(
|
||||
<div className="project-doc-code-block" key={`code-${index}`}>
|
||||
<div>{language}</div>
|
||||
<pre>
|
||||
<code>{codeLines.join("\n")}</code>
|
||||
</pre>
|
||||
</div>,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const heading = trimmed.match(/^(#{1,6})\s+(.*)$/);
|
||||
if (heading) {
|
||||
blocks.push(
|
||||
renderHeading(heading[1].length, heading[2], `heading-${index}`),
|
||||
);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const image = trimmed.match(/^!\[([^\]]*)]\(([^)]+)\)\s*$/);
|
||||
if (image) {
|
||||
blocks.push(
|
||||
<Image
|
||||
alt={image[1]}
|
||||
height={900}
|
||||
key={`image-${index}`}
|
||||
src={image[2]}
|
||||
width={1600}
|
||||
/>,
|
||||
);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
trimmed.includes("|") &&
|
||||
index + 1 < lines.length &&
|
||||
isTableDivider(lines[index + 1])
|
||||
) {
|
||||
const headers = splitTableRow(trimmed);
|
||||
index += 2;
|
||||
const rows: string[][] = [];
|
||||
while (index < lines.length && lines[index].trim().includes("|")) {
|
||||
rows.push(splitTableRow(lines[index]));
|
||||
index += 1;
|
||||
}
|
||||
blocks.push(
|
||||
<table key={`table-${index}`}>
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((cell) => (
|
||||
<th key={cell}>{parseInline(cell)}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.join("|")}>
|
||||
{row.map((cell) => (
|
||||
<td key={`${row.join("|")}-${cell}`}>{parseInline(cell)}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/^[-*]\s+/.test(trimmed)) {
|
||||
const items: string[] = [];
|
||||
while (index < lines.length && /^[-*]\s+/.test(lines[index].trim())) {
|
||||
items.push(lines[index].trim().replace(/^[-*]\s+/, ""));
|
||||
index += 1;
|
||||
}
|
||||
blocks.push(
|
||||
<ul key={`ul-${index}`}>
|
||||
{items.map((item) => (
|
||||
<li key={item}>{parseInline(item)}</li>
|
||||
))}
|
||||
</ul>,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/^\d+\.\s+/.test(trimmed)) {
|
||||
const items: string[] = [];
|
||||
while (index < lines.length && /^\d+\.\s+/.test(lines[index].trim())) {
|
||||
items.push(lines[index].trim().replace(/^\d+\.\s+/, ""));
|
||||
index += 1;
|
||||
}
|
||||
blocks.push(
|
||||
<ol key={`ol-${index}`}>
|
||||
{items.map((item) => (
|
||||
<li key={item}>{parseInline(item)}</li>
|
||||
))}
|
||||
</ol>,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/^>\s?/.test(trimmed)) {
|
||||
const quoteLines: string[] = [];
|
||||
while (index < lines.length && /^>\s?/.test(lines[index].trim())) {
|
||||
quoteLines.push(lines[index].trim().replace(/^>\s?/, ""));
|
||||
index += 1;
|
||||
}
|
||||
blocks.push(
|
||||
<blockquote key={`quote-${index}`}>
|
||||
{parseInline(quoteLines.join(" "))}
|
||||
</blockquote>,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const paragraphLines = [trimmed];
|
||||
index += 1;
|
||||
while (
|
||||
index < lines.length &&
|
||||
lines[index].trim() &&
|
||||
!startsBlock(lines[index].trim(), lines[index + 1]?.trim())
|
||||
) {
|
||||
paragraphLines.push(lines[index].trim());
|
||||
index += 1;
|
||||
}
|
||||
blocks.push(
|
||||
<p key={`p-${index}`}>{parseInline(paragraphLines.join(" "))}</p>,
|
||||
);
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return Object.keys(technicalDocs).map((slug) => ({ slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const doc = getDoc(slug);
|
||||
if (!doc) return {};
|
||||
return {
|
||||
title: `${doc.title} - ZK Data Agent`,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function TechnicalDocPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const doc = getDoc(slug);
|
||||
if (!doc) notFound();
|
||||
|
||||
const rawMarkdown = await readFile(markdownPath(doc.file), "utf8");
|
||||
const markdown = normalizeMarkdown(rawMarkdown);
|
||||
|
||||
return (
|
||||
<main className="project-doc-page project-doc-detail-page">
|
||||
<header className="project-doc-detail-hero">
|
||||
<nav className="project-doc-nav">
|
||||
<a href="/doc">ZK Data Agent</a>
|
||||
<div>
|
||||
<a href="/doc">返回总览</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="project-doc-detail-head">
|
||||
<p className="project-doc-kicker">技术文档</p>
|
||||
<h1>{doc.title}</h1>
|
||||
<a className="project-doc-back-link" href="/doc">
|
||||
返回架构总览
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="project-doc-detail-shell">
|
||||
<article className="project-doc-article">
|
||||
{renderMarkdown(markdown)}
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,652 +1,386 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import { ToolPositionMap } from "./tool-position-map";
|
||||
|
||||
import {
|
||||
ArrowRight,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
CircleDashed,
|
||||
FolderTree,
|
||||
Network,
|
||||
Route,
|
||||
Sparkles,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
type RoleId = "user" | "builder" | "admin";
|
||||
type ProductId = "chatgpt" | "cursor" | "claude" | "zk";
|
||||
type MemoryId = "chatgpt" | "letta" | "mem0" | "rag" | "ours";
|
||||
type SkillPartId = "skillmd" | "knowledge" | "scripts" | "examples" | "readme" | "update";
|
||||
|
||||
const roles: Record<
|
||||
RoleId,
|
||||
{
|
||||
name: string;
|
||||
type Section = {
|
||||
id: string;
|
||||
no: string;
|
||||
title: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
workflow: string[];
|
||||
proof: string[];
|
||||
}
|
||||
> = {
|
||||
user: {
|
||||
name: "普通使用者",
|
||||
title: "我想让它替我完成一段工作",
|
||||
question: "我为什么不用 ChatGPT 网页版问一问?",
|
||||
answer:
|
||||
"因为这里不是只回答一句话,而是把输入、计划、人工 review、脚本执行、文件产物和后续导出放进同一个任务空间。",
|
||||
workflow: ["选择 Skill", "描述目标", "review 计划", "等待执行", "拿 output 文件"],
|
||||
proof: ["会话文件面板", "右侧活动链路", "output/ 产物", "飞书在线文档转换"],
|
||||
},
|
||||
builder: {
|
||||
name: "Skill 作者",
|
||||
title: "我想把自己的经验变成团队能力",
|
||||
question: "为什么不是每个人自己写脚本、自己维护 prompt?",
|
||||
answer:
|
||||
"Skill 把流程、知识、脚本、样例和维护说明放到一个目录。别人不用理解你的全部细节,也能在 Web 上调用这份能力。",
|
||||
workflow: ["建 skill 目录", "写 SKILL.md", "沉淀 knowledge", "放 scripts", "提交 Git", "页面更新 Skill"],
|
||||
proof: ["skills/product-data", "skills/online-mining-v2", "skills/label-master", "Skill 更新按钮"],
|
||||
},
|
||||
admin: {
|
||||
name: "平台维护者",
|
||||
title: "我想让多人稳定使用,而不是每个人本地各跑各的",
|
||||
question: "为什么不用 Claude Code 本地跑?",
|
||||
answer:
|
||||
"Claude Code 很适合个人本地工程闭环;这里补的是团队 Web 入口、多用户会话、账号记忆、管理后台、共享 Skill 和统一部署。",
|
||||
workflow: ["管理用户", "看会话与用量", "处理记忆队列", "更新服务", "维护公共 Skill"],
|
||||
proof: ["/admin", "memory.db", "systemd 服务", "dev / online 环境", "会话隔离"],
|
||||
},
|
||||
summary: string;
|
||||
image?: string;
|
||||
doc?: string;
|
||||
points: string[];
|
||||
code: string[];
|
||||
decision: string;
|
||||
};
|
||||
|
||||
const products: Record<
|
||||
ProductId,
|
||||
{
|
||||
name: string;
|
||||
position: string;
|
||||
bestFor: string;
|
||||
limits: string;
|
||||
tech: string[];
|
||||
}
|
||||
> = {
|
||||
chatgpt: {
|
||||
name: "ChatGPT 网页版",
|
||||
position: "最好的通用对话入口",
|
||||
bestFor: "问答、写作、临时分析、个人信息辅助。",
|
||||
limits: "不天然绑定团队数据源、产物目录、Skill 更新和多用户运维。",
|
||||
tech: ["Saved Memory", "Reference Chat History", "Files", "Connectors"],
|
||||
},
|
||||
cursor: {
|
||||
name: "Cursor",
|
||||
position: "IDE 内的代码协作体验",
|
||||
bestFor: "围绕代码仓库改文件、补全、解释、局部重构。",
|
||||
limits: "偏个人 IDE 场景,业务流程共享、产物管理和多人 Web 入口不是核心。",
|
||||
tech: ["Codebase Index", "Composer", "IDE Context", "Rules"],
|
||||
},
|
||||
claude: {
|
||||
name: "Claude Code",
|
||||
position: "本地工程 Agent 的强基准",
|
||||
bestFor: "读代码、改文件、跑测试、长任务、多工具工程闭环。",
|
||||
limits: "团队业务能力共建、账号级 Skill 记忆、Web 管理和数据开发产物链需要另搭平台。",
|
||||
tech: ["Agent Loop", "Tools", "Skills", "MCP", "Terminal Workspace"],
|
||||
},
|
||||
zk: {
|
||||
name: "ZK Data Agent",
|
||||
position: "团队业务流程 Agent 工作台",
|
||||
bestFor: "把数据开发、标签判断、线上挖掘、格式导出沉淀成共享 Skill。",
|
||||
limits: "不追求替代所有 IDE/终端 Agent,优先服务团队业务流程沉淀。",
|
||||
tech: ["Skill Registry", "Review Gate", "Session Workspace", "Skill Memory", "Admin"],
|
||||
},
|
||||
};
|
||||
const assetPrefix = "/doc-assets/technical";
|
||||
const architectureImage = `${assetPrefix}/00-zk-data-agent-architecture.png`;
|
||||
|
||||
const comparisonRows = [
|
||||
{ label: "Web 多用户入口", chatgpt: "partial", cursor: "no", claude: "no", zk: "yes" },
|
||||
{ label: "团队共享 Skill", chatgpt: "partial", cursor: "partial", claude: "partial", zk: "yes" },
|
||||
{ label: "业务数据源接入", chatgpt: "partial", cursor: "no", claude: "partial", zk: "yes" },
|
||||
{ label: "会话产物管理", chatgpt: "partial", cursor: "no", claude: "partial", zk: "yes" },
|
||||
{ label: "人类 Review Gate", chatgpt: "partial", cursor: "partial", claude: "partial", zk: "yes" },
|
||||
{ label: "Skill 级记忆", chatgpt: "no", cursor: "no", claude: "partial", zk: "yes" },
|
||||
{ label: "管理后台/用量/队列", chatgpt: "no", cursor: "no", claude: "no", zk: "yes" },
|
||||
{ label: "数据开发链路沉淀", chatgpt: "partial", cursor: "no", claude: "partial", zk: "yes" },
|
||||
] as const;
|
||||
|
||||
const objectives = [
|
||||
const sections: Section[] = [
|
||||
{
|
||||
goal: "先回答为什么用我们",
|
||||
frontend: "用产品对比矩阵 + 选中产品详情,明确展示我们补的是团队流程平台能力。",
|
||||
message: "不是模型比 Claude Code 强,而是把 Web 多用户、共享 Skill、产物管理、记忆和管理后台补齐。",
|
||||
id: "base",
|
||||
no: "01",
|
||||
title: "基座 Runtime",
|
||||
summary:
|
||||
"基座把本地工程 Agent 的循环执行能力,包装成多人可使用、可观测、可沉淀、可扩展的 Web 工作台。",
|
||||
image: `${assetPrefix}/01-base-runtime.png`,
|
||||
doc: "/doc/01-base-runtime",
|
||||
points: [
|
||||
"基于 LocalCodingAgent、OpenAI-compatible 模型调用、Tool Runtime、Session Workspace 和 Skill System 组合而成。",
|
||||
"解决个人脚本和一次性对话难以团队复用的问题:流程、工具、知识、产物都挂到同一个会话空间。",
|
||||
"在 product-data、online-mining-v2、标签大师、飞书在线文档转换等场景中验证了基座的通用承载能力。",
|
||||
],
|
||||
code: [
|
||||
"backend/api/server.py",
|
||||
"src/agent_runtime.py",
|
||||
"src/agent_prompting.py",
|
||||
"src/openai_compat.py",
|
||||
],
|
||||
decision:
|
||||
"基座只承载跨业务稳定能力;变化快的业务判断、流程经验和格式转换下沉到 Skill。",
|
||||
},
|
||||
{
|
||||
goal: "让不同同事知道怎么融入工作",
|
||||
frontend: "用角色切换:普通使用者、Skill 作者、平台维护者分别看到自己的路径。",
|
||||
message: "看完不是只知道概念,而是知道自己明天怎么开始用。",
|
||||
id: "loop",
|
||||
no: "02",
|
||||
title: "Agent Loop",
|
||||
summary:
|
||||
"模型不是只回答一次,而是在多轮循环里阅读上下文、请求工具、观察结果、继续判断。",
|
||||
image: `${assetPrefix}/02-agent-loop.png`,
|
||||
doc: "/doc/02-agent-loop",
|
||||
points: [
|
||||
"模型是否返回 tool_calls 由模型基于 messages、工具描述、Skill 和上下文自行决定。",
|
||||
"工具结果写回 session,成为下一轮模型判断的输入。",
|
||||
"review gate 不是特殊流程,而是一次运行自然暂停,等待用户下一轮确认后 resume。",
|
||||
],
|
||||
code: [
|
||||
"LocalCodingAgent.run",
|
||||
"LocalCodingAgent.resume",
|
||||
"_run_prompt",
|
||||
"_query_model",
|
||||
],
|
||||
decision:
|
||||
"复杂任务需要执行后再判断;把工具观察结果纳入下一轮上下文,比一次性输出更可靠。",
|
||||
},
|
||||
{
|
||||
goal: "证明不是闭门造车",
|
||||
frontend: "用记忆方案谱系,把 ChatGPT、Letta、Mem0、RAG 和我们的取舍放在同一屏。",
|
||||
message: "我们参考了主流方案,但按团队 Skill 场景做了轻量可控实现。",
|
||||
id: "tools",
|
||||
no: "03",
|
||||
title: "工具体系",
|
||||
summary:
|
||||
"工具由 Tool spec 和 handler 两部分组成:spec 给模型选择,handler 在后端执行。",
|
||||
image: `${assetPrefix}/03-tools.png`,
|
||||
doc: "/doc/03-tools",
|
||||
points: [
|
||||
"Tool registry 汇总文件、执行、搜索、Skill、data_agent、MCP 等能力。",
|
||||
"模型只看到 name、description 和参数 schema,看不到 handler 实现。",
|
||||
"结构化分析和复杂文件写入优先走 python_exec;write_file 只适合小文件。",
|
||||
],
|
||||
code: [
|
||||
"src/agent_tools.py",
|
||||
"src/agent_tool_specs/files.py",
|
||||
"src/agent_tool_specs/execution.py",
|
||||
],
|
||||
decision:
|
||||
"模型负责选择能力,代码负责稳定执行能力;两者分开后才能做权限、取消、路径和审计。",
|
||||
},
|
||||
{
|
||||
goal: "解释 Skill 为什么能沉淀能力",
|
||||
frontend: "用可点击文件树解剖 SKILL.md、knowledge、scripts、examples、README 和更新机制。",
|
||||
message: "Skill 不是 prompt,而是可维护、可安装、可更新的能力包。",
|
||||
id: "skills",
|
||||
no: "04",
|
||||
title: "Skill 体系",
|
||||
summary:
|
||||
"Skill 是能力包,包含流程协议、领域知识、确定性脚本、样例和维护说明。",
|
||||
image: `${assetPrefix}/04-skills.png`,
|
||||
doc: "/doc/04-skills",
|
||||
points: [
|
||||
"SKILL.md 定义触发场景、执行流程、review 边界和产物约定。",
|
||||
"knowledge/ 放业务定义和边界经验,scripts/ 放稳定脚本。",
|
||||
"项目级 skills/ 可以通过页面更新机制刷新到模型可见能力列表。",
|
||||
],
|
||||
code: [
|
||||
"src/bundled_skills.py",
|
||||
"skills/*/SKILL.md",
|
||||
"frontend Skills 面板",
|
||||
],
|
||||
decision:
|
||||
"团队经验只有形成可安装、可更新、可阅读、可执行的目录结构,才方便多人复用和维护。",
|
||||
},
|
||||
{
|
||||
goal: "让技术同事能追代码",
|
||||
frontend: "每条链路都给出代码路径、落盘位置和真实运行节点。",
|
||||
message: "页面不是海报,是技术讲解和后续维护入口。",
|
||||
id: "workspace",
|
||||
no: "05",
|
||||
title: "工作区、运行态和记忆",
|
||||
summary:
|
||||
"每个账号和会话都有独立空间,运行过程通过事件流展示,交互结束后异步整理记忆。",
|
||||
image: `${assetPrefix}/05-workspace-memory-observability.png`,
|
||||
doc: "/doc/05-workspace-memory-observability",
|
||||
points: [
|
||||
"session/input 放输入材料,session/scratchpad 放中间过程,session/output 放最终产物。",
|
||||
"右侧活动区和摘要行来自运行态事件流,刷新后通过会话记录恢复。",
|
||||
"用户记忆和 Skill 记忆分开保存,只在需要的上下文中注入。",
|
||||
],
|
||||
code: [
|
||||
".port_sessions/accounts/{account}/sessions/{session}",
|
||||
"RunStateStore",
|
||||
"src/personal_memory.py",
|
||||
],
|
||||
decision: "多人使用时,隔离、恢复、取消、观测和产物管理比单次回答更重要。",
|
||||
},
|
||||
];
|
||||
|
||||
const memoryPatterns: Record<
|
||||
MemoryId,
|
||||
const skillSections: Section[] = [
|
||||
{
|
||||
name: string;
|
||||
source: string;
|
||||
borrowed: string;
|
||||
notEnough: string;
|
||||
ourChoice: string;
|
||||
docs: string;
|
||||
href: string;
|
||||
}
|
||||
> = {
|
||||
chatgpt: {
|
||||
name: "ChatGPT Saved Memory / Reference Chat History",
|
||||
source: "OpenAI 的记忆分成显式 Saved Memories 和从历史中提炼的 Reference Chat History。",
|
||||
borrowed: "借鉴:用户可管理、可删除、可开关;显式记忆和历史参考分层。",
|
||||
notEnough: "不足:它面向通用个性化,不知道某个团队 Skill 的操作经验应该只在该 Skill 生效。",
|
||||
ourChoice: "我们的取舍:用户记忆全局注入,Skill 使用记忆只在启用对应 Skill 时注入。",
|
||||
docs: "OpenAI Memory FAQ",
|
||||
href: "https://help.openai.com/en/articles/8590148-memory-faq",
|
||||
},
|
||||
letta: {
|
||||
name: "Letta / MemGPT Core + Archival Memory",
|
||||
source: "Letta 把 stateful agent 的消息、工具调用、记忆持久化,并区分 in-context memory blocks 和 archival memory。",
|
||||
borrowed: "借鉴:核心记忆常驻上下文,外部记忆通过工具或检索进入;记忆是 Agent state 的一部分。",
|
||||
notEnough: "不足:完整自管理记忆体系复杂,早期直接照搬会增加主链路风险和运维成本。",
|
||||
ourChoice: "我们的取舍:不让主 Agent 实时自改记忆,而是把交互事件异步交给后台 worker 整理。",
|
||||
docs: "Letta Stateful Agents / Archival Memory",
|
||||
href: "https://docs.letta.com/guides/core-concepts/stateful-agents",
|
||||
},
|
||||
mem0: {
|
||||
name: "Mem0 / Managed Memory Layer",
|
||||
source: "Mem0 把用户、Agent、Session 记忆做成可托管的长期记忆层,并支持 add/search/update/delete。",
|
||||
borrowed: "借鉴:每次交互后抽取记忆;调用前按用户请求检索并注入。",
|
||||
notEnough: "不足:我们当前更需要可审阅、可编辑、可随项目部署的轻量实现,而不是先接托管记忆服务。",
|
||||
ourChoice: "我们的取舍:SQLite 做事件队列,Markdown 做人可编辑记忆正文,后续可替换成 Mem0/向量服务。",
|
||||
docs: "Mem0 Platform Overview",
|
||||
href: "https://docs.mem0.ai/platform/overview",
|
||||
},
|
||||
rag: {
|
||||
name: "RAG / Vector Memory",
|
||||
source: "把资料切片、embedding、向量检索,再把相关片段注入上下文。",
|
||||
borrowed: "借鉴:大规模知识不应全部塞进 prompt,要按任务召回。",
|
||||
notEnough: "不足:Skill 使用经验通常是流程偏好和纠错结论,不是纯语义相似文本召回。",
|
||||
ourChoice: "我们的取舍:业务知识先放 skill/knowledge;记忆先用结构化 Markdown,后续知识膨胀再接召回索引。",
|
||||
docs: "Letta Archival Memory",
|
||||
href: "https://docs.letta.com/guides/ade/archival-memory",
|
||||
},
|
||||
ours: {
|
||||
name: "ZK Data Agent Memory",
|
||||
source: "结合团队 Skill 场景:用户偏好和 Skill 使用经验是两类不同记忆。",
|
||||
borrowed: "借鉴:显式记忆、历史抽取、长期持久化、用户可编辑、异步后台处理。",
|
||||
notEnough: "主动放弃:不做实时自修改、不强依赖向量库、不把所有历史都注入。",
|
||||
ourChoice: "最终形态:user.md + skills/*.md + memory.db queue + worker + enabled skill 注入。",
|
||||
docs: "本项目实现",
|
||||
href: "#memory-implementation",
|
||||
},
|
||||
};
|
||||
|
||||
const memoryPipeline = [
|
||||
{ title: "检测信号", text: "显式记住 / 纠错 / Skill 流程 / 工具失败", code: "detect_memory_signals" },
|
||||
{ title: "事件入队", text: "主链路只写 pending event,不同步整理", code: "enqueue_interaction" },
|
||||
{ title: "批量整理", text: "后台 worker 每轮最多取 8 条,按 priority 处理", code: "_process_account_events" },
|
||||
{ title: "LLM 合并", text: "合并旧记忆,不追加流水账;失败走规则 fallback", code: "_generate_memory_updates" },
|
||||
{ title: "Markdown 落盘", text: "用户可看、可改、可删;revision 可追踪", code: "user.md / skills/*.md" },
|
||||
{ title: "按需注入", text: "只注入用户记忆和当前启用 Skill 的记忆", code: "render_injection" },
|
||||
];
|
||||
|
||||
const skills: Record<
|
||||
SkillPartId,
|
||||
{
|
||||
name: string;
|
||||
path: string;
|
||||
purpose: string;
|
||||
agentSees: string;
|
||||
maintainerDoes: string;
|
||||
}
|
||||
> = {
|
||||
skillmd: {
|
||||
name: "SKILL.md",
|
||||
path: "skills/<name>/SKILL.md",
|
||||
purpose: "定义 Agent 如何识别任务、何时澄清、何时 review、何时调用脚本。",
|
||||
agentSees: "流程协议、工具优先级、产物约定、人类确认边界。",
|
||||
maintainerDoes: "把经验写成可执行步骤,而不是把所有业务细节塞成一大段 prompt。",
|
||||
},
|
||||
knowledge: {
|
||||
name: "knowledge/",
|
||||
path: "skills/<name>/knowledge/",
|
||||
purpose: "放业务定义、标签边界、字段说明、判断准则。",
|
||||
agentSees: "按需读取具体知识文件,用于分析和生成。",
|
||||
maintainerDoes: "把经常变的业务规则放这里,降低改 SKILL.md 的频率。",
|
||||
},
|
||||
scripts: {
|
||||
name: "scripts/",
|
||||
path: "skills/<name>/scripts/",
|
||||
purpose: "放确定性能力:校验、格式转换、导出、数据清洗。",
|
||||
agentSees: "通过 python_exec 执行脚本,不再现场手写复杂转换逻辑。",
|
||||
maintainerDoes: "把易错代码沉淀成可测试脚本,输入输出写清楚。",
|
||||
},
|
||||
examples: {
|
||||
name: "examples/",
|
||||
path: "skills/<name>/examples/",
|
||||
purpose: "放典型输入输出,帮助 Agent 对齐格式和边界。",
|
||||
agentSees: "少量高质量样例,比长篇抽象说明更稳。",
|
||||
maintainerDoes: "新增失败 case 的最小复现,作为回归材料。",
|
||||
},
|
||||
readme: {
|
||||
name: "README.md",
|
||||
path: "skills/<name>/README.md",
|
||||
purpose: "给人看的维护说明:这是什么、怎么测、怎么扩展。",
|
||||
agentSees: "通常不强制注入,但可被读取用于理解项目背景。",
|
||||
maintainerDoes: "保证同事能接手,不必找原作者口头说明。",
|
||||
},
|
||||
update: {
|
||||
name: "更新 Skill",
|
||||
path: "Web 会话页 > Skills > 更新 Skill",
|
||||
purpose: "同事提交 Git 后,页面内拉取最新 Skill 文件,刷新可用能力。",
|
||||
agentSees: "更新后的 Skill 列表会进入模型可见能力列表。",
|
||||
maintainerDoes: "提交 Skill 改动后提醒使用者点更新;平台发布时再统一收敛。",
|
||||
},
|
||||
};
|
||||
|
||||
const taskFlows = [
|
||||
{
|
||||
name: "产品定义 → 数据集",
|
||||
skill: "product-data",
|
||||
input: "产品文档 / 手写标签边界 / 示例 query",
|
||||
review: "目标标签、complex、覆盖范围、数量计划",
|
||||
outputs: ["records.jsonl", "review.csv", "train.jsonl", "eval.csv"],
|
||||
id: "product",
|
||||
no: "06",
|
||||
title: "product-data",
|
||||
summary:
|
||||
"从产品定义、标签边界、样例 query 或 badcase 出发,生成标准 canonical records 并导出多种数据格式。",
|
||||
image: `${assetPrefix}/06-product-data.png`,
|
||||
doc: "/doc/06-product-data",
|
||||
points: [
|
||||
"先抽取 generation goal,再让用户 review 标签、complex、覆盖范围和排除边界。",
|
||||
"确认后生成 generation plan,再确认数量、轮次、路径和导出目标。",
|
||||
"dataset draft text 通过 scripts 转成 canonical records,再导出 records.jsonl、流转 CSV、训练 JSONL、评测 CSV。",
|
||||
],
|
||||
code: [
|
||||
"skills/product-data/SKILL.md",
|
||||
"skills/product-data/scripts/*",
|
||||
"canonical_record_v1",
|
||||
],
|
||||
decision:
|
||||
"数据生成质量不只取决于模型文本,还取决于格式规范、校验脚本和 review 门禁。",
|
||||
},
|
||||
{
|
||||
name: "线上日志 → 专项样本",
|
||||
skill: "online-mining-v2 + elk-fetch",
|
||||
input: "线上 badcase / query 特征 / domain 条件 / ELK 表",
|
||||
review: "筛选策略、候选质量、是否直接转样本还是继续生成",
|
||||
outputs: ["candidates.csv", "sample_review.md", "records.jsonl"],
|
||||
id: "online",
|
||||
no: "07",
|
||||
title: "online-mining-v2",
|
||||
summary:
|
||||
"基于 ELK 线上日志挖掘候选样本,支持策略 review、抽样分析,以及直接转换为标准数据。",
|
||||
image: `${assetPrefix}/07-online-mining-v2.png`,
|
||||
doc: "/doc/07-online-mining-v2",
|
||||
points: [
|
||||
"从需求、badcase 或示例 query 分析查询策略。",
|
||||
"通过 ELK 表获取 req_id、session、模型 prompt、模型输出、domain 等字段。",
|
||||
"候选结果先抽样 review,再决定直接转样本还是进入 product-data 补数链路。",
|
||||
],
|
||||
code: [
|
||||
"skills/online-mining-v2/SKILL.md",
|
||||
"skills/elk-fetch/",
|
||||
"build_online_records.py",
|
||||
],
|
||||
decision: "线上挖掘不是一次查询,策略需要多轮调整,候选质量需要人工确认。",
|
||||
},
|
||||
{
|
||||
name: "标签定义 → 判断辅助",
|
||||
skill: "标签大师",
|
||||
input: "query / 多轮上下文 / 标签定义知识库",
|
||||
review: "复杂度、多指令、自动任务、业务标签、输出形态",
|
||||
outputs: ["label decision", "boundary evidence", "target function"],
|
||||
id: "label",
|
||||
no: "08",
|
||||
title: "标签大师",
|
||||
summary:
|
||||
"把标签定义、复杂度、多指令、自动任务和边界经验组织为可检索、可解释的知识体系。",
|
||||
image: `${assetPrefix}/08-label-master.png`,
|
||||
doc: "/doc/08-label-master",
|
||||
points: [
|
||||
"复杂度、标签、多指令、自动任务是不同判断维度。",
|
||||
"标签知识通过目录、索引、边界卡片和 manifest 组织。",
|
||||
"输出需要同时满足标签合法性、输出形态和 complex 独立字段要求。",
|
||||
],
|
||||
code: [
|
||||
"skills/label-master/knowledge",
|
||||
"build_label_manifest.py",
|
||||
"validate_label_output.py",
|
||||
],
|
||||
decision:
|
||||
"标签判断需要逐步分析和引用知识,而不是把所有定义塞进一次模型输入。",
|
||||
},
|
||||
{
|
||||
id: "external",
|
||||
no: "09",
|
||||
title: "外部系统 Skill",
|
||||
summary:
|
||||
"ELK、SQL、模型迭代、飞书文档等外部能力通过 Skill 包方式接入平台。",
|
||||
image: `${assetPrefix}/09-external-skills.png`,
|
||||
doc: "/doc/09-external-skills",
|
||||
points: [
|
||||
"SKILL.md 描述外部系统能力边界和使用流程。",
|
||||
"scripts/ 负责调用外部 API、鉴权、查询和格式转换。",
|
||||
"最终结果统一回到当前会话 output,而不是散落在项目根目录。",
|
||||
],
|
||||
code: [
|
||||
"skills/elk-fetch",
|
||||
"skills/data-factory-sql",
|
||||
"skills/model-iteration",
|
||||
"Feishu MCP 转在线文档",
|
||||
],
|
||||
decision: "外部系统能力变化快,适合跟随 Skill 独立迭代,避免侵入基座。",
|
||||
},
|
||||
];
|
||||
|
||||
const runtimeTrace = [
|
||||
{ stage: "前端发送", text: "携带 account/session/model/enabled skills", path: "app/api/chat/route.ts" },
|
||||
{ stage: "上下文拼接", text: "Skill 列表 + 用户记忆 + Skill 记忆 + workspace", path: "backend/api/server.py" },
|
||||
{ stage: "Agent Loop", text: "模型决定回复、调用工具、执行脚本或等待 review", path: "src/agent_runtime.py" },
|
||||
{ stage: "工具执行", text: "python_exec / file / search / MCP / Skill scripts", path: "src/agent_tools.py" },
|
||||
{ stage: "事件流", text: "tool_start、tool_end、content_delta、tool_delta", path: "RunStateStore" },
|
||||
{ stage: "产物落盘", text: "scratchpad 存中间过程,output 存最终文件", path: ".port_sessions/.../sessions" },
|
||||
{ stage: "后台记忆", text: "交互完成后异步整理长期经验", path: "src/personal_memory.py" },
|
||||
];
|
||||
|
||||
const sourceLinks = [
|
||||
{ label: "OpenAI Memory FAQ", href: "https://help.openai.com/en/articles/8590148-memory-faq" },
|
||||
{ label: "Letta Stateful Agents", href: "https://docs.letta.com/guides/core-concepts/stateful-agents" },
|
||||
{ label: "Letta Archival Memory", href: "https://docs.letta.com/guides/ade/archival-memory" },
|
||||
{ label: "Mem0 Overview", href: "https://docs.mem0.ai/platform/overview" },
|
||||
{ label: "Semantic Kernel + Mem0", href: "https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-memory" },
|
||||
];
|
||||
|
||||
function StatusIcon({ value }: { value: "yes" | "partial" | "no" }) {
|
||||
if (value === "yes") return <Check className="doc-status yes" />;
|
||||
if (value === "partial") return <CircleDashed className="doc-status partial" />;
|
||||
return <X className="doc-status no" />;
|
||||
}
|
||||
|
||||
function ProductComparison() {
|
||||
const [selected, setSelected] = useState<ProductId>("zk");
|
||||
const detail = products[selected];
|
||||
const productIds: ProductId[] = ["chatgpt", "cursor", "claude", "zk"];
|
||||
|
||||
function SectionBlock({ section }: { section: Section }) {
|
||||
return (
|
||||
<section className="doc-section doc-compare-lab" id="why-us">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">先回答最尖锐的问题</p>
|
||||
<h2>为什么不用 Claude Code / ChatGPT / Cursor 就够了?</h2>
|
||||
<p>
|
||||
不是因为我们模型更强,而是因为我们补的是团队业务流程平台:多人入口、共享 Skill、数据源、产物、记忆和管理。
|
||||
</p>
|
||||
</div>
|
||||
<div className="doc-compare-layout">
|
||||
<div className="doc-product-switcher">
|
||||
{productIds.map((id) => (
|
||||
<button
|
||||
className={selected === id ? "is-selected" : ""}
|
||||
key={id}
|
||||
onClick={() => setSelected(id)}
|
||||
type="button"
|
||||
>
|
||||
<span>{products[id].name}</span>
|
||||
<small>{products[id].position}</small>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="doc-product-card">
|
||||
<p className="doc-kicker">当前选中</p>
|
||||
<h3>{detail.name}</h3>
|
||||
<div className="doc-product-facts">
|
||||
<section className="project-doc-section" id={section.id}>
|
||||
<div className="project-doc-section-head">
|
||||
<span>{section.no}</span>
|
||||
<div>
|
||||
<span>最适合</span>
|
||||
<p>{detail.bestFor}</p>
|
||||
<h2>{section.title}</h2>
|
||||
<p>{section.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
{section.image ? (
|
||||
<a
|
||||
className="project-doc-figure"
|
||||
href={section.image}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Image
|
||||
alt={section.title}
|
||||
height={900}
|
||||
priority={section.id === "base"}
|
||||
src={section.image}
|
||||
width={1600}
|
||||
/>
|
||||
</a>
|
||||
) : null}
|
||||
<div className="project-doc-grid">
|
||||
<div>
|
||||
<h3>关键机制</h3>
|
||||
{section.points.map((point) => (
|
||||
<p className="project-doc-point" key={point}>
|
||||
{point}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<span>边界</span>
|
||||
<p>{detail.limits}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="doc-chip-list">
|
||||
{detail.tech.map((item) => (
|
||||
<span className="doc-chip" key={item}>{item}</span>
|
||||
<h3>代码入口</h3>
|
||||
{section.code.map((item) => (
|
||||
<code key={item}>{item}</code>
|
||||
))}
|
||||
{section.doc ? (
|
||||
<a className="project-doc-link" href={section.doc} rel="noopener">
|
||||
查看详细文档
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<h3>设计取舍</h3>
|
||||
<p>{section.decision}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="doc-matrix">
|
||||
<div className="doc-matrix-head">
|
||||
<span>能力</span>
|
||||
{productIds.map((id) => <strong key={id}>{products[id].name}</strong>)}
|
||||
</div>
|
||||
{comparisonRows.map((row) => (
|
||||
<div className="doc-matrix-row" key={row.label}>
|
||||
<span>{row.label}</span>
|
||||
{productIds.map((id) => (
|
||||
<div className={selected === id ? "is-highlighted" : ""} key={id}>
|
||||
<StatusIcon value={row[id]} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RoleSwitcher() {
|
||||
const [selected, setSelected] = useState<RoleId>("user");
|
||||
const role = roles[selected];
|
||||
|
||||
return (
|
||||
<section className="doc-section doc-role-lab" id="roles">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">融入工作的方式</p>
|
||||
<h2>同一个平台,三类人看到的是三条不同路径</h2>
|
||||
</div>
|
||||
<div className="doc-role-grid">
|
||||
<div className="doc-role-tabs">
|
||||
{(Object.keys(roles) as RoleId[]).map((id) => (
|
||||
<button
|
||||
className={selected === id ? "is-selected" : ""}
|
||||
key={id}
|
||||
onClick={() => setSelected(id)}
|
||||
type="button"
|
||||
>
|
||||
{roles[id].name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="doc-role-panel">
|
||||
<h3>{role.title}</h3>
|
||||
<div className="doc-role-question">{role.question}</div>
|
||||
<p>{role.answer}</p>
|
||||
<div className="doc-flow-chips">
|
||||
{role.workflow.map((step) => (
|
||||
<span key={step}>{step}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="doc-proof-panel">
|
||||
<p className="doc-kicker">平台证据</p>
|
||||
{role.proof.map((item) => (
|
||||
<div className="doc-proof-item" key={item}>
|
||||
<CheckCircle2 />
|
||||
<span>{item}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ObjectiveBoard() {
|
||||
return (
|
||||
<section className="doc-section doc-objectives" id="design-goals">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">这页怎么讲清楚系统</p>
|
||||
<h2>每个前端模块都必须服务一个表达目标</h2>
|
||||
</div>
|
||||
<div className="doc-objective-grid">
|
||||
{objectives.map((item, index) => (
|
||||
<article className="doc-objective" key={item.goal}>
|
||||
<span>{String(index + 1).padStart(2, "0")}</span>
|
||||
<h3>{item.goal}</h3>
|
||||
<p>{item.frontend}</p>
|
||||
<strong>{item.message}</strong>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function MemoryAtlas() {
|
||||
const [selected, setSelected] = useState<MemoryId>("ours");
|
||||
const item = memoryPatterns[selected];
|
||||
const order: MemoryId[] = ["chatgpt", "letta", "mem0", "rag", "ours"];
|
||||
|
||||
return (
|
||||
<section className="doc-section doc-memory-lab" id="memory">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">记忆方案谱系</p>
|
||||
<h2>我们不是闭门造车:先看主流方案,再看为什么这样落地</h2>
|
||||
<p>
|
||||
记忆不是一个功能开关。我们参考了显式保存、历史参考、状态化 Agent、归档检索、托管记忆层等方案,最后按团队 Skill 场景做了轻量实现。
|
||||
</p>
|
||||
</div>
|
||||
<div className="doc-memory-map">
|
||||
<div className="doc-memory-rail">
|
||||
{order.map((id, index) => (
|
||||
<button
|
||||
className={selected === id ? "is-selected" : ""}
|
||||
key={id}
|
||||
onClick={() => setSelected(id)}
|
||||
type="button"
|
||||
>
|
||||
<span>{String(index + 1).padStart(2, "0")}</span>
|
||||
<strong>{memoryPatterns[id].name}</strong>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="doc-memory-detail-v2">
|
||||
<div className="doc-memory-source">
|
||||
<a href={item.href}>{item.docs}</a>
|
||||
</div>
|
||||
<h3>{item.name}</h3>
|
||||
<div className="doc-memory-grid-v2">
|
||||
<div><span>它怎么做</span><p>{item.source}</p></div>
|
||||
<div><span>我们借鉴</span><p>{item.borrowed}</p></div>
|
||||
<div><span>为什么不够</span><p>{item.notEnough}</p></div>
|
||||
<div><span>我们的取舍</span><p>{item.ourChoice}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="doc-memory-implementation" id="memory-implementation">
|
||||
<div className="doc-section-heading compact">
|
||||
<p className="doc-kicker">当前实现</p>
|
||||
<h2>用户记忆 + Skill 记忆 + 异步队列</h2>
|
||||
</div>
|
||||
<div className="doc-memory-pipeline-v2">
|
||||
{memoryPipeline.map((step, index) => (
|
||||
<div className="doc-memory-step" key={step.title}>
|
||||
<span>{String(index + 1).padStart(2, "0")}</span>
|
||||
<strong>{step.title}</strong>
|
||||
<p>{step.text}</p>
|
||||
<code>{step.code}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SkillWorkbench() {
|
||||
const [selected, setSelected] = useState<SkillPartId>("skillmd");
|
||||
const part = skills[selected];
|
||||
const order: SkillPartId[] = ["skillmd", "knowledge", "scripts", "examples", "readme", "update"];
|
||||
|
||||
return (
|
||||
<section className="doc-section doc-skill-workbench" id="skill-dev">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">Skill 怎么开发,怎么共享,怎么更新</p>
|
||||
<h2>把一个人的经验打包成团队能力,不靠口口相传</h2>
|
||||
</div>
|
||||
<div className="doc-skill-workbench-grid">
|
||||
<div className="doc-file-tree-v2">
|
||||
{order.map((id) => (
|
||||
<button className={selected === id ? "is-selected" : ""} key={id} onClick={() => setSelected(id)} type="button">
|
||||
<FolderTree />
|
||||
<span>{skills[id].name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="doc-skill-part">
|
||||
<code>{part.path}</code>
|
||||
<h3>{part.name}</h3>
|
||||
<div className="doc-skill-part-grid">
|
||||
<div><span>解决什么</span><p>{part.purpose}</p></div>
|
||||
<div><span>Agent 看到什么</span><p>{part.agentSees}</p></div>
|
||||
<div><span>维护者做什么</span><p>{part.maintainerDoes}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="doc-skill-steps">
|
||||
<h3>一个 Skill 的提交路径</h3>
|
||||
{["创建目录", "补 SKILL.md", "沉淀脚本/知识", "本地测试", "提交 Git", "页面点更新 Skill"].map((step, index) => (
|
||||
<div key={step}><span>{index + 1}</span>{step}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskFlowMap() {
|
||||
return (
|
||||
<section className="doc-section doc-task-map" id="tasks">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">已经能进入工作的场景</p>
|
||||
<h2>不是展示功能点,而是展示从输入到产物的链路</h2>
|
||||
</div>
|
||||
<div className="doc-task-grid">
|
||||
{taskFlows.map((flow) => (
|
||||
<article className="doc-task-card" key={flow.name}>
|
||||
<div className="doc-task-title"><Route /><h3>{flow.name}</h3></div>
|
||||
<div className="doc-task-meta"><span>Skill</span><strong>{flow.skill}</strong></div>
|
||||
<div className="doc-task-meta"><span>输入</span><p>{flow.input}</p></div>
|
||||
<div className="doc-task-meta"><span>Review 点</span><p>{flow.review}</p></div>
|
||||
<div className="doc-output-list">
|
||||
{flow.outputs.map((output) => <span key={output}>{output}</span>)}
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RuntimeTrace() {
|
||||
return (
|
||||
<section className="doc-section doc-runtime-trace" id="runtime">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">运行时链路</p>
|
||||
<h2>从用户按下发送,到文件、活动区和记忆更新</h2>
|
||||
</div>
|
||||
<div className="doc-runtime-rail">
|
||||
{runtimeTrace.map((step, index) => (
|
||||
<div className="doc-runtime-node" key={step.stage}>
|
||||
<span>{String(index + 1).padStart(2, "0")}</span>
|
||||
<strong>{step.stage}</strong>
|
||||
<p>{step.text}</p>
|
||||
<code>{step.path}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DocPage() {
|
||||
return (
|
||||
<main className="doc-page doc-page-v2">
|
||||
<header className="doc-hero-v2">
|
||||
<nav className="doc-nav">
|
||||
<a className="doc-brand" href="/">
|
||||
<span className="doc-brand-mark">ZK</span>
|
||||
<span>Data Agent</span>
|
||||
</a>
|
||||
<div className="doc-nav-links">
|
||||
<a href="#why-us">为什么用</a>
|
||||
<a href="#roles">怎么融入</a>
|
||||
<a href="#memory">记忆取舍</a>
|
||||
<a href="#skill-dev">开发 Skill</a>
|
||||
<a href="#runtime">运行链路</a>
|
||||
<main className="project-doc-page">
|
||||
<header className="project-doc-hero">
|
||||
<nav className="project-doc-nav">
|
||||
<a href="/">ZK Data Agent</a>
|
||||
<div>
|
||||
<a href="#base">基座</a>
|
||||
<a href="#loop">Agent Loop</a>
|
||||
<a href="#skills">Skill</a>
|
||||
<a href="#product">业务链路</a>
|
||||
<a href="#code-index">代码索引</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section className="doc-hero-v2-grid">
|
||||
<div className="doc-hero-v2-copy">
|
||||
<p className="doc-kicker">不是更聪明的聊天框</p>
|
||||
<h1>这是一个把团队业务流程沉淀成共享 Skill 的 Agent 工作台</h1>
|
||||
<div className="project-doc-hero-inner">
|
||||
<p className="project-doc-kicker">技术架构说明</p>
|
||||
<h1>ZK Data Agent</h1>
|
||||
<p>
|
||||
它补的不是“再问模型一次”,而是团队级能力复用:多人入口、业务 Skill、线上数据源、会话产物、Skill 记忆、管理后台和可审阅执行链路。
|
||||
基于本地工程 Agent、工具调用、Skill
|
||||
能力包和会话工作区构建的团队工作台,用来把数据开发、线上挖掘、标签知识建设中的流程经验变成可复用能力。
|
||||
</p>
|
||||
<div className="doc-hero-actions">
|
||||
<a className="doc-primary-link" href="#why-us">先回答为什么用我们 <ArrowRight /></a>
|
||||
<a className="doc-secondary-link" href="#skill-dev">我想开发 Skill</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="doc-command-wall">
|
||||
<div className="doc-command-head"><Sparkles /> 一次真实任务会经过什么</div>
|
||||
{["选择 product-data Skill", "对齐标签和 complex", "生成可 review 计划", "调用 scripts 导出 records/train/eval", "文件进入当前会话 output"].map((item, index) => (
|
||||
<div className="doc-command-row" key={item}>
|
||||
<span>{String(index + 1).padStart(2, "0")}</span>
|
||||
<strong>{item}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<ObjectiveBoard />
|
||||
<ProductComparison />
|
||||
<RoleSwitcher />
|
||||
<TaskFlowMap />
|
||||
<MemoryAtlas />
|
||||
<SkillWorkbench />
|
||||
<RuntimeTrace />
|
||||
<ToolPositionMap />
|
||||
|
||||
<section className="doc-section doc-source-links">
|
||||
<div className="doc-section-heading">
|
||||
<p className="doc-kicker">参考来源</p>
|
||||
<h2>记忆和 Agent 设计参考了这些现成方案</h2>
|
||||
<section className="project-doc-section" id="architecture">
|
||||
<div className="project-doc-section-head">
|
||||
<span>00</span>
|
||||
<div>
|
||||
<h2>ZK-Data-Agent 架构</h2>
|
||||
<p>
|
||||
基座是一个通用 Code Agent Loop;我们的工作不是重写这个闭环,而是在关键节点补上团队业务能力,让执行、业务上下文和流程沉淀进入同一套工作台。
|
||||
</p>
|
||||
</div>
|
||||
<div className="doc-source-grid">
|
||||
{sourceLinks.map((link) => (
|
||||
<a href={link.href} key={link.href}><Network />{link.label}</a>
|
||||
</div>
|
||||
<a
|
||||
className="project-doc-figure"
|
||||
href={architectureImage}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Image
|
||||
alt="ZK-Data-Agent 架构"
|
||||
height={900}
|
||||
priority
|
||||
src={architectureImage}
|
||||
width={1600}
|
||||
/>
|
||||
</a>
|
||||
<div className="project-doc-grid">
|
||||
<div>
|
||||
<h3>基座提供执行闭环</h3>
|
||||
<p>
|
||||
Code Agent 负责把任务放进多轮循环:组装上下文、模型推理、调用工具、观察结果,再继续规划或完成。它让模型从一次回答变成可以持续执行的运行时。
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>增强层体现我们的工作</h3>
|
||||
<p>
|
||||
ZK-Data-Agent 在上下文、工具、产物和团队协作节点接入 Skill 体系、Skill 热更新、用户记忆、Skill 记忆、portable scripts、会话工作区、活动流和管理后台。
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>效率来自工程化补齐</h3>
|
||||
<p>
|
||||
工具和工作区提升研发执行效率;Skill 与记忆提升业务嵌入深度;Git 化 Skill、热更新和团队治理提升流程可复用度。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{sections.map((section) => (
|
||||
<SectionBlock key={section.id} section={section} />
|
||||
))}
|
||||
|
||||
{skillSections.map((section) => (
|
||||
<SectionBlock key={section.id} section={section} />
|
||||
))}
|
||||
|
||||
<section className="project-doc-section" id="code-index">
|
||||
<div className="project-doc-section-head">
|
||||
<span>I</span>
|
||||
<div>
|
||||
<h2>代码索引</h2>
|
||||
<p>
|
||||
后续排查或二次开发时,可以先按下面入口定位问题属于基座、工具、Skill、工作区还是记忆。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="project-doc-index">
|
||||
<div>
|
||||
<h3>Runtime</h3>
|
||||
<code>backend/api/server.py</code>
|
||||
<code>src/agent_runtime.py</code>
|
||||
<code>src/openai_compat.py</code>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Prompt / Tool</h3>
|
||||
<code>src/agent_prompting.py</code>
|
||||
<code>src/agent_tools.py</code>
|
||||
<code>src/agent_tool_specs/</code>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Skill</h3>
|
||||
<code>src/bundled_skills.py</code>
|
||||
<code>skills/product-data/</code>
|
||||
<code>skills/label-master/</code>
|
||||
</div>
|
||||
<div>
|
||||
<h3>State</h3>
|
||||
<code>src/personal_memory.py</code>
|
||||
<code>.port_sessions/accounts/</code>
|
||||
<code>frontend/app/app/doc/page.tsx</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
type CSSProperties,
|
||||
type PointerEvent,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
type ToolPosition = {
|
||||
id: string;
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
type: string;
|
||||
color: string;
|
||||
summary: string;
|
||||
fit: string;
|
||||
limit: string;
|
||||
};
|
||||
|
||||
type Point3D = {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
|
||||
type ProjectionMode = "xy" | "xz" | "yz" | "free";
|
||||
|
||||
const tools: ToolPosition[] = [
|
||||
{
|
||||
id: "manual",
|
||||
name: "纯人工",
|
||||
x: 24,
|
||||
y: 82,
|
||||
z: 22,
|
||||
type: "人主导",
|
||||
color: "#64748b",
|
||||
summary: "最懂业务,但查、写、跑、改、整理都靠人推进。",
|
||||
fit: "复杂边界判断、探索性分析、最终质量把关。",
|
||||
limit: "执行成本高,流程复用依赖个人习惯和文档质量。",
|
||||
},
|
||||
{
|
||||
id: "web-ai",
|
||||
name: "网页版 AI",
|
||||
x: 54,
|
||||
y: 24,
|
||||
z: 18,
|
||||
type: "通用 AI",
|
||||
color: "#38bdf8",
|
||||
summary: "适合问答、总结、生成草稿,启动快。",
|
||||
fit: "通用解释、文本生成、局部判断辅助。",
|
||||
limit: "不天然接入内部日志、标签规则、标准产物和团队流程。",
|
||||
},
|
||||
{
|
||||
id: "copilot",
|
||||
name: "IDE Copilot",
|
||||
x: 62,
|
||||
y: 34,
|
||||
z: 28,
|
||||
type: "编码辅助",
|
||||
color: "#60a5fa",
|
||||
summary: "在编辑器里提升局部编码效率。",
|
||||
fit: "补全、局部函数、单文件修改。",
|
||||
limit: "更偏代码片段,不负责跨系统数据流和业务产物链。",
|
||||
},
|
||||
{
|
||||
id: "cursor",
|
||||
name: "Cursor",
|
||||
x: 76,
|
||||
y: 46,
|
||||
z: 38,
|
||||
type: "IDE Agent",
|
||||
color: "#818cf8",
|
||||
summary: "围绕代码仓库做理解、修改和多文件协作。",
|
||||
fit: "工程代码迭代、仓库内上下文开发。",
|
||||
limit: "团队业务流程、线上数据、标准导出和共享 Skill 需要另行组织。",
|
||||
},
|
||||
{
|
||||
id: "code-agent",
|
||||
name: "Claude Code / Codex",
|
||||
x: 84,
|
||||
y: 58,
|
||||
z: 45,
|
||||
type: "工程 Agent",
|
||||
color: "#a78bfa",
|
||||
summary: "能读代码、调工具、执行多步工程任务。",
|
||||
fit: "本地工程闭环、复杂代码修改、命令执行。",
|
||||
limit: "强在个人工作区;团队级业务流程沉淀和 Web 多用户协作不是默认形态。",
|
||||
},
|
||||
{
|
||||
id: "notebook",
|
||||
name: "个人自动化脚本",
|
||||
x: 66,
|
||||
y: 74,
|
||||
z: 48,
|
||||
type: "个人自动化",
|
||||
color: "#f59e0b",
|
||||
summary: "贴近业务数据,能把部分流程脚本化。",
|
||||
fit: "数据清洗、临时统计、可重复实验。",
|
||||
limit: "入口、权限、review、产物管理和团队复用通常需要人额外维护。",
|
||||
},
|
||||
{
|
||||
id: "prompt-doc",
|
||||
name: "经验文档",
|
||||
x: 38,
|
||||
y: 62,
|
||||
z: 60,
|
||||
type: "经验沉淀",
|
||||
color: "#10b981",
|
||||
summary: "能记录规则和经验,便于传播。",
|
||||
fit: "标签边界、流程说明、操作规范。",
|
||||
limit: "本身不执行任务,仍需要人把文档转成操作。",
|
||||
},
|
||||
{
|
||||
id: "zk",
|
||||
name: "ZK Data Agent",
|
||||
x: 80,
|
||||
y: 88,
|
||||
z: 86,
|
||||
type: "业务 Agent 工作台",
|
||||
color: "#2563eb",
|
||||
summary:
|
||||
"把 Agent Loop、工具执行、业务知识、review 和产物管理组织成团队流程。",
|
||||
fit: "数据生成、线上挖掘、标签判断、外部系统 Skill 接入。",
|
||||
limit: "不替代人的业务判断;重点是把判断节点放进可复用流程。",
|
||||
},
|
||||
];
|
||||
|
||||
const presets: Array<{
|
||||
mode: ProjectionMode;
|
||||
name: string;
|
||||
rotateX: number;
|
||||
rotateY: number;
|
||||
}> = [
|
||||
{ mode: "xy", name: "研发 × 业务", rotateX: 0, rotateY: 0 },
|
||||
{ mode: "xz", name: "研发 × 复用", rotateX: -90, rotateY: 0 },
|
||||
{ mode: "yz", name: "业务 × 复用", rotateX: 0, rotateY: -90 },
|
||||
];
|
||||
|
||||
const dimensionCards = [
|
||||
{
|
||||
title: "研发执行效率",
|
||||
text: "读代码、调工具、跑脚本、生成产物。",
|
||||
},
|
||||
{
|
||||
title: "业务嵌入深度",
|
||||
text: "接住日志、标签体系、数据格式和团队规则。",
|
||||
},
|
||||
{
|
||||
title: "流程可复用度",
|
||||
text: "把一次解决方案沉淀成团队能继续调用的能力。",
|
||||
},
|
||||
];
|
||||
|
||||
const frictionCases = [
|
||||
{ text: "标签边界依赖经验", weight: 5 },
|
||||
{ text: "格式手工补齐", weight: 4 },
|
||||
{ text: "规则散在文档", weight: 4 },
|
||||
{ text: "线上数据难取", weight: 4 },
|
||||
{ text: "经验靠口口相传", weight: 3 },
|
||||
{ text: "脚本只在个人机器", weight: 3 },
|
||||
{ text: "Review 断在中间", weight: 3 },
|
||||
{ text: "样例难复用", weight: 2 },
|
||||
{ text: "产物路径分散", weight: 2 },
|
||||
{ text: "路径和权限反复确认", weight: 2 },
|
||||
];
|
||||
|
||||
const cubeSize = 360;
|
||||
const cubeCenter = cubeSize / 2;
|
||||
const stageCenter = 280;
|
||||
const perspective = 950;
|
||||
const axisOrigin = { x: -cubeCenter, y: cubeCenter, z: -cubeCenter };
|
||||
const axisDefinitions = [
|
||||
{
|
||||
id: "x",
|
||||
name: "研发执行效率",
|
||||
color: "#2563eb",
|
||||
start: axisOrigin,
|
||||
end: { x: cubeCenter, y: cubeCenter, z: -cubeCenter },
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "业务嵌入深度",
|
||||
color: "#0f766e",
|
||||
start: axisOrigin,
|
||||
end: { x: -cubeCenter, y: -cubeCenter, z: -cubeCenter },
|
||||
},
|
||||
{
|
||||
id: "z",
|
||||
name: "流程可复用度",
|
||||
color: "#9333ea",
|
||||
start: axisOrigin,
|
||||
end: { x: -cubeCenter, y: cubeCenter, z: cubeCenter },
|
||||
},
|
||||
];
|
||||
const visibleAxesByMode: Record<ProjectionMode, string[]> = {
|
||||
xy: ["x", "y"],
|
||||
xz: ["x", "z"],
|
||||
yz: ["z", "y"],
|
||||
free: ["x", "y", "z"],
|
||||
};
|
||||
|
||||
function score(tool: ToolPosition) {
|
||||
return Math.round((tool.x + tool.y + tool.z) / 3);
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function getPointCoordinates(tool: ToolPosition) {
|
||||
return {
|
||||
x: (tool.x / 100) * cubeSize - cubeCenter,
|
||||
y: cubeCenter - (tool.y / 100) * cubeSize,
|
||||
z: (tool.z / 100) * cubeSize - cubeCenter,
|
||||
};
|
||||
}
|
||||
|
||||
function projectPoint(
|
||||
point: Point3D,
|
||||
rotateX: number,
|
||||
rotateY: number,
|
||||
mode: ProjectionMode,
|
||||
) {
|
||||
if (mode === "xy") {
|
||||
return {
|
||||
x: stageCenter + point.x,
|
||||
y: stageCenter + point.y,
|
||||
z: point.z,
|
||||
scale: 1,
|
||||
};
|
||||
}
|
||||
if (mode === "xz") {
|
||||
return {
|
||||
x: stageCenter + point.x,
|
||||
y: stageCenter - point.z,
|
||||
z: -point.y,
|
||||
scale: 1,
|
||||
};
|
||||
}
|
||||
if (mode === "yz") {
|
||||
return {
|
||||
x: stageCenter + point.z,
|
||||
y: stageCenter + point.y,
|
||||
z: point.x,
|
||||
scale: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const angleX = (rotateX * Math.PI) / 180;
|
||||
const angleY = (rotateY * Math.PI) / 180;
|
||||
const cosX = Math.cos(angleX);
|
||||
const sinX = Math.sin(angleX);
|
||||
const cosY = Math.cos(angleY);
|
||||
const sinY = Math.sin(angleY);
|
||||
const afterY = {
|
||||
x: point.x * cosY + point.z * sinY,
|
||||
y: point.y,
|
||||
z: -point.x * sinY + point.z * cosY,
|
||||
};
|
||||
const rotated = {
|
||||
x: afterY.x,
|
||||
y: afterY.y * cosX - afterY.z * sinX,
|
||||
z: afterY.y * sinX + afterY.z * cosX,
|
||||
};
|
||||
const scale = perspective / (perspective - rotated.z);
|
||||
|
||||
return {
|
||||
x: stageCenter + rotated.x * scale,
|
||||
y: stageCenter + rotated.y * scale,
|
||||
z: rotated.z,
|
||||
scale,
|
||||
};
|
||||
}
|
||||
|
||||
function toolAnchorStyle(
|
||||
tool: ToolPosition,
|
||||
projected: ReturnType<typeof projectPoint>,
|
||||
) {
|
||||
const scale = Math.min(1.08, Math.max(0.88, projected.scale));
|
||||
|
||||
return {
|
||||
"--tool-x": `${projected.x}px`,
|
||||
"--tool-y": `${projected.y}px`,
|
||||
"--label-scale": `${scale}`,
|
||||
"--point-color": tool.color,
|
||||
zIndex: Math.round(500 + projected.z),
|
||||
} as CSSProperties;
|
||||
}
|
||||
|
||||
function getProjectedAxes(
|
||||
rotateX: number,
|
||||
rotateY: number,
|
||||
mode: ProjectionMode,
|
||||
) {
|
||||
const visibleAxes = new Set(visibleAxesByMode[mode]);
|
||||
return axisDefinitions
|
||||
.filter((axis) => visibleAxes.has(axis.id))
|
||||
.map((axis) => {
|
||||
const start = projectPoint(axis.start, rotateX, rotateY, mode);
|
||||
const end = projectPoint(axis.end, rotateX, rotateY, mode);
|
||||
const vectorX = end.x - start.x;
|
||||
const vectorY = end.y - start.y;
|
||||
const length = Math.max(1, Math.hypot(vectorX, vectorY));
|
||||
const labelX = clamp(end.x + (vectorX / length) * 34, 44, 516);
|
||||
const labelY = clamp(end.y + (vectorY / length) * 34, 32, 528);
|
||||
|
||||
return {
|
||||
...axis,
|
||||
start,
|
||||
end,
|
||||
labelStyle: {
|
||||
"--axis-label-x": `${labelX}px`,
|
||||
"--axis-label-y": `${labelY}px`,
|
||||
"--axis-color": axis.color,
|
||||
} as CSSProperties,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function ToolPositionMap() {
|
||||
const [projectionMode, setProjectionMode] = useState<ProjectionMode>("xy");
|
||||
const [rotateX, setRotateX] = useState(0);
|
||||
const [rotateY, setRotateY] = useState(0);
|
||||
const [selectedId, setSelectedId] = useState("zk");
|
||||
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragRef = useRef<{
|
||||
x: number;
|
||||
y: number;
|
||||
rotateX: number;
|
||||
rotateY: number;
|
||||
} | null>(null);
|
||||
|
||||
const selected = useMemo(
|
||||
() => tools.find((tool) => tool.id === selectedId) ?? tools[0],
|
||||
[selectedId],
|
||||
);
|
||||
const projectedTools = useMemo(
|
||||
() =>
|
||||
tools
|
||||
.map((tool) => ({
|
||||
tool,
|
||||
projected: projectPoint(
|
||||
getPointCoordinates(tool),
|
||||
rotateX,
|
||||
rotateY,
|
||||
projectionMode,
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => a.projected.z - b.projected.z),
|
||||
[rotateX, rotateY, projectionMode],
|
||||
);
|
||||
const projectedAxes = useMemo(
|
||||
() => getProjectedAxes(rotateX, rotateY, projectionMode),
|
||||
[rotateX, rotateY, projectionMode],
|
||||
);
|
||||
|
||||
const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {
|
||||
setProjectionMode("free");
|
||||
dragRef.current = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
rotateX,
|
||||
rotateY,
|
||||
};
|
||||
setIsDragging(true);
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragRef.current) return;
|
||||
const deltaX = event.clientX - dragRef.current.x;
|
||||
const deltaY = event.clientY - dragRef.current.y;
|
||||
setRotateX(dragRef.current.rotateX - deltaY * 0.45);
|
||||
setRotateY(dragRef.current.rotateY + deltaX * 0.45);
|
||||
};
|
||||
|
||||
const handlePointerEnd = (event: PointerEvent<HTMLDivElement>) => {
|
||||
dragRef.current = null;
|
||||
setIsDragging(false);
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="project-doc-section project-doc-position" id="position">
|
||||
<div className="position-framing">
|
||||
<div className="position-framing-copy">
|
||||
<span>效率公式</span>
|
||||
<h3 className="position-formula">
|
||||
<span>团队效率</span>
|
||||
<b>≈</b>
|
||||
<span>研发执行效率</span>
|
||||
<b>×</b>
|
||||
<span>业务嵌入深度</span>
|
||||
<b>×</b>
|
||||
<span>流程可复用度</span>
|
||||
</h3>
|
||||
<p>
|
||||
AI
|
||||
能回答,脚本能处理,文档能沉淀。但只要其中一环还要人工来回衔接,效率就会被拉回到原来的流程里。
|
||||
</p>
|
||||
</div>
|
||||
<div className="position-dimension-grid">
|
||||
{dimensionCards.map((card) => (
|
||||
<div key={card.title}>
|
||||
<strong>{card.title}</strong>
|
||||
<p>{card.text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="position-friction-cloud">
|
||||
<strong>典型断点</strong>
|
||||
{frictionCases.map((item) => (
|
||||
<span className={`is-weight-${item.weight}`} key={item.text}>
|
||||
{item.text}
|
||||
</span>
|
||||
))}
|
||||
<em>下面这张图,就是把这些工作方式放进同一套坐标里看。</em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="position-map-layout">
|
||||
<div className="position-stage-card">
|
||||
<div className="position-controls">
|
||||
<div>
|
||||
<span>
|
||||
{projectionMode === "free"
|
||||
? "拖动坐标系自由旋转"
|
||||
: "二维投影视图"}
|
||||
</span>
|
||||
<strong>
|
||||
{projectionMode === "free"
|
||||
? `X ${Math.round(rotateX)} / Y ${Math.round(rotateY)}`
|
||||
: presets.find((preset) => preset.mode === projectionMode)
|
||||
?.name}
|
||||
</strong>
|
||||
</div>
|
||||
<div className="position-preset-row">
|
||||
{presets.map((preset) => (
|
||||
<button
|
||||
className={
|
||||
projectionMode === preset.mode ? "is-selected" : undefined
|
||||
}
|
||||
key={preset.name}
|
||||
onClick={() => {
|
||||
setProjectionMode(preset.mode);
|
||||
setRotateX(preset.rotateX);
|
||||
setRotateY(preset.rotateY);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{preset.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={
|
||||
isDragging ? "position-stage is-dragging" : "position-stage"
|
||||
}
|
||||
onPointerCancel={handlePointerEnd}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerEnd}
|
||||
>
|
||||
<div className="position-scene">
|
||||
<div className="position-space-grid" />
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="position-axis-overlay"
|
||||
viewBox="0 0 560 560"
|
||||
>
|
||||
{projectedAxes.map((axis) => (
|
||||
<line
|
||||
key={axis.id}
|
||||
stroke={axis.color}
|
||||
x1={axis.start.x}
|
||||
x2={axis.end.x}
|
||||
y1={axis.start.y}
|
||||
y2={axis.end.y}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
<div className="position-axis-label-layer">
|
||||
{projectedAxes.map((axis) => (
|
||||
<span
|
||||
className="position-axis-label"
|
||||
key={axis.id}
|
||||
style={axis.labelStyle}
|
||||
>
|
||||
{axis.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="position-tool-layer">
|
||||
{projectedTools.map(({ tool, projected }) => (
|
||||
<button
|
||||
className={[
|
||||
"position-tool-anchor",
|
||||
tool.id === selectedId ? "is-selected" : "",
|
||||
tool.id === hoveredId ? "is-hovered" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
key={tool.id}
|
||||
onClick={() => setSelectedId(tool.id)}
|
||||
onBlur={() => setHoveredId(null)}
|
||||
onFocus={() => setHoveredId(tool.id)}
|
||||
onMouseEnter={() => setHoveredId(tool.id)}
|
||||
onMouseLeave={() => setHoveredId(null)}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
style={toolAnchorStyle(tool, projected)}
|
||||
type="button"
|
||||
>
|
||||
<span className="position-ball" />
|
||||
<span className="position-tool-label">{tool.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="position-detail-card">
|
||||
<div className="position-score">
|
||||
<span>{selected.type}</span>
|
||||
<strong>{score(selected)}</strong>
|
||||
</div>
|
||||
<h3>{selected.name}</h3>
|
||||
<p>{selected.summary}</p>
|
||||
<div className="position-meter-list">
|
||||
<div>
|
||||
<span>研发执行效率</span>
|
||||
<i style={{ width: `${selected.x}%` }} />
|
||||
</div>
|
||||
<div>
|
||||
<span>业务嵌入深度</span>
|
||||
<i style={{ width: `${selected.y}%` }} />
|
||||
</div>
|
||||
<div>
|
||||
<span>流程可复用度</span>
|
||||
<i style={{ width: `${selected.z}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>适合</dt>
|
||||
<dd>{selected.fit}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>限制</dt>
|
||||
<dd>{selected.limit}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
# 01. 基座 Runtime 架构
|
||||
|
||||

|
||||
|
||||
## 1. 基座解决的问题
|
||||
|
||||
基座不绑定某一个业务流程。它提供的是通用 Agent runtime:
|
||||
|
||||
- 多用户 Web 入口。
|
||||
- 多会话状态管理。
|
||||
- 模型选择和 OpenAI-compatible 调用。
|
||||
- Tool registry 和 tool handler 执行。
|
||||
- Skill 发现、启用和提示词注入。
|
||||
- 当前会话工作区。
|
||||
- 运行态、事件流、活动区和刷新恢复。
|
||||
- 用户记忆和 Skill 记忆后台。
|
||||
|
||||
业务能力例如 `product-data`、`online-mining-v2`、`label-master` 都跑在这个基座上。
|
||||
|
||||
## 2. 文字架构图
|
||||
|
||||
```text
|
||||
浏览器 / Web UI
|
||||
|
|
||||
| 用户消息、模型选择、Skill 启用、文件面板、停止运行
|
||||
v
|
||||
frontend/app
|
||||
|
|
||||
| /api/chat、/api/claw/*、/admin
|
||||
v
|
||||
backend/api/server.py
|
||||
|
|
||||
| 账号配置、会话目录、run manager、run_state_store、memory_manager
|
||||
v
|
||||
LocalCodingAgent
|
||||
|
|
||||
| build_session、prompt sections、tool_specs、Agent loop
|
||||
v
|
||||
OpenAICompatClient
|
||||
|
|
||||
| messages + tools -> model backend
|
||||
v
|
||||
模型
|
||||
|
|
||||
| assistant text 或 tool_calls
|
||||
v
|
||||
Tool Runtime
|
||||
|
|
||||
| read/write/python_exec/data_agent/MCP/search/bash
|
||||
v
|
||||
session workspace
|
||||
|
|
||||
| input / scratchpad / output / session.json
|
||||
v
|
||||
前端活动区和文件面板
|
||||
|
||||
运行结束后:
|
||||
|
||||
AgentRunResult
|
||||
-> session_store 持久化
|
||||
-> memory_manager.enqueue_interaction
|
||||
-> memory worker 异步整理
|
||||
-> 下一轮 render_injection 注入
|
||||
```
|
||||
|
||||
## 3. 关键代码入口
|
||||
|
||||
### 3.1 Web 后端入口
|
||||
|
||||
主要文件:
|
||||
|
||||
```text
|
||||
backend/api/server.py
|
||||
```
|
||||
|
||||
关键职责:
|
||||
|
||||
- 维护账号和会话配置。
|
||||
- 创建或复用 `LocalCodingAgent`。
|
||||
- 给 Agent 注入 `runtime_context`、记忆、Jupyter 上下文。
|
||||
- 记录 run event,并通过 NDJSON streaming 返回前端。
|
||||
- 运行结束后写入 elapsed、标题、memory event。
|
||||
|
||||
关键函数和位置:
|
||||
|
||||
```text
|
||||
backend/api/server.py:691 enabled_skill_names(...)
|
||||
backend/api/server.py:822 _build_agent(...)
|
||||
backend/api/server.py:853 agent_for(...)
|
||||
backend/api/server.py:866 run_lock_for(...)
|
||||
backend/api/server.py:1720 /api/chat 运行链路开始
|
||||
backend/api/server.py:1739 memory_manager.render_injection(...)
|
||||
backend/api/server.py:1751 emit_agent_event(...)
|
||||
backend/api/server.py:1897 序列化运行结果和 elapsed
|
||||
backend/api/server.py:1920 memory_manager.enqueue_interaction(...)
|
||||
backend/api/server.py:2034 StreamingResponse NDJSON
|
||||
```
|
||||
|
||||
### 3.2 Agent Runtime
|
||||
|
||||
主要文件:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py
|
||||
```
|
||||
|
||||
关键职责:
|
||||
|
||||
- 初始化 tool registry、plugin runtime、MCP runtime、search runtime 等。
|
||||
- 新会话用 `run(...)`,旧会话用 `resume(...)`。
|
||||
- 在 `_run_prompt(...)` 中执行完整 Agent loop。
|
||||
- 维护 usage、cost、tool_calls、events、file_history。
|
||||
- 在结束时持久化 session。
|
||||
|
||||
关键函数和位置:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:158 class LocalCodingAgent
|
||||
src/agent_runtime.py:195 __post_init__
|
||||
src/agent_runtime.py:413 run(...)
|
||||
src/agent_runtime.py:441 resume(...)
|
||||
src/agent_runtime.py:520 _run_prompt 主链路
|
||||
src/agent_runtime.py:552 tool_specs = [tool.to_openai_tool() ...]
|
||||
src/agent_runtime.py:619 for turn_index in range(...)
|
||||
src/agent_runtime.py:1008 遍历模型返回的 tool_calls
|
||||
src/agent_runtime.py:1490 _query_model(...)
|
||||
```
|
||||
|
||||
### 3.3 系统提示词
|
||||
|
||||
主要文件:
|
||||
|
||||
```text
|
||||
src/agent_prompting.py
|
||||
```
|
||||
|
||||
关键职责:
|
||||
|
||||
- 定义中控 Agent 身份。
|
||||
- 注入工具使用策略。
|
||||
- 注入工作空间边界。
|
||||
- 注入 Skill 列表。
|
||||
- 注入 ask_user 等 runtime 指导。
|
||||
|
||||
关键函数和位置:
|
||||
|
||||
```text
|
||||
src/agent_prompting.py:135 get_intro_section
|
||||
src/agent_prompting.py:155 get_doing_tasks_section
|
||||
src/agent_prompting.py:182 get_actions_section
|
||||
src/agent_prompting.py:196 get_workspace_boundary_section
|
||||
src/agent_prompting.py:210 get_using_your_tools_section
|
||||
src/agent_prompting.py:274 get_skill_guidance_section
|
||||
src/agent_prompting.py:414 get_ask_user_guidance_section
|
||||
```
|
||||
|
||||
## 4. 基座的分层职责
|
||||
|
||||
```text
|
||||
Web UI
|
||||
负责交互、展示、文件面板、活动区、Skill 勾选、管理后台。
|
||||
|
||||
Backend API
|
||||
负责账号、会话、模型配置、运行态、服务端事件流。
|
||||
|
||||
Agent Runtime
|
||||
负责 Agent loop、模型调用、工具调用、预算和持久化。
|
||||
|
||||
Prompting
|
||||
负责把规则、工具、公约、Skill 列表转成模型可见上下文。
|
||||
|
||||
Tool Runtime
|
||||
负责稳定执行动作,并把结果结构化回传给模型。
|
||||
|
||||
Skill System
|
||||
负责让业务流程、知识、脚本可被 Agent 发现和使用。
|
||||
|
||||
Workspace
|
||||
负责隔离每个用户和每个会话的输入、临时文件和交付产物。
|
||||
|
||||
Memory Worker
|
||||
负责从交互历史中异步整理长期偏好和 Skill 使用经验。
|
||||
```
|
||||
|
||||
## 5. 设计边界
|
||||
|
||||
基座只应该承载跨业务复用的稳定能力,例如:
|
||||
|
||||
- 工具执行。
|
||||
- 会话状态。
|
||||
- 运行态事件。
|
||||
- 工作区路径。
|
||||
- Skill 发现和启用。
|
||||
- 模型适配。
|
||||
- 记忆后台。
|
||||
|
||||
业务流程不应该写死在基座里。数据生成、线上挖掘、标签判断等变化快的流程应该沉到 Skill;确定性脚本应该放在对应 Skill 的 `scripts/` 下。
|
||||
@@ -0,0 +1,246 @@
|
||||
# 02. Agent Loop 执行机制
|
||||
|
||||

|
||||
|
||||
## 1. Agent Loop 的基本形态
|
||||
|
||||
Agent 每轮不是只调用一次模型,而是一个循环:
|
||||
|
||||
```text
|
||||
用户输入
|
||||
-> 构造 session 和 prompt
|
||||
-> 调模型
|
||||
-> 模型返回 assistant text 或 tool_calls
|
||||
-> 如果没有 tool_calls:输出最终回复,结束
|
||||
-> 如果有 tool_calls:执行工具
|
||||
-> 工具结果写回 session
|
||||
-> 下一轮模型继续读取工具结果
|
||||
-> 直到最终回复、预算超限、取消、max_turns 或 review 停止
|
||||
```
|
||||
|
||||
这个循环允许 Agent 做“观察-执行-再观察”的任务,例如:
|
||||
|
||||
- 先读文件,再决定是否需要抽取。
|
||||
- 先生成 plan,等待用户 review。
|
||||
- 先运行脚本,再根据校验结果修复。
|
||||
- 先查询线上数据,再抽样展示。
|
||||
|
||||
## 2. 新会话和恢复会话
|
||||
|
||||
新会话入口:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:413 run(...)
|
||||
```
|
||||
|
||||
关键动作:
|
||||
|
||||
```text
|
||||
1. 清理当前 managed_agent_id 和 resume_source_session_id。
|
||||
2. 创建 session_id。
|
||||
3. 创建 scratchpad_directory。
|
||||
4. 绑定 plan_runtime/task_runtime 到 scratchpad。
|
||||
5. 调 _run_prompt(...)
|
||||
6. 累计 usage。
|
||||
```
|
||||
|
||||
恢复会话入口:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:441 resume(...)
|
||||
```
|
||||
|
||||
关键动作:
|
||||
|
||||
```text
|
||||
1. 从 StoredAgentSession 恢复 AgentSessionState。
|
||||
2. 回放 file_history 和 compaction 信息。
|
||||
3. 设置 active_session_id 和 last_session_path。
|
||||
4. 恢复 plugin state。
|
||||
5. 复用已有 scratchpad_directory。
|
||||
6. 调 _run_prompt(...)
|
||||
```
|
||||
|
||||
这解释了为什么刷新页面、回到旧会话后,Agent 理论上可以继续同一个 session 的上下文,而不是新建一个任务。
|
||||
|
||||
## 3. `_run_prompt` 主链路
|
||||
|
||||
核心位置:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:520 _run_prompt 主体
|
||||
```
|
||||
|
||||
主链路关键步骤:
|
||||
|
||||
```text
|
||||
1. slash command 预处理。
|
||||
2. hook policy / plugin hook 修改 prompt。
|
||||
3. agent_manager.start_agent(...) 记录运行。
|
||||
4. 新建或复用 AgentSessionState。
|
||||
5. runtime_context prepend 到模型可见的用户消息。
|
||||
6. session.append_user(...)。
|
||||
7. tool_context 注入 scratchpad、plan_runtime、task_runtime。
|
||||
8. 生成 tool_specs。
|
||||
9. 初始化 usage、cost、tool_calls、events。
|
||||
10. 进入 turn loop。
|
||||
```
|
||||
|
||||
对应代码点:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:523 agent_manager.start_agent(...)
|
||||
src/agent_runtime.py:531 session = base_session or build_session(...)
|
||||
src/agent_runtime.py:539 _prepend_runtime_context(...)
|
||||
src/agent_runtime.py:543 session.append_user(...)
|
||||
src/agent_runtime.py:546 replace(self.tool_context, scratchpad_directory=...)
|
||||
src/agent_runtime.py:552 tool_specs = [tool.to_openai_tool() ...]
|
||||
src/agent_runtime.py:582 stream_events = _RuntimeEventBuffer(event_sink)
|
||||
src/agent_runtime.py:619 for turn_index in range(...)
|
||||
```
|
||||
|
||||
## 4. 模型调用和 tool_calls 判断
|
||||
|
||||
模型调用入口:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1490 _query_model(...)
|
||||
```
|
||||
|
||||
非流式路径中:
|
||||
|
||||
```text
|
||||
turn = self.client.complete(
|
||||
session.to_openai_messages(),
|
||||
tool_specs,
|
||||
output_schema=...
|
||||
)
|
||||
```
|
||||
|
||||
模型能否返回 `tool_calls` 取决于:
|
||||
|
||||
- 当前 `messages`。
|
||||
- 系统提示词。
|
||||
- Skill 列表。
|
||||
- 工具描述和参数 schema。
|
||||
- 模型自身 tool calling 能力。
|
||||
|
||||
基座不会强制某个工具被调用。基座只把工具能力暴露给模型,并在模型返回 `tool_calls` 后负责执行。
|
||||
|
||||
## 5. 没有 tool_calls 时
|
||||
|
||||
如果模型本轮没有工具调用:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:763 if not turn.tool_calls
|
||||
```
|
||||
|
||||
后续可能有三种情况:
|
||||
|
||||
1. 直接输出最终回复。
|
||||
2. 如果模型输出被截断,自动追加 continuation prompt。
|
||||
3. 如果达到 continuation 限制,则追加提示并结束。
|
||||
|
||||
最终会:
|
||||
|
||||
```text
|
||||
session.append_assistant(...)
|
||||
_append_final_text_stream_events(...)
|
||||
AgentRunResult(...)
|
||||
_persist_session(...)
|
||||
```
|
||||
|
||||
## 6. 有 tool_calls 时
|
||||
|
||||
如果模型返回工具调用:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1008 for tool_call in turn.tool_calls
|
||||
```
|
||||
|
||||
每个工具调用会:
|
||||
|
||||
```text
|
||||
1. tool_calls 计数 +1。
|
||||
2. 检查预算。
|
||||
3. session.start_tool(...) 写入工具开始消息。
|
||||
4. stream_events 追加 tool_start。
|
||||
5. 根据工具名执行 handler。
|
||||
6. 工具结果写回 session。
|
||||
7. stream_events 追加工具结果。
|
||||
8. 下一轮模型读取工具结果继续判断。
|
||||
```
|
||||
|
||||
关键代码点:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1054 session.start_tool(...)
|
||||
src/agent_runtime.py:1060 stream_events.append(type='tool_start')
|
||||
src/agent_tools.py:60 execute_tool(...)
|
||||
src/agent_tools.py:76 execute_tool_streaming(...)
|
||||
```
|
||||
|
||||
## 7. 为什么 review 可以暂停流程
|
||||
|
||||
review 门禁不是特殊 UI 魔法,而是 Agent loop 的自然结果:
|
||||
|
||||
1. Skill 要求模型在某一步调用 review 工具。
|
||||
2. 工具创建 pending state,并返回需要展示的信息。
|
||||
3. Agent 生成回复,告诉用户需要确认。
|
||||
4. 当前 run 结束。
|
||||
5. 用户下一轮回复“确认”。
|
||||
6. Agent resume 旧 session,调用 confirm 工具。
|
||||
7. 后续流程继续。
|
||||
|
||||
例如 `product-data`:
|
||||
|
||||
```text
|
||||
data_agent_prepare_generation_goal
|
||||
-> pending goal
|
||||
-> 停止等待用户确认
|
||||
|
||||
data_agent_confirm_generation_goal
|
||||
-> confirmed_goal_id
|
||||
-> data_agent_prepare_generation_plan
|
||||
-> pending plan
|
||||
-> 停止等待用户确认
|
||||
|
||||
data_agent_confirm_generation_plan
|
||||
-> confirmed_plan_id
|
||||
-> 允许生成 draft 和转换 records
|
||||
```
|
||||
|
||||
## 8. 预算、取消和 max_turns
|
||||
|
||||
基座会在模型调用前后、工具请求前检查预算:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:592 initial_budget = self._check_budget(...)
|
||||
src/agent_runtime.py:732 budget_after_model = self._check_budget(...)
|
||||
src/agent_runtime.py:1014 budget_after_tool_request = self._check_budget(...)
|
||||
```
|
||||
|
||||
如果达到最大轮次:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:1440 _build_max_turns_output(...)
|
||||
```
|
||||
|
||||
输出会包含最后一次阶段说明,提醒用户可以继续补充指令。
|
||||
|
||||
取消由后端 run manager 和 tool process registry 处理。Web 点停止后,后端将 run 标记取消,并让工具执行上下文感知 cancel_event。
|
||||
|
||||
## 9. 事件流和前端活动区
|
||||
|
||||
Agent loop 内会不断追加 `stream_events`,后端用 `event_sink` 把事件推给前端。
|
||||
|
||||
后端关键代码:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1751 emit_agent_event(event)
|
||||
backend/api/server.py:1756 state.run_manager.record_event(...)
|
||||
backend/api/server.py:1758 state.run_state_store.record_event(...)
|
||||
backend/api/server.py:2034 StreamingResponse(..., media_type='application/x-ndjson')
|
||||
```
|
||||
|
||||
前端活动区展示的阶段说明、工具开始、工具完成、最终文本,本质上都来自这些 runtime events 或 session replay。
|
||||
@@ -0,0 +1,298 @@
|
||||
# 03. 工具体系和 Tool Handler
|
||||
|
||||

|
||||
|
||||
## 1. 工具体系的职责
|
||||
|
||||
Tool 是执行层。模型只能“请求调用工具”,不能直接执行工具。
|
||||
后端根据工具名找到 handler,校验参数,执行动作,并把结果写回 session。
|
||||
|
||||
工具适合承载:
|
||||
|
||||
- 稳定文件读写。
|
||||
- Python 执行。
|
||||
- Python 包安装。
|
||||
- 数据格式转换和校验。
|
||||
- review 状态机。
|
||||
- 外部系统访问。
|
||||
- 需要权限、取消、路径约束或审计的动作。
|
||||
|
||||
## 2. Tool registry 构成
|
||||
|
||||
入口:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:105 default_tool_registry()
|
||||
```
|
||||
|
||||
它会合并多类工具:
|
||||
|
||||
```text
|
||||
build_file_tools(...)
|
||||
build_execution_tools(...)
|
||||
LSP / web_fetch / search / account / config / task / team / workflow 等
|
||||
Skill 工具
|
||||
data_agent 工具
|
||||
```
|
||||
|
||||
关键片段:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:107 build_file_tools
|
||||
src/agent_tools.py:116 build_execution_tools
|
||||
src/agent_tools.py:1012 AgentTool(name='Skill')
|
||||
src/agent_tools.py:1033 build_data_agent_tools
|
||||
```
|
||||
|
||||
返回结果是:
|
||||
|
||||
```python
|
||||
return {tool.name: tool for tool in tools}
|
||||
```
|
||||
|
||||
## 3. Tool spec 注入模型
|
||||
|
||||
在 Agent loop 中:
|
||||
|
||||
```text
|
||||
src/agent_runtime.py:552
|
||||
tool_specs = [tool.to_openai_tool() for tool in self.tool_registry.values()]
|
||||
```
|
||||
|
||||
每个工具包含:
|
||||
|
||||
```text
|
||||
name
|
||||
description
|
||||
parameters JSON schema
|
||||
handler
|
||||
```
|
||||
|
||||
模型看到的是 name、description、parameters。handler 不暴露给模型,只在后端执行。
|
||||
|
||||
## 4. Tool handler 执行路径
|
||||
|
||||
工具执行入口:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:60 execute_tool(...)
|
||||
src/agent_tools.py:76 execute_tool_streaming(...)
|
||||
```
|
||||
|
||||
执行逻辑:
|
||||
|
||||
```text
|
||||
1. 从 tool_registry 取 tool。
|
||||
2. 找不到则返回 Unknown tool。
|
||||
3. bash 走 streaming。
|
||||
4. 其他工具调用 tool.execute(arguments, context)。
|
||||
5. 工具结果序列化回模型和前端。
|
||||
```
|
||||
|
||||
## 5. 文件工具
|
||||
|
||||
定义位置:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/files.py
|
||||
```
|
||||
|
||||
主要工具:
|
||||
|
||||
- `list_dir`
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `notebook_edit`
|
||||
- `glob_search`
|
||||
- `grep_search`
|
||||
|
||||
### 5.1 `write_file` 的边界
|
||||
|
||||
`write_file` 当前明确定位为“小文件工具”:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/files.py
|
||||
Write or append a SMALL UTF-8 file inside the workspace.
|
||||
```
|
||||
|
||||
适合:
|
||||
|
||||
- 短 Markdown。
|
||||
- 少量配置。
|
||||
- 小 JSON。
|
||||
- 少量 JSONL。
|
||||
- 小 CSV。
|
||||
|
||||
不适合:
|
||||
|
||||
- 长 Python 脚本。
|
||||
- 大 JSON。
|
||||
- JSONL 数据集。
|
||||
- 长 CSV。
|
||||
- 大段包含引号和换行的内容。
|
||||
|
||||
原因是模型生成工具参数时,需要把内容嵌入 JSON 参数。长文本、引号、换行会显著增加非法 JSON 参数概率。
|
||||
|
||||
因此系统提示词也要求:
|
||||
|
||||
```text
|
||||
复杂写文件优先用 python_exec 通过 pathlib/json/csv 写入。
|
||||
```
|
||||
|
||||
## 6. Python 执行工具
|
||||
|
||||
定义位置:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/execution.py
|
||||
```
|
||||
|
||||
主要工具:
|
||||
|
||||
- `python_exec`
|
||||
- `python_package`
|
||||
- `bash`
|
||||
- `sleep`
|
||||
|
||||
### 6.1 `python_exec`
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
结构化文件分析、JSON/JSONL 处理、批量校验、数据抽样、快速计算。
|
||||
```
|
||||
|
||||
支持两种模式:
|
||||
|
||||
```text
|
||||
code
|
||||
一次性 Python 代码。
|
||||
|
||||
script_path
|
||||
调用项目或 Skill 内已有脚本。
|
||||
```
|
||||
|
||||
重要约束:
|
||||
|
||||
- 不用 `bash python ...`。
|
||||
- 不在 `python_exec.code` 里用 subprocess 二次调用 Python 脚本。
|
||||
- 临时文件写入 `PYTHON_EXEC_SCRATCHPAD`。
|
||||
- 交付产物写入 session/output。
|
||||
|
||||
### 6.2 `python_package`
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
给当前用户独立 Python venv 安装缺失包。
|
||||
```
|
||||
|
||||
典型场景:
|
||||
|
||||
- `pandas`
|
||||
- `pyarrow`
|
||||
- `openpyxl`
|
||||
- `elasticsearch`
|
||||
- `urllib3`
|
||||
|
||||
设计上避免安装到项目 `.venv` 或系统 Python。
|
||||
|
||||
### 6.3 `bash`
|
||||
|
||||
`bash` 是兜底工具,不是默认 Python 执行方式。
|
||||
|
||||
适合:
|
||||
|
||||
- git 只读检查。
|
||||
- 系统命令。
|
||||
- 进程控制。
|
||||
- 用户确认后的依赖安装或服务管理。
|
||||
|
||||
不适合:
|
||||
|
||||
- Python 数据分析。
|
||||
- JSON/CSV 转换。
|
||||
- 包安装。
|
||||
|
||||
## 7. 路径解析和 session 路由
|
||||
|
||||
关键实现:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1589 _data_agent_output_path(...)
|
||||
src/agent_tools.py:1624 _data_agent_session_output_root(...)
|
||||
src/agent_tools.py:1630 _resolve_path(...)
|
||||
src/agent_tools.py:1658 _session_logical_path(...)
|
||||
src/agent_tools.py:1685 _execution_cwd(...)
|
||||
```
|
||||
|
||||
逻辑路径会被映射到当前 session:
|
||||
|
||||
```text
|
||||
output/... -> session/output/...
|
||||
outputs/... -> session/output/...
|
||||
scratchpad/... -> session/scratchpad/...
|
||||
scratch/... -> session/scratchpad/...
|
||||
input/... -> session/input/...
|
||||
inputs/... -> session/input/...
|
||||
```
|
||||
|
||||
`python_exec` 的执行 cwd 优先是当前会话 scratchpad:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1685 _execution_cwd(...)
|
||||
```
|
||||
|
||||
## 8. data_agent 工具
|
||||
|
||||
声明位置:
|
||||
|
||||
```text
|
||||
src/agent_tool_specs/data_agent.py
|
||||
```
|
||||
|
||||
handler 注册位置:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1033 build_data_agent_tools(...)
|
||||
```
|
||||
|
||||
主要工具分两类:
|
||||
|
||||
### 8.1 前链路和 review 状态机
|
||||
|
||||
- `data_agent_load_input_sources`
|
||||
- `data_agent_render_source_context`
|
||||
- `data_agent_extract_case_evidence`
|
||||
- `data_agent_prepare_generation_goal`
|
||||
- `data_agent_confirm_generation_goal`
|
||||
- `data_agent_prepare_generation_plan`
|
||||
- `data_agent_show_generation_plan`
|
||||
- `data_agent_update_generation_plan`
|
||||
- `data_agent_confirm_generation_plan`
|
||||
|
||||
这类工具当前仍属于平台工具,因为它们维护 pending/confirmed 状态,以及产品数据生成的 review 门禁。
|
||||
|
||||
### 8.2 历史格式转换工具
|
||||
|
||||
- `data_agent_normalize_dataset_draft`
|
||||
- `data_agent_validate_dataset_records`
|
||||
- `data_agent_export_dataset_records`
|
||||
- `data_agent_export_training_jsonl`
|
||||
- `data_agent_export_planning_eval_csv`
|
||||
|
||||
这部分能力已经逐步迁移到 `skills/product-data/scripts/`,平台工具更多是历史兼容和适配层。
|
||||
|
||||
## 9. 工具设计原则
|
||||
|
||||
当前工具体系的技术取舍:
|
||||
|
||||
```text
|
||||
模型负责决策。
|
||||
工具负责执行。
|
||||
Skill 负责流程经验。
|
||||
脚本负责可迁移确定性能力。
|
||||
```
|
||||
|
||||
工具描述要足够明确,否则模型会选错工具;参数 schema 要尽量简单,否则不同模型后端可能不兼容。
|
||||
@@ -0,0 +1,256 @@
|
||||
# 04. Skill 体系和能力包约定
|
||||
|
||||

|
||||
|
||||
## 1. Skill 的定位
|
||||
|
||||
Skill 是经验层。它不是单纯 prompt,也不是单纯脚本。
|
||||
|
||||
一个 Skill 应该回答:
|
||||
|
||||
- 什么场景触发。
|
||||
- 输入材料是什么。
|
||||
- Agent 应该按什么流程做。
|
||||
- 哪些地方必须让用户 review。
|
||||
- 应该调用哪些工具或脚本。
|
||||
- 产物应该写到哪里。
|
||||
- 哪些做法是禁止的。
|
||||
|
||||
稳定可执行逻辑不应该长期写在 Skill 文本里,而应该进入:
|
||||
|
||||
```text
|
||||
skills/<skill-name>/scripts/
|
||||
```
|
||||
|
||||
或者平台级工具。
|
||||
|
||||
## 2. Skill loader 实现
|
||||
|
||||
关键文件:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py
|
||||
```
|
||||
|
||||
项目级 Skill 目录:
|
||||
|
||||
```text
|
||||
skills/<skill-name>/SKILL.md
|
||||
```
|
||||
|
||||
核心数据结构:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:28 BundledSkill
|
||||
```
|
||||
|
||||
字段:
|
||||
|
||||
```text
|
||||
name
|
||||
description
|
||||
when_to_use
|
||||
aliases
|
||||
allowed_tools
|
||||
user_invocable
|
||||
source
|
||||
path
|
||||
get_prompt
|
||||
```
|
||||
|
||||
## 3. SKILL.md 解析
|
||||
|
||||
解析逻辑:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:147 _parse_front_matter
|
||||
src/bundled_skills.py:176 _load_directory_skill
|
||||
```
|
||||
|
||||
`SKILL.md` 必须有 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: product-data
|
||||
description: 从产品/标签定义、手写边界规则或示例 query 中提取标签边界...
|
||||
when_to_use: 当用户提供产品定义、标签规则...
|
||||
aliases: definition-data, label-data
|
||||
allowed_tools: read_file, write_file, python_exec
|
||||
---
|
||||
```
|
||||
|
||||
解析后:
|
||||
|
||||
- frontmatter 进入 `BundledSkill` 元数据。
|
||||
- body 作为真正的 Skill prompt。
|
||||
- 如果调用 Skill 时带 args,会追加到 `## Invocation Arguments`。
|
||||
|
||||
对应实现:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:138 _directory_skill_prompt
|
||||
```
|
||||
|
||||
## 4. Skill 发现顺序
|
||||
|
||||
项目 Skill 发现入口:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:199 load_directory_skills
|
||||
src/bundled_skills.py:215 load_project_skills
|
||||
```
|
||||
|
||||
系统提示词中可见 Skill 列表由:
|
||||
|
||||
```text
|
||||
src/bundled_skills.py:270 format_skills_for_system_prompt
|
||||
```
|
||||
|
||||
生成。
|
||||
|
||||
Web 后端会按当前账号和 session 配置计算启用 Skill:
|
||||
|
||||
```text
|
||||
backend/api/server.py:691 enabled_skill_names
|
||||
backend/api/server.py:706 set_skill_enabled
|
||||
backend/api/server.py:738 set_all_skills_enabled
|
||||
```
|
||||
|
||||
这意味着:
|
||||
|
||||
- Skill 可以存在于项目中,但不一定对某个 session 启用。
|
||||
- 启用状态影响系统提示词里的 Skill 列表。
|
||||
- 被禁用的 Skill 不应该被模型主动选择。
|
||||
|
||||
## 5. Skill 工具
|
||||
|
||||
Skill 本身也是一个工具:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1012 AgentTool(name='Skill')
|
||||
```
|
||||
|
||||
模型调用:
|
||||
|
||||
```json
|
||||
{
|
||||
"skill": "product-data",
|
||||
"args": "用户原始需求或显式参数"
|
||||
}
|
||||
```
|
||||
|
||||
执行后,Skill body 会被加入对话,让模型按 Skill 中的流程继续做任务。
|
||||
|
||||
## 6. Skill 和 Agent Loop 的关系
|
||||
|
||||
Skill 不会替代 Agent loop,而是改变 Agent loop 的下一步决策依据。
|
||||
|
||||
典型模式:
|
||||
|
||||
```text
|
||||
用户提出任务
|
||||
-> 模型从 Skill 列表中选择某个 Skill
|
||||
-> 调用 Skill 工具
|
||||
-> Skill.md 正文进入上下文
|
||||
-> 模型按 Skill 指令调用 read_file/python_exec/data_agent 等工具
|
||||
-> 工具结果进入上下文
|
||||
-> 模型继续按 Skill 流程推进
|
||||
```
|
||||
|
||||
因此 Skill 的好坏直接影响:
|
||||
|
||||
- 模型能否召回正确能力。
|
||||
- 是否会过早执行。
|
||||
- 是否能在 review 门禁停下来。
|
||||
- 是否能使用正确工具而不是手写不稳定逻辑。
|
||||
|
||||
## 7. 推荐 Skill 目录结构
|
||||
|
||||
```text
|
||||
skills/<skill-name>/
|
||||
SKILL.md
|
||||
README.md
|
||||
knowledge/
|
||||
scripts/
|
||||
examples/
|
||||
schemas/
|
||||
tools.yaml
|
||||
requirements.txt
|
||||
```
|
||||
|
||||
各部分职责:
|
||||
|
||||
```text
|
||||
SKILL.md
|
||||
运行时入口,写流程、门禁、工具调用方式和禁止事项。
|
||||
|
||||
README.md
|
||||
给维护者看的说明,不一定进入模型上下文。
|
||||
|
||||
knowledge/
|
||||
业务知识、标签规则、字段说明、边界案例。
|
||||
|
||||
scripts/
|
||||
确定性脚本,优先通过 python_exec.script_path 执行。
|
||||
|
||||
examples/
|
||||
示例输入输出,用于回归和讲解。
|
||||
|
||||
schemas/
|
||||
JSON schema 或字段约定。
|
||||
|
||||
tools.yaml
|
||||
描述 portable scripts 如何注册为工具,便于迁移到其他 Agent。
|
||||
|
||||
requirements.txt
|
||||
Skill 脚本的 Python 依赖。
|
||||
```
|
||||
|
||||
## 8. Skill 更新
|
||||
|
||||
Web 后端提供 Skill 更新能力:
|
||||
|
||||
```text
|
||||
backend/api/server.py:765 sync_skills_from_git
|
||||
```
|
||||
|
||||
核心行为:
|
||||
|
||||
```text
|
||||
1. 检查当前目录是否是 git 仓库。
|
||||
2. 检查 tracked 文件是否干净。
|
||||
3. git fetch origin。
|
||||
4. git pull --ff-only origin 当前分支。
|
||||
5. 清理当前账号 agent cache。
|
||||
6. 重新读取 get_bundled_skills。
|
||||
```
|
||||
|
||||
这让新增或修改 Skill 后,不一定需要重启服务才能让 Skill 列表刷新。
|
||||
|
||||
## 9. Skill 设计边界
|
||||
|
||||
适合写在 Skill:
|
||||
|
||||
- 工作流。
|
||||
- 何时提问。
|
||||
- 何时 review。
|
||||
- 哪些工具优先。
|
||||
- 输出位置约定。
|
||||
- 常见失败经验。
|
||||
|
||||
不适合长期写在 Skill:
|
||||
|
||||
- 大段可检索知识。
|
||||
- 复杂代码。
|
||||
- 格式转换。
|
||||
- 查询外部系统的具体实现。
|
||||
- 需要校验的稳定数据结构。
|
||||
|
||||
这些应该分别放到:
|
||||
|
||||
```text
|
||||
knowledge/
|
||||
scripts/
|
||||
schemas/
|
||||
platform tools
|
||||
```
|
||||
@@ -0,0 +1,243 @@
|
||||
# 05. 会话工作区、运行态和记忆
|
||||
|
||||

|
||||
|
||||
## 1. 会话工作区
|
||||
|
||||
每个用户、每个会话都有独立目录:
|
||||
|
||||
```text
|
||||
.port_sessions/accounts/<account_id>/sessions/<session_id>/
|
||||
input/
|
||||
scratchpad/
|
||||
output/
|
||||
session.json
|
||||
```
|
||||
|
||||
目录职责:
|
||||
|
||||
```text
|
||||
input/
|
||||
用户上传或明确提供的输入资料。
|
||||
|
||||
scratchpad/
|
||||
临时脚本、中间文件、抽样缓存、断点记录。
|
||||
|
||||
output/
|
||||
最终交付产物。
|
||||
|
||||
session.json
|
||||
会话消息、工具调用、usage、events、file_history、runtime metadata。
|
||||
```
|
||||
|
||||
## 2. 工作区路径路由
|
||||
|
||||
路径解析实现:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1658 _session_logical_path
|
||||
```
|
||||
|
||||
逻辑路径:
|
||||
|
||||
```text
|
||||
output/... -> session/output/...
|
||||
outputs/... -> session/output/...
|
||||
scratchpad/... -> session/scratchpad/...
|
||||
scratch/... -> session/scratchpad/...
|
||||
input/... -> session/input/...
|
||||
inputs/... -> session/input/...
|
||||
```
|
||||
|
||||
Python 执行 cwd:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1685 _execution_cwd
|
||||
```
|
||||
|
||||
优先使用当前 session scratchpad。这是为了避免临时脚本和缓存污染项目根目录。
|
||||
|
||||
## 3. 平台代码写保护
|
||||
|
||||
相关实现:
|
||||
|
||||
```text
|
||||
src/agent_tools.py:1118 _PLATFORM_READONLY_DIRS
|
||||
src/agent_tools.py:1698 _is_platform_code_path
|
||||
src/agent_tools.py:1708 _ensure_not_platform_code_write
|
||||
```
|
||||
|
||||
当前平台目录:
|
||||
|
||||
```text
|
||||
src
|
||||
backend
|
||||
frontend
|
||||
scripts
|
||||
```
|
||||
|
||||
在数据 Agent 会话中默认视为只读。除非用户明确进入平台开发任务,否则业务任务不应修改平台代码。
|
||||
|
||||
## 4. Run 状态和活动区
|
||||
|
||||
后端在 `/api/chat` 运行时创建 run record:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1720 run_record = state.run_manager.start(...)
|
||||
backend/api/server.py:1722 state.run_state_store.start(...)
|
||||
```
|
||||
|
||||
运行事件通过 `emit_agent_event` 记录:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1751 emit_agent_event
|
||||
backend/api/server.py:1756 run_manager.record_event
|
||||
backend/api/server.py:1758 run_state_store.record_event
|
||||
```
|
||||
|
||||
返回前端:
|
||||
|
||||
```text
|
||||
backend/api/server.py:2034 StreamingResponse
|
||||
```
|
||||
|
||||
前端活动区看到的内容主要来自:
|
||||
|
||||
- `run_started`
|
||||
- `tool_start`
|
||||
- `tool_result`
|
||||
- 模型阶段说明
|
||||
- final text stream events
|
||||
- run finish/error/cancel 状态
|
||||
|
||||
## 5. 会话持久化
|
||||
|
||||
运行结束后,后端序列化结果:
|
||||
|
||||
```text
|
||||
backend/api/server.py:2056 _serialize_run_result
|
||||
backend/api/server.py:2069 _normalize_transcript_entry
|
||||
```
|
||||
|
||||
会话读取:
|
||||
|
||||
```text
|
||||
backend/api/server.py:2094 _serialize_stored_session
|
||||
```
|
||||
|
||||
`agent_runtime` 在多个结束路径都会调用:
|
||||
|
||||
```text
|
||||
_persist_session(session, result)
|
||||
```
|
||||
|
||||
这让刷新后可以恢复:
|
||||
|
||||
- 用户消息。
|
||||
- assistant 文本。
|
||||
- tool_calls。
|
||||
- tool result。
|
||||
- elapsed。
|
||||
- file_history。
|
||||
|
||||
## 6. 记忆体系
|
||||
|
||||
实现位置:
|
||||
|
||||
```text
|
||||
src/personal_memory.py
|
||||
```
|
||||
|
||||
### 6.1 文件和数据库
|
||||
|
||||
```text
|
||||
memory.db
|
||||
user.md
|
||||
skills/<skill-name>.md
|
||||
```
|
||||
|
||||
常量:
|
||||
|
||||
```text
|
||||
src/personal_memory.py:26 MEMORY_DB_FILENAME
|
||||
src/personal_memory.py:27 USER_MEMORY_FILENAME
|
||||
src/personal_memory.py:28 SKILL_MEMORY_DIRNAME
|
||||
```
|
||||
|
||||
### 6.2 注入逻辑
|
||||
|
||||
```text
|
||||
src/personal_memory.py:102 render_injection
|
||||
```
|
||||
|
||||
注入规则:
|
||||
|
||||
```text
|
||||
1. 读取 user.md。
|
||||
2. 根据 enabled_skill_names 读取对应 skills/<skill>.md。
|
||||
3. 拼成 # 个性化记忆。
|
||||
4. 如果用户本轮要求冲突,以本轮要求为准。
|
||||
```
|
||||
|
||||
后端调用:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1739 memory_manager.render_injection(...)
|
||||
```
|
||||
|
||||
### 6.3 入队逻辑
|
||||
|
||||
运行结束后:
|
||||
|
||||
```text
|
||||
backend/api/server.py:1920 memory_manager.enqueue_interaction(...)
|
||||
```
|
||||
|
||||
记忆模块内:
|
||||
|
||||
```text
|
||||
src/personal_memory.py:138 enqueue_interaction
|
||||
src/personal_memory.py:635 detect_memory_signals
|
||||
```
|
||||
|
||||
会检测:
|
||||
|
||||
- 显式记忆词:记住、以后、下次、默认、总是、不要、应该、固定。
|
||||
- 纠错词:不对、不是这样、格式错、之前说过、还是不行。
|
||||
- Skill/工具/流程/格式相关表述。
|
||||
- 工具参数非法 JSON 等工具经验。
|
||||
|
||||
### 6.4 后台整理
|
||||
|
||||
核心逻辑:
|
||||
|
||||
```text
|
||||
src/personal_memory.py:378 _consolidate_events
|
||||
src/personal_memory.py:419 _generate_memory_updates
|
||||
src/personal_memory.py:471 _fallback_memory_updates
|
||||
```
|
||||
|
||||
设计取舍:
|
||||
|
||||
- 主链路不直接生成记忆。
|
||||
- 事件先进入 SQLite 队列。
|
||||
- 后台 worker 批量整理。
|
||||
- LLM 失败时有 fallback 规则。
|
||||
- Markdown 是最终可编辑记忆正文。
|
||||
|
||||
## 7. 可观测性设计
|
||||
|
||||
当前可观测性来自三个层次:
|
||||
|
||||
```text
|
||||
运行态
|
||||
run_manager + run_state_store,支持运行中刷新、停止、恢复活动区。
|
||||
|
||||
会话态
|
||||
session.json,保存完整消息和工具调用。
|
||||
|
||||
产物态
|
||||
session/input、scratchpad、output,文件面板可查看和下载。
|
||||
```
|
||||
|
||||
这个设计让用户不仅看到最终回复,也能看到 Agent 做了什么、文件在哪里、失败在哪个工具或阶段。
|
||||
@@ -0,0 +1,292 @@
|
||||
# 06. product-data Skill 实现
|
||||
|
||||

|
||||
|
||||
## 1. 定位
|
||||
|
||||
`product-data` 是数据开发链路的核心 Skill,负责:
|
||||
|
||||
```text
|
||||
产品定义 / 标签规则 / 手写边界 / 示例 query / badcase
|
||||
-> 输入文本化
|
||||
-> generation goal
|
||||
-> 用户 review
|
||||
-> generation plan
|
||||
-> 用户确认数量、标签、边界、路径
|
||||
-> dataset draft text
|
||||
-> canonical records
|
||||
-> validate
|
||||
-> export records / table / training / eval
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/product-data/SKILL.md
|
||||
skills/product-data/knowledge/
|
||||
skills/product-data/scripts/
|
||||
```
|
||||
|
||||
## 2. Skill 目录结构
|
||||
|
||||
当前 `SKILL.md` 中定义的能力组织:
|
||||
|
||||
```text
|
||||
skills/product-data/
|
||||
SKILL.md
|
||||
tools.yaml
|
||||
requirements.txt
|
||||
knowledge/
|
||||
dataset_draft_v1.md
|
||||
canonical_record_v1.md
|
||||
portable_skill_contract.md
|
||||
schemas/
|
||||
scripts/
|
||||
normalize_dataset_draft.py
|
||||
validate_dataset_records.py
|
||||
export_dataset_records.py
|
||||
export_dataset_table.py
|
||||
export_training_jsonl.py
|
||||
export_planning_eval_csv.py
|
||||
```
|
||||
|
||||
分工:
|
||||
|
||||
```text
|
||||
SKILL.md
|
||||
流程协议、门禁、工具调用顺序、禁止事项。
|
||||
|
||||
knowledge/
|
||||
数据草稿、canonical record 和 portable skill contract。
|
||||
|
||||
scripts/
|
||||
确定性转换、校验和导出。
|
||||
|
||||
data_agent_* 平台工具
|
||||
负责输入文本化和 review 状态机。
|
||||
```
|
||||
|
||||
## 3. 输入类型
|
||||
|
||||
Skill 将输入分成三类:
|
||||
|
||||
```text
|
||||
文件定义型
|
||||
产品定义、标签定义、路由规则、表格、Markdown、CSV。
|
||||
|
||||
手写规则型
|
||||
用户直接描述边界,例如“找附近美食给餐饮服务,导航去某地给地图导航”。
|
||||
|
||||
示例归纳型
|
||||
用户只给 query/example/badcase,需要先归纳边界和标签倾向。
|
||||
```
|
||||
|
||||
三类输入最后统一整理成:
|
||||
|
||||
```text
|
||||
dataset_label
|
||||
target / target_definitions
|
||||
plan_hint
|
||||
coverage
|
||||
exclusions
|
||||
open_questions
|
||||
source_refs
|
||||
complex 规则
|
||||
```
|
||||
|
||||
这一步称为 `generation_goal`。
|
||||
|
||||
## 4. Review 门禁
|
||||
|
||||
`product-data` 有两个门禁模式。
|
||||
|
||||
### 4.1 一次确认模式
|
||||
|
||||
适用:
|
||||
|
||||
- 用户直接给出清晰手写规则。
|
||||
- target 表达明确。
|
||||
- 用户已经希望生成数据。
|
||||
|
||||
链路:
|
||||
|
||||
```text
|
||||
data_agent_prepare_generation_plan(direct_review=true)
|
||||
-> 展示目标 + 数量 + 路径
|
||||
-> 等待“确认,开始生成”
|
||||
```
|
||||
|
||||
### 4.2 两段确认模式
|
||||
|
||||
适用:
|
||||
|
||||
- 用户提供文件、表格、badcase、长文档。
|
||||
- 标签、边界、字段有歧义。
|
||||
- 需要先从资料中抽取 generation goal。
|
||||
|
||||
链路:
|
||||
|
||||
```text
|
||||
data_agent_load_input_sources
|
||||
-> data_agent_render_source_context
|
||||
-> Agent 整理 generation_goal
|
||||
-> data_agent_prepare_generation_goal
|
||||
-> 用户确认目标
|
||||
-> data_agent_confirm_generation_goal
|
||||
-> data_agent_prepare_generation_plan
|
||||
-> 用户确认计划
|
||||
-> data_agent_confirm_generation_plan
|
||||
```
|
||||
|
||||
## 5. 和 Agent Loop 的关系
|
||||
|
||||
`product-data` 明确利用 Agent loop 做分阶段控制。
|
||||
|
||||
```text
|
||||
第一轮:
|
||||
模型选择 product-data
|
||||
调输入工具
|
||||
调 prepare_generation_goal 或 prepare_generation_plan
|
||||
输出 review 信息
|
||||
停止
|
||||
|
||||
第二轮:
|
||||
用户确认目标或计划
|
||||
Agent resume session
|
||||
调 confirm 工具
|
||||
继续下一阶段
|
||||
|
||||
第三轮:
|
||||
用户确认计划
|
||||
Agent 生成 dataset draft
|
||||
每批调用 normalize 脚本
|
||||
调 validate 脚本
|
||||
调 export 脚本
|
||||
输出最终文件路径
|
||||
```
|
||||
|
||||
重点是:确认状态不是靠自由文本记忆,而是由工具维护 `confirmed_goal_id` 和 `confirmed_plan_id`。
|
||||
|
||||
## 6. 数据格式设计
|
||||
|
||||
### 6.1 dataset draft text
|
||||
|
||||
面向模型生成,要求模型用较低结构负担描述:
|
||||
|
||||
- 用户 / 小爱 对话。
|
||||
- 当前 query。
|
||||
- target。
|
||||
- complex。
|
||||
- 场景说明。
|
||||
|
||||
设计目标是降低模型直接写 JSONL 的难度。
|
||||
|
||||
### 6.2 canonical record
|
||||
|
||||
面向工具处理,字段稳定。
|
||||
|
||||
用于:
|
||||
|
||||
- 校验结构。
|
||||
- 导出流转 CSV。
|
||||
- 导出训练 JSONL。
|
||||
- 导出评测 planningPrompt CSV。
|
||||
|
||||
### 6.3 complex 独立维度
|
||||
|
||||
`complex` 不属于 target。
|
||||
|
||||
内部保存:
|
||||
|
||||
```text
|
||||
complex: false
|
||||
target: Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
训练输出组合:
|
||||
|
||||
```text
|
||||
complex=false
|
||||
Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
评测 CSV 里:
|
||||
|
||||
```text
|
||||
code标签: Agent(tag="地图导航")
|
||||
complex: FALSE
|
||||
```
|
||||
|
||||
## 7. 脚本链路
|
||||
|
||||
生成式数据确认后,Skill 要求使用 `python_exec.script_path` 调脚本。
|
||||
|
||||
典型顺序:
|
||||
|
||||
```text
|
||||
normalize_dataset_draft.py
|
||||
输入 draft + confirmed_plan_id
|
||||
输出/追加 scratchpad/normalized_records.jsonl
|
||||
|
||||
validate_dataset_records.py
|
||||
读取 normalized_records.jsonl
|
||||
校验字段、target、complex、时间戳、多轮上下文
|
||||
|
||||
export_dataset_records.py
|
||||
导出 output/records.jsonl
|
||||
同时生成 output/records.csv
|
||||
|
||||
export_training_jsonl.py
|
||||
导出 output/training.jsonl
|
||||
|
||||
export_planning_eval_csv.py
|
||||
导出 output/eval_planning.csv
|
||||
```
|
||||
|
||||
为什么不用 `write_file`:
|
||||
|
||||
- records、CSV、JSONL 都是强格式数据。
|
||||
- 模型手写容易出错。
|
||||
- 脚本能统一 timestamp、prev_session、context、complex/target 组合。
|
||||
|
||||
## 8. 输出约束
|
||||
|
||||
固定逻辑输出:
|
||||
|
||||
```text
|
||||
output/records.jsonl
|
||||
output/records.csv
|
||||
output/training.jsonl
|
||||
output/eval_planning.csv
|
||||
```
|
||||
|
||||
通过路径路由,实际写入:
|
||||
|
||||
```text
|
||||
.port_sessions/accounts/<account>/sessions/<session>/output/
|
||||
```
|
||||
|
||||
Skill 明确禁止:
|
||||
|
||||
- 按数据集名创建随机子目录。
|
||||
- 把 records 写到项目根目录。
|
||||
- 用 `write_file` 手写最终 JSONL/CSV。
|
||||
- 在未确认 plan 前生成数据。
|
||||
|
||||
## 9. 设计边界
|
||||
|
||||
`product-data` 负责:
|
||||
|
||||
- 数据目标对齐。
|
||||
- 生成计划 review。
|
||||
- dataset draft 生成协议。
|
||||
- canonical records 转换和导出。
|
||||
|
||||
不负责:
|
||||
|
||||
- 判断所有标签知识。
|
||||
- 查询线上数据。
|
||||
- ELK 检索。
|
||||
- 模型训练或 git 数据仓库提交。
|
||||
|
||||
标签知识应由 `label-master` 辅助,线上数据由 `online-mining-v2` 或相关 Skill 获取。
|
||||
@@ -0,0 +1,177 @@
|
||||
# 07. online-mining-v2 Skill 实现
|
||||
|
||||

|
||||
|
||||
## 1. 定位
|
||||
|
||||
`online-mining-v2` 用于从线上 ELK 日志中挖掘 case,并把候选样本转换成 `product-data` 兼容的标准数据。
|
||||
|
||||
典型链路:
|
||||
|
||||
```text
|
||||
线上挖掘需求
|
||||
-> 明确目标标签、complex、筛选特征
|
||||
-> 探索 ELK 表字段
|
||||
-> 搜索候选
|
||||
-> 抽样 review
|
||||
-> 调整策略
|
||||
-> 直接转 canonical records
|
||||
-> 或转 product-data 生成补数
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/online-mining-v2/SKILL.md
|
||||
skills/online-mining-v2/knowledge/
|
||||
skills/online-mining-v2/scripts/
|
||||
```
|
||||
|
||||
## 2. 能力组织
|
||||
|
||||
`online-mining-v2` 不新增平台注册工具。它把能力放在 Skill 目录下,通过 `python_exec.script_path` 执行。
|
||||
|
||||
```text
|
||||
scripts/
|
||||
online_mining_common.py
|
||||
elk_profile_index.py
|
||||
elk_search_cases.py
|
||||
elk_fetch_by_request_ids.py
|
||||
elk_join_request_logs.py
|
||||
build_dataset_draft.py
|
||||
build_online_records.py
|
||||
```
|
||||
|
||||
这样做的原因:
|
||||
|
||||
- ELK 查询逻辑属于该 Skill 的业务能力。
|
||||
- 迁移到其他 Agent 时可以直接跑脚本。
|
||||
- 平台只需要提供通用 `python_exec` 和 `python_package`。
|
||||
|
||||
## 3. 默认数据源
|
||||
|
||||
当前重点支持两类索引:
|
||||
|
||||
```text
|
||||
pre-processing*
|
||||
前处理日志。
|
||||
适合拿模型 prompt、模型输出、候选 domain、excellent_domains_result。
|
||||
|
||||
arch-flat-nlp-log-f-*
|
||||
主 NLP 日志。
|
||||
适合拿 query、domain、func、request_id、device_id、device、tts/text/to_speak。
|
||||
```
|
||||
|
||||
Skill 文档中会引导 Agent:
|
||||
|
||||
- 先用 `elk_profile_index.py` 看字段和样本。
|
||||
- 再用 `elk_search_cases.py` 根据 query/domain/model output 搜索。
|
||||
- 必要时用 request_id 做双表 join。
|
||||
|
||||
## 4. 和 Agent Loop 的关系
|
||||
|
||||
这个 Skill 的交互不是“一次查询结束”,而是策略迭代:
|
||||
|
||||
```text
|
||||
用户描述需求
|
||||
-> Agent 整理目标标签和筛选特征
|
||||
-> 如缺目标标签或字段,先问用户
|
||||
-> python_exec 调 elk_profile_index.py
|
||||
-> 模型观察字段和样本
|
||||
-> python_exec 调 elk_search_cases.py
|
||||
-> 模型观察候选质量
|
||||
-> 抽样展示给用户 review
|
||||
-> 用户说哪里不对
|
||||
-> 调整 filters / regex / domain / model output 条件
|
||||
-> 再搜索
|
||||
```
|
||||
|
||||
Agent loop 的价值在这里很明显:每次工具结果都会进入上下文,模型可以基于真实候选调整策略。
|
||||
|
||||
## 5. 两条分支
|
||||
|
||||
`online-mining-v2` 最重要的设计是强制区分两个分支。
|
||||
|
||||
### 5.1 直接线上样本分支
|
||||
|
||||
适用:
|
||||
|
||||
```text
|
||||
用户想把线上筛出来的真实 case 作为评测集或专项集。
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
```text
|
||||
不生成新 query。
|
||||
不进入 product-data generation plan。
|
||||
用 build_online_records.py 直接转 canonical records。
|
||||
```
|
||||
|
||||
典型输出:
|
||||
|
||||
```text
|
||||
output/records.jsonl
|
||||
output/records.csv
|
||||
```
|
||||
|
||||
### 5.2 补充生成分支
|
||||
|
||||
适用:
|
||||
|
||||
```text
|
||||
用户想围绕线上问题扩写更多类似 case。
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
```text
|
||||
先分析线上 badcase。
|
||||
整理错误类型和覆盖目标。
|
||||
再转 product-data 的 generation goal / plan / draft / records 流程。
|
||||
```
|
||||
|
||||
这条边界避免了旧流程里出现的错误:用户只是想“把候选转样本”,Agent 却误走“生成新数据”。
|
||||
|
||||
## 6. 和 product-data 的关系
|
||||
|
||||
`online-mining-v2` 后处理复用 `product-data` 的标准:
|
||||
|
||||
```text
|
||||
canonical record v1
|
||||
records.jsonl
|
||||
records.csv
|
||||
training.jsonl
|
||||
eval_planning.csv
|
||||
```
|
||||
|
||||
复用脚本:
|
||||
|
||||
```text
|
||||
skills/product-data/scripts/normalize_dataset_draft.py
|
||||
skills/product-data/scripts/validate_dataset_records.py
|
||||
skills/product-data/scripts/export_dataset_records.py
|
||||
skills/product-data/scripts/export_training_jsonl.py
|
||||
skills/product-data/scripts/export_planning_eval_csv.py
|
||||
```
|
||||
|
||||
因此线上挖掘和产品定义生成最终可以进入同一套数据格式。
|
||||
|
||||
## 7. 设计边界
|
||||
|
||||
`online-mining-v2` 负责:
|
||||
|
||||
- ELK 字段探索。
|
||||
- ELK 条件搜索。
|
||||
- request_id 补全。
|
||||
- 候选样本 review。
|
||||
- 线上候选转 canonical records。
|
||||
|
||||
不负责:
|
||||
|
||||
- 定义 canonical record 标准。
|
||||
- 生成全新补数。
|
||||
- 判断复杂标签知识。
|
||||
- 训练数据提交。
|
||||
|
||||
这些分别交给 `product-data`、`label-master` 或后续数据仓库 Skill。
|
||||
@@ -0,0 +1,231 @@
|
||||
# 08. label-master Skill 实现
|
||||
|
||||

|
||||
|
||||
## 1. 定位
|
||||
|
||||
`label-master` 是标签知识和边界分析 Skill。
|
||||
|
||||
它不把“给 query 打标签”做成一个黑盒工具,而是让 Agent 逐步读取知识、比较候选、解释依据,并在必要时调用脚本校验最终输出格式。
|
||||
|
||||
典型链路:
|
||||
|
||||
```text
|
||||
query / 标签边界问题 / target 校验需求
|
||||
-> 读取决策流程
|
||||
-> 读取候选召回索引
|
||||
-> 找 2-5 个候选标签
|
||||
-> 读取候选标签卡片
|
||||
-> 命中混淆时读取边界卡
|
||||
-> 判断 complex / 多指令 / 自动任务等结构维度
|
||||
-> 判断输出形态
|
||||
-> 给出推荐、候选、依据、排除项和不确定点
|
||||
-> 如要落数据,调用 validate_label_output.py
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/label-master/SKILL.md
|
||||
skills/label-master/knowledge/
|
||||
skills/label-master/scripts/
|
||||
```
|
||||
|
||||
## 2. 知识组织
|
||||
|
||||
核心目录:
|
||||
|
||||
```text
|
||||
knowledge/
|
||||
标签总览.md
|
||||
决策流程.md
|
||||
索引/
|
||||
候选召回索引.md
|
||||
标签索引.md
|
||||
维度索引.md
|
||||
label_manifest.json
|
||||
判断维度/
|
||||
复杂度/
|
||||
多指令/
|
||||
自动任务/
|
||||
标注输出形态.md
|
||||
输出能力/
|
||||
标签/
|
||||
边界/
|
||||
边界索引.md
|
||||
高频混淆/
|
||||
领域概览/
|
||||
迁移记录.md
|
||||
```
|
||||
|
||||
设计原则:
|
||||
|
||||
```text
|
||||
索引先行
|
||||
不直接读全量标签卡。
|
||||
|
||||
维度分离
|
||||
complex、多指令、自动任务不是业务标签。
|
||||
|
||||
标签卡片中文维护
|
||||
方便人工编辑。
|
||||
|
||||
输出能力独立
|
||||
function、intent、object、Agent 包装放在输出能力层。
|
||||
|
||||
边界卡优先人工维护
|
||||
高频混淆写清楚,不只依赖自动迁移总结。
|
||||
```
|
||||
|
||||
## 3. Agent 使用方式
|
||||
|
||||
`label-master` 的关键在于利用 Agent 的多轮阅读和规划能力。
|
||||
|
||||
典型 Agent loop:
|
||||
|
||||
```text
|
||||
模型选择 label-master
|
||||
-> read_file knowledge/决策流程.md
|
||||
-> read_file knowledge/索引/候选召回索引.md
|
||||
-> read_file 候选标签卡片
|
||||
-> read_file 高频混淆边界卡
|
||||
-> 必要时 read_file 判断维度/复杂度/*
|
||||
-> 必要时 read_file 输出能力/*
|
||||
-> 输出可 review 判断
|
||||
```
|
||||
|
||||
为什么不做成单个 `classify_query(query) -> label` 工具:
|
||||
|
||||
- 标签判断常常需要比较候选和排除项。
|
||||
- function / intent / Agent 包装要看迁移状态。
|
||||
- complex、多指令、自动任务是独立维度。
|
||||
- 人类 review 需要看到依据,而不是只看到最终标签。
|
||||
|
||||
因此脚本只做索引和校验,不替代语义判断。
|
||||
|
||||
## 4. 输出形态
|
||||
|
||||
标签知识中存在多种输出形态:
|
||||
|
||||
```text
|
||||
Agent(tag="xxx")
|
||||
function program
|
||||
intent
|
||||
object + function 组合
|
||||
多指令 JSON
|
||||
自动任务 JSON
|
||||
complex=true/false
|
||||
```
|
||||
|
||||
`complex` 是独立维度,不应该混在 target 里。
|
||||
|
||||
例如训练输出可以组合成:
|
||||
|
||||
```text
|
||||
complex=false
|
||||
Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
但内部判断要拆成:
|
||||
|
||||
```text
|
||||
complex: false
|
||||
target: Agent(tag="地图导航")
|
||||
```
|
||||
|
||||
## 5. 脚本能力
|
||||
|
||||
### 5.1 build_label_manifest.py
|
||||
|
||||
用途:
|
||||
|
||||
```text
|
||||
把 Markdown 知识生成机器索引。
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/label-master/scripts/build_label_manifest.py
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```text
|
||||
skills/label-master/knowledge/索引/label_manifest.json
|
||||
```
|
||||
|
||||
使用场景:
|
||||
|
||||
- 新增标签卡。
|
||||
- 修改输出能力。
|
||||
- 修改判断维度。
|
||||
- 修改边界知识。
|
||||
|
||||
### 5.2 validate_label_output.py
|
||||
|
||||
用途:
|
||||
|
||||
```text
|
||||
校验 target/function/intent/Agent/多指令/自动任务输出格式。
|
||||
```
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/label-master/scripts/validate_label_output.py
|
||||
```
|
||||
|
||||
典型调用:
|
||||
|
||||
```text
|
||||
python_exec(script_path="skills/label-master/scripts/validate_label_output.py", args=["--target", "Agent(tag=\"地图导航\")"])
|
||||
```
|
||||
|
||||
批量校验:
|
||||
|
||||
```text
|
||||
--file output/records.jsonl --field target
|
||||
```
|
||||
|
||||
## 6. 和 product-data 的关系
|
||||
|
||||
`product-data` 在需要确认 target 或生成边界数据时,可以读取 `label-master` 的知识。
|
||||
|
||||
分工:
|
||||
|
||||
```text
|
||||
label-master
|
||||
判断标签知识、边界、输出形态、target 合法性。
|
||||
|
||||
product-data
|
||||
做数据生成 review、draft、canonical records、导出。
|
||||
```
|
||||
|
||||
当用户给出模糊标签名时,应该先用 `label-master` 辅助确认:
|
||||
|
||||
```text
|
||||
标签是否存在
|
||||
使用 Agent 包装还是 function
|
||||
complex 默认是什么
|
||||
是否有高频混淆边界
|
||||
```
|
||||
|
||||
再进入 `product-data` 的生成计划。
|
||||
|
||||
## 7. 设计边界
|
||||
|
||||
`label-master` 负责:
|
||||
|
||||
- 标签知识检索。
|
||||
- 候选标签比较。
|
||||
- 边界解释。
|
||||
- 输出形态判断。
|
||||
- target 格式校验。
|
||||
|
||||
不负责:
|
||||
|
||||
- 生成数据集。
|
||||
- 线上日志挖掘。
|
||||
- 导出训练/评测格式。
|
||||
- 直接替用户确认争议边界。
|
||||
@@ -0,0 +1,186 @@
|
||||
# 09. 外部系统 Skill:ELK、SQL、模型迭代
|
||||
|
||||

|
||||
|
||||
## 1. 这类 Skill 的共同特征
|
||||
|
||||
外部系统 Skill 的核心不是复杂 prompt,而是把某个内部系统的调用方式、参数约定、依赖和输出格式打包起来。
|
||||
|
||||
典型形态:
|
||||
|
||||
```text
|
||||
SKILL.md
|
||||
写清楚什么时候用、参数怎么选、哪些动作需要确认。
|
||||
|
||||
scripts/
|
||||
执行真实外部系统调用。
|
||||
|
||||
python_exec
|
||||
作为统一执行入口。
|
||||
|
||||
python_package
|
||||
处理依赖缺失。
|
||||
|
||||
output/
|
||||
查询结果或报表写入当前 session output。
|
||||
```
|
||||
|
||||
## 2. elk-fetch
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/elk-fetch/SKILL.md
|
||||
skills/elk-fetch/elk_query.py
|
||||
```
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
按 request id 或查询条件读取小米内网 ELK 日志。
|
||||
```
|
||||
|
||||
实现方式:
|
||||
|
||||
- `SKILL.md` 写明支持的 profile 和查询方式。
|
||||
- 实际执行必须使用 `python_exec.script_path`。
|
||||
- 缺 `elasticsearch`、`urllib3` 时用 `python_package`。
|
||||
- 不让模型用 bash 手写临时 Python 查询脚本。
|
||||
|
||||
典型价值:
|
||||
|
||||
```text
|
||||
把“怎么查 ELK”这类个人经验变成团队共享能力。
|
||||
```
|
||||
|
||||
和 `online-mining-v2` 的区别:
|
||||
|
||||
```text
|
||||
elk-fetch
|
||||
更偏单次日志查询和调试。
|
||||
|
||||
online-mining-v2
|
||||
更偏批量挖掘、策略迭代、样本转换。
|
||||
```
|
||||
|
||||
## 3. data-factory-sql
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/data-factory-sql/SKILL.md
|
||||
skills/data-factory-sql/run_sql.py
|
||||
```
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
通过 Kyuubi HTTP API 执行数据工场 SQL,轮询状态并下载 CSV 结果。
|
||||
```
|
||||
|
||||
实现方式:
|
||||
|
||||
- 用户直接给 SQL 时,可以确认后执行。
|
||||
- 用户只给分析需求时,Agent 可以先生成 SQL 草稿,再让用户确认。
|
||||
- 执行必须通过 `python_exec.script_path`。
|
||||
- 输出默认写入当前 session output。
|
||||
|
||||
这个 Skill 的门禁重点是:
|
||||
|
||||
```text
|
||||
SQL 执行前确认。
|
||||
控制查询范围和 limit。
|
||||
输出路径清晰。
|
||||
失败时展示错误和可调整建议。
|
||||
```
|
||||
|
||||
## 4. model-iteration
|
||||
|
||||
位置:
|
||||
|
||||
```text
|
||||
skills/model-iteration/SKILL.md
|
||||
skills/model-iteration/scripts/
|
||||
```
|
||||
|
||||
定位:
|
||||
|
||||
```text
|
||||
模型训练、评估、数据准备和迭代流程辅助。
|
||||
```
|
||||
|
||||
这类 Skill 属于混合工程型:
|
||||
|
||||
- 有流程。
|
||||
- 有脚本。
|
||||
- 有配置。
|
||||
- 可能调用外部训练平台。
|
||||
- 高风险动作较多。
|
||||
|
||||
因此它更需要明确:
|
||||
|
||||
```text
|
||||
哪些步骤只是分析。
|
||||
哪些步骤会启动训练。
|
||||
哪些步骤需要用户确认。
|
||||
输出目录和实验记录在哪里。
|
||||
```
|
||||
|
||||
## 5. 外部 Skill 的通用约定
|
||||
|
||||
### 5.1 调用方式
|
||||
|
||||
优先:
|
||||
|
||||
```json
|
||||
{
|
||||
"script_path": "skills/<skill-name>/scripts/run.py",
|
||||
"stdin": "{...}",
|
||||
"timeout_seconds": 120
|
||||
}
|
||||
```
|
||||
|
||||
或者脚本在 Skill 根目录时:
|
||||
|
||||
```json
|
||||
{
|
||||
"script_path": "skills/elk-fetch/elk_query.py",
|
||||
"args": ["..."]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 依赖处理
|
||||
|
||||
依赖缺失时:
|
||||
|
||||
```text
|
||||
python_package(action="install", packages=[...])
|
||||
```
|
||||
|
||||
不要:
|
||||
|
||||
```text
|
||||
bash pip install ...
|
||||
bash python ...
|
||||
```
|
||||
|
||||
### 5.3 输出处理
|
||||
|
||||
外部系统查询结果应该:
|
||||
|
||||
- stdout 给结构化摘要。
|
||||
- 大结果写入 `output/` 或 `scratchpad/`。
|
||||
- 返回实际文件路径。
|
||||
- 避免把大量数据直接塞进最终回复。
|
||||
|
||||
### 5.4 安全和确认
|
||||
|
||||
需要确认的动作:
|
||||
|
||||
- 执行大范围 SQL。
|
||||
- 启动训练。
|
||||
- 写外部路径。
|
||||
- 推送代码或数据仓库。
|
||||
- 导出可能包含敏感字段的线上数据。
|
||||
|
||||
只读、小范围、用户明确指定 rid 或 SQL 的查询,可以直接执行,但仍要控制结果规模。
|
||||
@@ -0,0 +1,59 @@
|
||||
# ZK Data Agent 技术架构说明
|
||||
|
||||
这组文档先服务于技术 review 和后续讲解材料沉淀,不做宣传表达,不做产品比较。
|
||||
每个章节尽量对应当前代码里的真实模块、函数和 Skill 实现,后续 `/doc` 页面可以从这里抽取内容做视觉化呈现。
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
1. [基座 Runtime 架构](01-base-runtime.md)
|
||||
2. [Agent Loop 执行机制](02-agent-loop.md)
|
||||
3. [工具体系和 Tool Handler](03-tools.md)
|
||||
4. [Skill 体系和能力包约定](04-skills.md)
|
||||
5. [会话工作区、运行态和记忆](05-workspace-memory-observability.md)
|
||||
6. [product-data Skill 实现](06-product-data.md)
|
||||
7. [online-mining-v2 Skill 实现](07-online-mining-v2.md)
|
||||
8. [label-master Skill 实现](08-label-master.md)
|
||||
9. [外部系统 Skill:ELK、SQL、模型迭代](09-external-skills.md)
|
||||
|
||||
## 一句话定位
|
||||
|
||||
ZK Data Agent 的基座是一个 Web 化、多用户、可观测的 Agent runtime。
|
||||
业务能力通过 `skills/<skill-name>/SKILL.md`、`knowledge/` 和 `scripts/` 组织;稳定执行能力通过 Tool handler 或 Skill 内 portable scripts 承载;每次运行通过 Agent loop 让模型在“判断、调用工具、观察结果、继续判断”之间循环。
|
||||
|
||||
## 当前技术主线
|
||||
|
||||
```text
|
||||
Web UI
|
||||
-> backend/api/server.py
|
||||
-> LocalCodingAgent
|
||||
-> agent_prompting 组装系统提示词和 Skill 列表
|
||||
-> OpenAICompatClient 调模型
|
||||
-> 模型返回文本或 tool_calls
|
||||
-> agent_tools 执行 handler
|
||||
-> session workspace 保存 input/scratchpad/output/session.json
|
||||
-> run_state_store / event stream 推给前端活动区
|
||||
-> personal_memory 后台异步整理用户记忆和 Skill 记忆
|
||||
```
|
||||
|
||||
## 代码入口速查
|
||||
|
||||
| 主题 | 主要代码 |
|
||||
|------|----------|
|
||||
| Agent runtime | `src/agent_runtime.py` |
|
||||
| 系统提示词 | `src/agent_prompting.py` |
|
||||
| Tool registry / handler | `src/agent_tools.py`、`src/agent_tool_specs/` |
|
||||
| Skill loader | `src/bundled_skills.py` |
|
||||
| 模型兼容层 | `src/openai_compat.py` |
|
||||
| Web 后端 | `backend/api/server.py` |
|
||||
| 会话持久化 | `src/agent_session.py`、`src/session_store.py` |
|
||||
| 记忆后台 | `src/personal_memory.py` |
|
||||
| 数据生成 Skill | `skills/product-data/` |
|
||||
| 线上挖掘 Skill | `skills/online-mining-v2/` |
|
||||
| 标签知识 Skill | `skills/label-master/` |
|
||||
|
||||
## 后续维护原则
|
||||
|
||||
- 如果是在讲“基座怎么运行”,优先改 01-05。
|
||||
- 如果是在讲“某个 Skill 怎么做事”,优先改 06-09。
|
||||
- 如果代码实现发生变化,先更新对应章节,再考虑同步 README 或 `/doc` 页面。
|
||||
- 页面表达、图形素材、演示脚本不写在这里;这里只保留技术事实和设计边界。
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |