diff --git a/docs/technical-architecture/01-base-runtime.md b/docs/technical-architecture/01-base-runtime.md new file mode 100644 index 0000000..825d65e --- /dev/null +++ b/docs/technical-architecture/01-base-runtime.md @@ -0,0 +1,221 @@ +# 01. 基座 Runtime 架构 + +![基座 Runtime 架构](assets/01-base-runtime.png) + +## 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/` 下。 diff --git a/docs/technical-architecture/02-agent-loop.md b/docs/technical-architecture/02-agent-loop.md new file mode 100644 index 0000000..bd17007 --- /dev/null +++ b/docs/technical-architecture/02-agent-loop.md @@ -0,0 +1,246 @@ +# 02. Agent Loop 执行机制 + +![Agent Loop 执行机制](assets/02-agent-loop.png) + +## 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。 diff --git a/docs/technical-architecture/03-tools.md b/docs/technical-architecture/03-tools.md new file mode 100644 index 0000000..6f3eb91 --- /dev/null +++ b/docs/technical-architecture/03-tools.md @@ -0,0 +1,298 @@ +# 03. 工具体系和 Tool Handler + +![工具体系和 Tool Handler](assets/03-tools.png) + +## 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 要尽量简单,否则不同模型后端可能不兼容。 diff --git a/docs/technical-architecture/04-skills.md b/docs/technical-architecture/04-skills.md new file mode 100644 index 0000000..ceaf3fe --- /dev/null +++ b/docs/technical-architecture/04-skills.md @@ -0,0 +1,256 @@ +# 04. Skill 体系和能力包约定 + +![Skill 体系和能力包约定](assets/04-skills.png) + +## 1. Skill 的定位 + +Skill 是经验层。它不是单纯 prompt,也不是单纯脚本。 + +一个 Skill 应该回答: + +- 什么场景触发。 +- 输入材料是什么。 +- Agent 应该按什么流程做。 +- 哪些地方必须让用户 review。 +- 应该调用哪些工具或脚本。 +- 产物应该写到哪里。 +- 哪些做法是禁止的。 + +稳定可执行逻辑不应该长期写在 Skill 文本里,而应该进入: + +```text +skills//scripts/ +``` + +或者平台级工具。 + +## 2. Skill loader 实现 + +关键文件: + +```text +src/bundled_skills.py +``` + +项目级 Skill 目录: + +```text +skills//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.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 +``` diff --git a/docs/technical-architecture/05-workspace-memory-observability.md b/docs/technical-architecture/05-workspace-memory-observability.md new file mode 100644 index 0000000..4255311 --- /dev/null +++ b/docs/technical-architecture/05-workspace-memory-observability.md @@ -0,0 +1,243 @@ +# 05. 会话工作区、运行态和记忆 + +![会话工作区、运行态和记忆](assets/05-workspace-memory-observability.png) + +## 1. 会话工作区 + +每个用户、每个会话都有独立目录: + +```text +.port_sessions/accounts//sessions// + 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/.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/.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 做了什么、文件在哪里、失败在哪个工具或阶段。 diff --git a/docs/technical-architecture/06-product-data.md b/docs/technical-architecture/06-product-data.md new file mode 100644 index 0000000..cfb5096 --- /dev/null +++ b/docs/technical-architecture/06-product-data.md @@ -0,0 +1,292 @@ +# 06. product-data Skill 实现 + +![product-data Skill 实现](assets/06-product-data.png) + +## 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//sessions//output/ +``` + +Skill 明确禁止: + +- 按数据集名创建随机子目录。 +- 把 records 写到项目根目录。 +- 用 `write_file` 手写最终 JSONL/CSV。 +- 在未确认 plan 前生成数据。 + +## 9. 设计边界 + +`product-data` 负责: + +- 数据目标对齐。 +- 生成计划 review。 +- dataset draft 生成协议。 +- canonical records 转换和导出。 + +不负责: + +- 判断所有标签知识。 +- 查询线上数据。 +- ELK 检索。 +- 模型训练或 git 数据仓库提交。 + +标签知识应由 `label-master` 辅助,线上数据由 `online-mining-v2` 或相关 Skill 获取。 diff --git a/docs/technical-architecture/07-online-mining-v2.md b/docs/technical-architecture/07-online-mining-v2.md new file mode 100644 index 0000000..703495b --- /dev/null +++ b/docs/technical-architecture/07-online-mining-v2.md @@ -0,0 +1,177 @@ +# 07. online-mining-v2 Skill 实现 + +![online-mining-v2 Skill 实现](assets/07-online-mining-v2.png) + +## 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。 diff --git a/docs/technical-architecture/08-label-master.md b/docs/technical-architecture/08-label-master.md new file mode 100644 index 0000000..90a749a --- /dev/null +++ b/docs/technical-architecture/08-label-master.md @@ -0,0 +1,231 @@ +# 08. label-master Skill 实现 + +![label-master Skill 实现](assets/08-label-master.png) + +## 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 格式校验。 + +不负责: + +- 生成数据集。 +- 线上日志挖掘。 +- 导出训练/评测格式。 +- 直接替用户确认争议边界。 diff --git a/docs/technical-architecture/09-external-skills.md b/docs/technical-architecture/09-external-skills.md new file mode 100644 index 0000000..f4099c2 --- /dev/null +++ b/docs/technical-architecture/09-external-skills.md @@ -0,0 +1,186 @@ +# 09. 外部系统 Skill:ELK、SQL、模型迭代 + +![外部系统 Skill 接入模式](assets/09-external-skills.png) + +## 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//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 的查询,可以直接执行,但仍要控制结果规模。 diff --git a/docs/technical-architecture/README.md b/docs/technical-architecture/README.md new file mode 100644 index 0000000..a7c3d4d --- /dev/null +++ b/docs/technical-architecture/README.md @@ -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.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` 页面。 +- 页面表达、图形素材、演示脚本不写在这里;这里只保留技术事实和设计边界。 diff --git a/docs/technical-architecture/assets/00-zk-data-agent-architecture.png b/docs/technical-architecture/assets/00-zk-data-agent-architecture.png new file mode 100644 index 0000000..e7c517d Binary files /dev/null and b/docs/technical-architecture/assets/00-zk-data-agent-architecture.png differ diff --git a/docs/technical-architecture/assets/01-base-runtime.png b/docs/technical-architecture/assets/01-base-runtime.png new file mode 100644 index 0000000..51497a4 Binary files /dev/null and b/docs/technical-architecture/assets/01-base-runtime.png differ diff --git a/docs/technical-architecture/assets/02-agent-loop.png b/docs/technical-architecture/assets/02-agent-loop.png new file mode 100644 index 0000000..6f5e991 Binary files /dev/null and b/docs/technical-architecture/assets/02-agent-loop.png differ diff --git a/docs/technical-architecture/assets/03-tools.png b/docs/technical-architecture/assets/03-tools.png new file mode 100644 index 0000000..e15b8b8 Binary files /dev/null and b/docs/technical-architecture/assets/03-tools.png differ diff --git a/docs/technical-architecture/assets/04-skills.png b/docs/technical-architecture/assets/04-skills.png new file mode 100644 index 0000000..51e77cc Binary files /dev/null and b/docs/technical-architecture/assets/04-skills.png differ diff --git a/docs/technical-architecture/assets/05-workspace-memory-observability.png b/docs/technical-architecture/assets/05-workspace-memory-observability.png new file mode 100644 index 0000000..99a376f Binary files /dev/null and b/docs/technical-architecture/assets/05-workspace-memory-observability.png differ diff --git a/docs/technical-architecture/assets/06-product-data.png b/docs/technical-architecture/assets/06-product-data.png new file mode 100644 index 0000000..e737a2a Binary files /dev/null and b/docs/technical-architecture/assets/06-product-data.png differ diff --git a/docs/technical-architecture/assets/07-online-mining-v2.png b/docs/technical-architecture/assets/07-online-mining-v2.png new file mode 100644 index 0000000..e1336cb Binary files /dev/null and b/docs/technical-architecture/assets/07-online-mining-v2.png differ diff --git a/docs/technical-architecture/assets/08-label-master.png b/docs/technical-architecture/assets/08-label-master.png new file mode 100644 index 0000000..db0ac87 Binary files /dev/null and b/docs/technical-architecture/assets/08-label-master.png differ diff --git a/docs/technical-architecture/assets/09-external-skills.png b/docs/technical-architecture/assets/09-external-skills.png new file mode 100644 index 0000000..f8674f3 Binary files /dev/null and b/docs/technical-architecture/assets/09-external-skills.png differ diff --git a/docs/technical-architecture/assets_black/01-base-runtime.png b/docs/technical-architecture/assets_black/01-base-runtime.png new file mode 100644 index 0000000..c400aa5 Binary files /dev/null and b/docs/technical-architecture/assets_black/01-base-runtime.png differ diff --git a/docs/technical-architecture/assets_black/02-agent-loop.png b/docs/technical-architecture/assets_black/02-agent-loop.png new file mode 100644 index 0000000..d420775 Binary files /dev/null and b/docs/technical-architecture/assets_black/02-agent-loop.png differ diff --git a/docs/technical-architecture/assets_black/03-tools.png b/docs/technical-architecture/assets_black/03-tools.png new file mode 100644 index 0000000..9f6672c Binary files /dev/null and b/docs/technical-architecture/assets_black/03-tools.png differ diff --git a/docs/technical-architecture/assets_black/04-skills.png b/docs/technical-architecture/assets_black/04-skills.png new file mode 100644 index 0000000..757bb9b Binary files /dev/null and b/docs/technical-architecture/assets_black/04-skills.png differ diff --git a/docs/technical-architecture/assets_black/05-workspace-memory-observability.png b/docs/technical-architecture/assets_black/05-workspace-memory-observability.png new file mode 100644 index 0000000..5eb03d9 Binary files /dev/null and b/docs/technical-architecture/assets_black/05-workspace-memory-observability.png differ diff --git a/docs/technical-architecture/assets_black/06-product-data.png b/docs/technical-architecture/assets_black/06-product-data.png new file mode 100644 index 0000000..562624b Binary files /dev/null and b/docs/technical-architecture/assets_black/06-product-data.png differ diff --git a/docs/technical-architecture/assets_black/07-online-mining-v2.png b/docs/technical-architecture/assets_black/07-online-mining-v2.png new file mode 100644 index 0000000..e0c4e39 Binary files /dev/null and b/docs/technical-architecture/assets_black/07-online-mining-v2.png differ diff --git a/docs/technical-architecture/assets_black/08-label-master.png b/docs/technical-architecture/assets_black/08-label-master.png new file mode 100644 index 0000000..609131c Binary files /dev/null and b/docs/technical-architecture/assets_black/08-label-master.png differ diff --git a/docs/technical-architecture/assets_black/09-external-skills.png b/docs/technical-architecture/assets_black/09-external-skills.png new file mode 100644 index 0000000..52a6742 Binary files /dev/null and b/docs/technical-architecture/assets_black/09-external-skills.png differ diff --git a/frontend/app/app/doc/[slug]/page.tsx b/frontend/app/app/doc/[slug]/page.tsx new file mode 100644 index 0000000..8bcd8b3 --- /dev/null +++ b/frontend/app/app/doc/[slug]/page.tsx @@ -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({token.slice(1, -1)}); + } else if (token.startsWith("**")) { + nodes.push({token.slice(2, -2)}); + } else { + const href = match[3] ?? ""; + const isExternal = /^https?:\/\//.test(href); + nodes.push( + + {match[2]} + , + ); + } + + 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

{parseInline(text)}

; + if (depth === 2) return

{parseInline(text)}

; + if (depth === 3) return

{parseInline(text)}

; + if (depth === 4) return

{parseInline(text)}

; + if (depth === 5) return
{parseInline(text)}
; + return
{parseInline(text)}
; +} + +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( +
+
{language}
+
+						{codeLines.join("\n")}
+					
+
, + ); + 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[1]}, + ); + 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( + + + + {headers.map((cell) => ( + + ))} + + + + {rows.map((row) => ( + + {row.map((cell) => ( + + ))} + + ))} + +
{parseInline(cell)}
{parseInline(cell)}
, + ); + 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( +
    + {items.map((item) => ( +
  • {parseInline(item)}
  • + ))} +
, + ); + 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( +
    + {items.map((item) => ( +
  1. {parseInline(item)}
  2. + ))} +
, + ); + 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( +
+ {parseInline(quoteLines.join(" "))} +
, + ); + 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( +

{parseInline(paragraphLines.join(" "))}

, + ); + } + + return blocks; +} + +export function generateStaticParams() { + return Object.keys(technicalDocs).map((slug) => ({ slug })); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise { + 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 ( +
+
+ +
+

技术文档

+

{doc.title}

+ + 返回架构总览 + +
+
+ +
+
+ {renderMarkdown(markdown)} +
+
+
+ ); +} diff --git a/frontend/app/app/doc/page.tsx b/frontend/app/app/doc/page.tsx index a31af16..388d9ae 100644 --- a/frontend/app/app/doc/page.tsx +++ b/frontend/app/app/doc/page.tsx @@ -1,589 +1,266 @@ -"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; - 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 环境", "会话隔离"], - }, +type Section = { + id: string; + no: string; + title: string; + 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//SKILL.md", - purpose: "定义 Agent 如何识别任务、何时澄清、何时 review、何时调用脚本。", - agentSees: "流程协议、工具优先级、产物约定、人类确认边界。", - maintainerDoes: "把经验写成可执行步骤,而不是把所有业务细节塞成一大段 prompt。", - }, - knowledge: { - name: "knowledge/", - path: "skills//knowledge/", - purpose: "放业务定义、标签边界、字段说明、判断准则。", - agentSees: "按需读取具体知识文件,用于分析和生成。", - maintainerDoes: "把经常变的业务规则放这里,降低改 SKILL.md 的频率。", - }, - scripts: { - name: "scripts/", - path: "skills//scripts/", - purpose: "放确定性能力:校验、格式转换、导出、数据清洗。", - agentSees: "通过 python_exec 执行脚本,不再现场手写复杂转换逻辑。", - maintainerDoes: "把易错代码沉淀成可测试脚本,输入输出写清楚。", - }, - examples: { - name: "examples/", - path: "skills//examples/", - purpose: "放典型输入输出,帮助 Agent 对齐格式和边界。", - agentSees: "少量高质量样例,比长篇抽象说明更稳。", - maintainerDoes: "新增失败 case 的最小复现,作为回归材料。", - }, - readme: { - name: "README.md", - path: "skills//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 ; - if (value === "partial") return ; - return ; -} - -function ProductComparison() { - const [selected, setSelected] = useState("zk"); - const detail = products[selected]; - const productIds: ProductId[] = ["chatgpt", "cursor", "claude", "zk"]; - +function SectionBlock({ section }: { section: Section }) { return ( -
-
-

先回答最尖锐的问题

-

为什么不用 Claude Code / ChatGPT / Cursor 就够了?

-

- 不是因为我们模型更强,而是因为我们补的是团队业务流程平台:多人入口、共享 Skill、数据源、产物、记忆和管理。 -

+
+
+ {section.no} +
+

{section.title}

+

{section.summary}

+
-
-
- {productIds.map((id) => ( - + {section.image ? ( + + {section.title} + + ) : null} +
+
+

关键机制

+ {section.points.map((point) => ( +

+ {point} +

))}
-
-

当前选中

-

{detail.name}

-
-
- 最适合 -

{detail.bestFor}

-
-
- 边界 -

{detail.limits}

-
-
-
- {detail.tech.map((item) => ( - {item} - ))} -
-
-
-
-
- 能力 - {productIds.map((id) => {products[id].name})} -
- {comparisonRows.map((row) => ( -
- {row.label} - {productIds.map((id) => ( -
- -
- ))} -
- ))} -
-
- ); -} - -function RoleSwitcher() { - const [selected, setSelected] = useState("user"); - const role = roles[selected]; - - return ( -
-
-

融入工作的方式

-

同一个平台,三类人看到的是三条不同路径

-
-
-
- {(Object.keys(roles) as RoleId[]).map((id) => ( - +
+

代码入口

+ {section.code.map((item) => ( + {item} ))} + {section.doc ? ( + + 查看详细文档 + + ) : null}
-
-

{role.title}

-
{role.question}
-

{role.answer}

-
- {role.workflow.map((step) => ( - {step} - ))} -
+
+

设计取舍

+

{section.decision}

-
-

平台证据

- {role.proof.map((item) => ( -
- - {item} -
- ))} -
-
-
- ); -} - -function ObjectiveBoard() { - return ( -
-
-

这页怎么讲清楚系统

-

每个前端模块都必须服务一个表达目标

-
-
- {objectives.map((item, index) => ( -
- {String(index + 1).padStart(2, "0")} -

{item.goal}

-

{item.frontend}

- {item.message} -
- ))} -
-
- ); -} - -function MemoryAtlas() { - const [selected, setSelected] = useState("ours"); - const item = memoryPatterns[selected]; - const order: MemoryId[] = ["chatgpt", "letta", "mem0", "rag", "ours"]; - - return ( -
-
-

记忆方案谱系

-

我们不是闭门造车:先看主流方案,再看为什么这样落地

-

- 记忆不是一个功能开关。我们参考了显式保存、历史参考、状态化 Agent、归档检索、托管记忆层等方案,最后按团队 Skill 场景做了轻量实现。 -

-
-
-
- {order.map((id, index) => ( - - ))} -
-
- -

{item.name}

-
-
它怎么做

{item.source}

-
我们借鉴

{item.borrowed}

-
为什么不够

{item.notEnough}

-
我们的取舍

{item.ourChoice}

-
-
-
-
-
-

当前实现

-

用户记忆 + Skill 记忆 + 异步队列

-
-
- {memoryPipeline.map((step, index) => ( -
- {String(index + 1).padStart(2, "0")} - {step.title} -

{step.text}

- {step.code} -
- ))} -
-
-
- ); -} - -function SkillWorkbench() { - const [selected, setSelected] = useState("skillmd"); - const part = skills[selected]; - const order: SkillPartId[] = ["skillmd", "knowledge", "scripts", "examples", "readme", "update"]; - - return ( -
-
-

Skill 怎么开发,怎么共享,怎么更新

-

把一个人的经验打包成团队能力,不靠口口相传

-
-
-
- {order.map((id) => ( - - ))} -
-
- {part.path} -

{part.name}

-
-
解决什么

{part.purpose}

-
Agent 看到什么

{part.agentSees}

-
维护者做什么

{part.maintainerDoes}

-
-
-
-

一个 Skill 的提交路径

- {["创建目录", "补 SKILL.md", "沉淀脚本/知识", "本地测试", "提交 Git", "页面点更新 Skill"].map((step, index) => ( -
{index + 1}{step}
- ))} -
-
-
- ); -} - -function TaskFlowMap() { - return ( -
-
-

已经能进入工作的场景

-

不是展示功能点,而是展示从输入到产物的链路

-
-
- {taskFlows.map((flow) => ( -
-

{flow.name}

-
Skill{flow.skill}
-
输入

{flow.input}

-
Review 点

{flow.review}

-
- {flow.outputs.map((output) => {output})} -
-
- ))} -
-
- ); -} - -function RuntimeTrace() { - return ( -
-
-

运行时链路

-

从用户按下发送,到文件、活动区和记忆更新

-
-
- {runtimeTrace.map((step, index) => ( -
- {String(index + 1).padStart(2, "0")} - {step.stage} -

{step.text}

- {step.path} -
- ))}
); @@ -591,62 +268,119 @@ function RuntimeTrace() { export default function DocPage() { return ( -
-
-