diff --git a/docs/claude-agent-sdk-cli-startup.md b/docs/claude-agent-sdk-cli-startup.md deleted file mode 100644 index 0df5f6a..0000000 --- a/docs/claude-agent-sdk-cli-startup.md +++ /dev/null @@ -1,365 +0,0 @@ -# Claude Agent SDK 如何启动 Claude Code CLI - -本文基于 `@anthropic-ai/claude-agent-sdk@0.2.123` 的 npm 包内容分析。这个包对应的 `claudeCodeVersion` 是 `2.1.123`。 - -## 结论 - -`@anthropic-ai/claude-agent-sdk` 本质上不是直接在 TypeScript SDK 内调用 Claude Messages API 完成 agent 循环,而是把 Claude Code 作为一个子进程启动起来,然后通过 stdin/stdout 的 `stream-json` 协议和这个子进程通信。 - -也就是说,SDK 的主要职责是: - -- 找到当前平台对应的 Claude Code 可执行文件。 -- 把 `query()` 的 options 转换成 Claude Code CLI 参数。 -- 用 `child_process.spawn()` 启动 Claude Code。 -- 往子进程 stdin 写入用户消息 JSONL。 -- 从子进程 stdout 读取 Claude Code 返回的流式 JSONL。 -- 处理权限回调、hooks、MCP、session store mirror、resume 等 SDK 层能力。 - -真正执行 agent loop、调用模型、调工具、读写文件、跑 Bash 的核心逻辑在 Claude Code CLI 里。 - -## 包结构 - -安装 `@anthropic-ai/claude-agent-sdk` 后,主包里有: - -```text -@anthropic-ai/claude-agent-sdk/ - sdk.mjs - sdk.d.ts - bridge.mjs - assistant.mjs - browser-sdk.js - package.json -``` - -主包还声明了一组平台相关的 optional dependencies,例如: - -```text -@anthropic-ai/claude-agent-sdk-darwin-arm64 -@anthropic-ai/claude-agent-sdk-darwin-x64 -@anthropic-ai/claude-agent-sdk-linux-x64 -@anthropic-ai/claude-agent-sdk-win32-x64 -``` - -在 macOS arm64 上实际安装的是: - -```text -@anthropic-ai/claude-agent-sdk-darwin-arm64/ - claude - package.json -``` - -其中 `claude` 是一个原生 Mach-O arm64 可执行文件,大约 206 MB。SDK 默认会启动这个文件。 - -## query() 的启动链路 - -用户代码通常这样调用: - -```ts -import { query } from '@anthropic-ai/claude-agent-sdk' - -for await (const message of query({ - prompt: 'Hello', - options: { - cwd: process.cwd(), - model: 'claude-sonnet-4-6', - maxTurns: 1, - }, -})) { - console.log(message) -} -``` - -内部链路可以简化为: - -```text -query() - -> 组装 Query 实例 - -> 创建 ProcessTransport - -> 找到 Claude Code 可执行文件 - -> 组装 CLI args 和 env - -> spawn Claude Code 子进程 - -> stdin 写入用户消息 - -> stdout 读取 SDKMessage -``` - -## 如何找到 Claude Code 可执行文件 - -SDK 会先看用户是否传了: - -```ts -options.pathToClaudeCodeExecutable -``` - -如果没有传,它会根据 `process.platform` 和 `process.arch` 去解析 optional dependency 里的可执行文件: - -```text -@anthropic-ai/claude-agent-sdk-${platform}-${arch}/claude -``` - -例如当前机器是 macOS arm64,所以会解析: - -```text -@anthropic-ai/claude-agent-sdk-darwin-arm64/claude -``` - -如果找不到 native binary,SDK 会报错,提示重新安装并保留 optional dependencies,或者显式传入 `pathToClaudeCodeExecutable`。 - -## 启动时传给 CLI 的参数 - -SDK 会把 `query()` 的 options 映射成 CLI 参数。一个最小示例大概会启动: - -```bash -claude \ - --output-format stream-json \ - --verbose \ - --input-format stream-json \ - --max-turns 1 \ - --model claude-sonnet-4-6 \ - --permission-mode plan -``` - -其中几个关键参数是固定的: - -- `--output-format stream-json`:要求 CLI 用流式 JSON 输出。 -- `--input-format stream-json`:要求 CLI 从 stdin 读取流式 JSON 输入。 -- `--verbose`:让 CLI 输出更完整的 SDK message 流。 - -其他 options 会继续映射成对应 CLI 参数: - -```text -model -> --model -maxTurns -> --max-turns -maxBudgetUsd -> --max-budget-usd -permissionMode -> --permission-mode -allowedTools -> --allowedTools -disallowedTools -> --disallowedTools -tools -> --tools -mcpServers -> --mcp-config -resume -> --resume -continue -> --continue -sessionId -> --session-id -systemPrompt/settings/sandbox 等 -> 对应设置或额外参数 -``` - -SDK 支持 `spawnClaudeCodeProcess`,如果传了这个 callback,就不会走默认本地 `spawn()`,而是把下面这些信息交给调用方自己启动: - -```ts -{ - command, - args, - cwd, - env, - signal, -} -``` - -这通常用于把 Claude Code 放进 VM、容器或远程环境里执行。 - -## 环境变量 - -SDK 启动子进程前会复制 `options.env` 或 `process.env`,然后注入一些 SDK 相关变量。 - -常见的有: - -```text -CLAUDE_CODE_ENTRYPOINT=sdk-ts -CLAUDE_AGENT_SDK_VERSION=0.2.123 -``` - -如果启用了某些功能,还会加入额外变量,例如: - -```text -CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true -CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH=1 -CLAUDE_CODE_QUESTION_PREVIEW_FORMAT=... -``` - -认证信息一般通过环境变量或本机 Claude Code 登录状态传给 CLI,例如: - -```text -ANTHROPIC_API_KEY -CLAUDE_CODE_OAUTH_TOKEN -CLAUDE_CONFIG_DIR -``` - -SDK 本身没有在主流程里直接手写 Messages API 请求。它把认证环境传给 Claude Code CLI,由 CLI 去完成实际的模型调用。 - -## stdin/stdout 通信协议 - -SDK 会向 Claude Code 子进程的 stdin 写 JSONL。普通字符串 prompt 会被包装成类似这样的用户消息: - -```json -{"type":"user","session_id":"","message":{"role":"user","content":[{"type":"text","text":"Hello"}]},"parent_tool_use_id":null} -``` - -每条消息后面加换行。 - -Claude Code CLI 的 stdout 也会按行输出 JSON。SDK 逐行读取、`JSON.parse()`,然后 yield 给用户: - -```ts -for await (const message of query(...)) { - // message 来自 CLI stdout 的 stream-json -} -``` - -所以 `query()` 返回的是一个 `AsyncGenerator`。常见 message 类型包括: - -- `system`:初始化、状态变化等。 -- `assistant`:Claude 的 assistant message。 -- `user`:工具结果等用户侧消息。 -- `result`:本轮结束结果。 - -## ProcessTransport 做了什么 - -SDK 内部的 ProcessTransport 负责管理子进程生命周期: - -- 启动 Claude Code 子进程。 -- 保存 stdin/stdout 引用。 -- abort 时发送 `SIGTERM`。 -- close 时先结束 stdin,必要时再终止进程。 -- 监听 `error` 和 `exit`。 -- 把 stdout 的每一行 JSON 解析成 SDK message。 -- 如果进程非 0 退出,转换成 SDK 错误。 - -默认本地启动时使用 Node 的: - -```ts -child_process.spawn(command, args, { - cwd, - stdio: ['pipe', 'pipe', stderrMode], - signal, - env, - windowsHide: true, -}) -``` - -## 权限、hooks、MCP 怎么回传 - -虽然主消息流是 stdout,但 SDK 还会处理一些特殊控制消息: - -- `control_request` -- `control_response` -- `control_cancel_request` -- `transcript_mirror` -- `keep_alive` - -例如设置了 `canUseTool` 时,SDK 会把 CLI 参数设为: - -```text ---permission-prompt-tool stdio -``` - -这表示 Claude Code CLI 遇到工具权限请求时,会通过 stdio 给 SDK 发控制请求。SDK 调用用户传入的 `canUseTool` callback 后,再把 decision 写回 CLI。 - -hooks、SDK MCP server、elicitation 等能力也是类似思路:Claude Code CLI 发控制事件,SDK 在 TypeScript 侧执行 callback,再通过 stdin 回传结果。 - -## SessionStore 的工作方式 - -这个仓库里的 S3、Redis、Postgres 代码都是 `SessionStore` 示例 adapter。它们不是 SDK 启动 Claude 的核心,而是给 SDK 的 session mirror/resume 功能用的。 - -可以把 `SessionStore` 理解成 Claude Code 本地 session 存储之外的“外部 transcript 镜像层”。它最直接的用途确实是 resume,但不只用于 resume。 - -当传入: - -```ts -options: { sessionStore } -``` - -SDK 仍然会让 Claude Code CLI 写本地 transcript,然后开启: - -```text ---session-mirror -``` - -CLI 会发出 `transcript_mirror` frame,SDK 收到后调用: - -```ts -sessionStore.append(key, entries) -``` - -resume 时,如果使用外部 `sessionStore`,SDK 会先把外部存储里的 transcript materialize 成一个临时 `CLAUDE_CONFIG_DIR`,再用 `--resume` 启动 Claude Code CLI。 - -所以外部 store 的作用是“镜像和恢复会话”,不是替代 Claude Code CLI,也不是让 SDK 绕过 Claude Code 自己的本地 session 机制。 - -### Store 除了 resume 还有什么作用 - -`SessionStore` 的核心接口包括: - -```ts -append(key, entries) -load(key) -listSessions?(projectKey) -delete?(key) -listSubkeys?(key) -``` - -这些能力对应几类用途: - -- `append()`:把 Claude Code 本地写入成功后的 transcript 追加镜像到外部系统。 -- `load()`:resume 时从外部系统读回 transcript。 -- `listSessions()`:支持 `continue`,也支持在外部系统里列出某个项目下有哪些 session。 -- `delete()`:让 SDK 的 `deleteSession()` 能删除外部存储里的 session。 -- `listSubkeys()`:恢复或读取 subagent transcript,因为 subagent 会话存储在主 session 下面的 subpath 里。 - -因此,resume 是最主要的消费场景,但 store 也提供了跨机器 session 列表、删除、读取历史消息、读取 subagent 消息、导入本地 session 到外部存储等管理能力。 - -### 为什么 Claude Code 已经存 session,还需要 Store - -Claude Code CLI 本身会把 session 写到本机 `CLAUDE_CONFIG_DIR` 下,默认通常是用户目录里的 `.claude/projects/...`。这对单机 CLI 使用足够,但对 SDK 嵌入场景有几个限制: - -- 本地磁盘只在当前机器可见。服务端多实例、容器、CI、serverless、桌面应用多设备同步时,下一次请求可能不在同一台机器上。 -- 本地 session 生命周期跟机器或容器绑定。容器销毁后,本地 transcript 也可能丢失。 -- 产品侧通常需要统一管理 session,例如按用户、租户、项目做持久化、审计、检索、备份、清理和合规保留。 -- SDK host 可能想让 `CLAUDE_CONFIG_DIR` 指向临时目录,避免长期在运行环境本地落盘,但仍然需要把 session 保存到自己的系统里。 -- Claude Code 的本地 session 格式是 CLI 的运行时存储;外部 store 则是 SDK 暴露出来的可控持久化边界,方便接入 Redis、S3、Postgres 等基础设施。 - -所以这里是双写模型: - -```text -Claude Code CLI - -> 先写本地 transcript - -> 通过 transcript_mirror 通知 SDK - -> SDK 调用 SessionStore.append() 写外部存储 -``` - -resume 时则反过来: - -```text -SessionStore.load() - -> SDK 生成临时 CLAUDE_CONFIG_DIR/projects/.../*.jsonl - -> spawn claude --resume - -> Claude Code CLI 仍然按自己的本地 session 机制恢复 -``` - -这个设计的重点是:不改 Claude Code CLI 的核心 session 读取逻辑,而是在 SDK 层提供一个外部持久化和跨环境恢复的桥。 - -### Store 不是强一致主存储 - -文档里也强调,`append()` 失败会被记录并作为 stream error 暴露,但不会阻塞当前对话。也就是说外部 store 更像 mirror,而不是 Claude Code 执行路径上的强依赖数据库。 - -这有一个重要含义:对话能继续运行,不会因为 Redis/S3/Postgres 短暂失败就直接中断;但如果 mirror 失败,后续从外部 store resume 时可能缺少部分 transcript。因此生产环境需要监控 mirror error,并根据业务要求做重试、告警或补偿。 - -## Direct Connect 是另一条路径 - -SDK 里还有 `DirectConnectTransport`,它不是启动本地 CLI,而是连接远程 Claude Code server: - -- 先 `POST /sessions` 创建 session。 -- 再通过 WebSocket 连接服务端返回的 `ws_url`。 -- 后续消息通过 WebSocket JSONL 传输。 - -但普通 `query()` 默认走的是本地 Claude Code 子进程。 - -## 总结 - -`@anthropic-ai/claude-agent-sdk` 的启动模型可以概括成: - -```text -TypeScript SDK - -> resolve platform native claude binary - -> spawn claude --input-format stream-json --output-format stream-json ... - -> write user/control JSONL to stdin - -> read assistant/system/result JSONL from stdout - -> expose AsyncGenerator API to caller -``` - -因此,它更像是 Claude Code CLI 的程序化 wrapper 和 transport 层。真正的 agent 能力、模型调用、工具执行、权限策略和 transcript 写入,主要发生在 Claude Code CLI 子进程内部。 diff --git a/docs/codex-sdk-cli-call-flow.zh.md b/docs/codex-sdk-cli-call-flow.zh.md deleted file mode 100644 index 5cc8e42..0000000 --- a/docs/codex-sdk-cli-call-flow.zh.md +++ /dev/null @@ -1,275 +0,0 @@ -# Codex SDK 如何调用 Codex CLI - -结论:这个仓库里的 SDK 本质上都是通过本机 Codex 可执行文件来驱动 Codex,但调用方式不同。 - -- TypeScript SDK:直接包装 `codex exec --experimental-json`。 -- Python SDK:启动 `codex app-server --listen stdio://`,再通过 JSON-RPC v2 调用 app-server。 - -## TypeScript SDK - -入口在 `sdk/typescript/src/codex.ts`。 - -使用者创建: - -```ts -const codex = new Codex(); -const thread = codex.startThread(); -const result = await thread.run("..."); -``` - -内部流程是: - -1. `Codex` 构造 `CodexExec`。 -2. `CodexExec` 找到本机平台对应的 Codex binary。 -3. `Thread.run()` 调用 `runStreamedInternal()`。 -4. `runStreamedInternal()` 调用 `CodexExec.run()`。 -5. `CodexExec.run()` 使用 `child_process.spawn()` 启动: - -```bash -codex exec --experimental-json ... -``` - -prompt 不是作为命令行参数传入,而是写入子进程 stdin: - -```ts -child.stdin.write(args.input); -child.stdin.end(); -``` - -Codex CLI 的 stdout 是 JSONL。SDK 用 `readline` 一行一行读取 stdout,然后把每一行 `JSON.parse()` 成事件: - -```ts -for await (const line of stdout) { - yield JSON.parse(line); -} -``` - -`Thread.run()` 是对流式事件的再包装:它收集 `item.completed`、`turn.completed` 等事件,最后返回: - -- `items` -- `finalResponse` -- `usage` - -如果要继续同一个对话,SDK 会保存 `thread.started` 事件里的 `thread_id`。下一次 run 时,如果已有 thread id,就会追加: - -```bash -codex exec --experimental-json resume -``` - -所以 TypeScript SDK 可以理解为 Codex CLI 的轻量包装层:它负责参数转换、启动 CLI、写 stdin、读 JSONL stdout、整理事件。 - -## TypeScript SDK 如何找到 Codex binary - -文件:`sdk/typescript/src/exec.ts` - -默认情况下,SDK 不直接调用 PATH 里的 `codex`,而是解析 npm 包里的平台 binary。 - -它先根据当前平台生成 target triple,例如: - -- macOS arm64:`aarch64-apple-darwin` -- macOS x64:`x86_64-apple-darwin` -- Linux x64:`x86_64-unknown-linux-musl` -- Windows x64:`x86_64-pc-windows-msvc` - -然后定位: - -```text -@openai/codex -└── optional platform package - └── vendor//codex/codex -``` - -如果找不到,会报错提示安装 `@openai/codex` 及其 optional dependencies。 - -也可以通过 `codexPathOverride` 指定自己的 binary 路径。 - -## TypeScript SDK 参数如何映射到 CLI - -`CodexExec.run()` 会把 SDK options 转成 CLI 参数: - -| SDK 选项 | CLI 参数 | -|---|---| -| `model` | `--model` | -| `sandboxMode` | `--sandbox` | -| `workingDirectory` | `--cd` | -| `additionalDirectories` | `--add-dir` | -| `skipGitRepoCheck` | `--skip-git-repo-check` | -| `outputSchemaFile` | `--output-schema` | -| `threadId` | `resume ` | -| `images` | `--image ` | -| `baseUrl` | `--config openai_base_url=...` | -| `approvalPolicy` | `--config approval_policy=...` | -| `webSearchMode` | `--config web_search=...` | - -SDK 里的 `config` 对象会被展开成 dotted TOML overrides,例如: - -```ts -{ - sandbox_workspace_write: { - network_access: true, - }, -} -``` - -会变成: - -```bash ---config sandbox_workspace_write.network_access=true -``` - -如果传了 `apiKey`,SDK 会注入环境变量: - -```text -CODEX_API_KEY= -``` - -同时还会设置: - -```text -CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codex_sdk_ts -``` - -## Python SDK - -入口在 `sdk/python/src/codex_app_server/api.py` 和 `sdk/python/src/codex_app_server/client.py`。 - -Python SDK 不是直接调用 `codex exec`。它启动的是: - -```bash -codex app-server --listen stdio:// -``` - -然后通过 stdin/stdout 发送 JSON-RPC v2 消息。 - -典型使用方式: - -```py -from codex_app_server import Codex - -with Codex() as codex: - thread = codex.thread_start(model="gpt-5") - result = thread.run("Say hello.") -``` - -内部流程是: - -1. `Codex()` 创建 `AppServerClient`。 -2. `AppServerClient.start()` 用 `subprocess.Popen()` 启动 Codex binary。 -3. 默认启动参数是: - -```bash -codex app-server --listen stdio:// -``` - -4. `Codex()` 立即发送 `initialize` JSON-RPC 请求。 -5. `thread_start()` 发送 `thread/start`。 -6. `thread.run()` 发送 `turn/start`。 -7. SDK 持续读取 server notification,直到收到 `turn/completed`。 - -Python SDK 每条消息都是一行 JSON。写请求时: - -```py -stdin.write(json.dumps(payload) + "\n") -``` - -读响应和通知时: - -```py -line = stdout.readline() -message = json.loads(line) -``` - -所以 Python SDK 更像是 app-server 协议客户端,而不是 `codex exec` 包装器。 - -## Python SDK 如何找到 Codex binary - -文件:`sdk/python/src/codex_app_server/client.py` - -默认从运行时包里找: - -```py -from codex_cli_bin import bundled_codex_path -``` - -也就是发布版本会依赖一个 pinned runtime package: - -```text -openai-codex-cli-bin -``` - -这个包里包含对应平台的 Codex binary。 - -本地开发时可以通过: - -```py -AppServerConfig(codex_bin="/path/to/codex") -``` - -显式指定 binary。 - -## Python SDK 的 JSON-RPC 映射 - -Python SDK 把高级方法映射到 app-server v2 RPC: - -| SDK 方法 | JSON-RPC method | -|---|---| -| `initialize()` | `initialize` | -| `thread_start()` | `thread/start` | -| `thread_resume()` | `thread/resume` | -| `thread_list()` | `thread/list` | -| `thread_read()` | `thread/read` | -| `thread_fork()` | `thread/fork` | -| `turn_start()` | `turn/start` | -| `turn_steer()` | `turn/steer` | -| `turn_interrupt()` | `turn/interrupt` | -| `model_list()` | `model/list` | - -server 也可能向 SDK 发起 request,例如审批类请求。SDK 默认通过 `approval_handler` 处理,然后写回 JSON-RPC response。 - -## 两个 SDK 的核心区别 - -| 维度 | TypeScript SDK | Python SDK | -|---|---|---| -| 启动命令 | `codex exec --experimental-json` | `codex app-server --listen stdio://` | -| 通信协议 | CLI JSONL events | JSON-RPC v2 | -| prompt 输入 | 写入 stdin | `turn/start` params | -| 事件输出 | stdout JSONL | server notifications | -| 会话继续 | `codex exec resume ` | `thread/resume` 或同一个 app-server thread | -| 抽象层级 | 更薄,偏 CLI wrapper | 更厚,偏 app-server client | - -## 简化调用链 - -TypeScript: - -```text -Codex.startThread() - -> Thread.run() - -> CodexExec.run() - -> spawn("codex", ["exec", "--experimental-json", ...]) - -> stdin 写入 prompt - -> stdout 读取 JSONL events - -> 聚合成 RunResult -``` - -Python: - -```text -Codex() - -> AppServerClient.start() - -> Popen(["codex", "app-server", "--listen", "stdio://"]) - -> initialize - -> thread/start - -> turn/start - -> 读取 notifications - -> turn/completed 后聚合 RunResult -``` - -## 总结 - -如果你问“SDK 是不是对 Codex CLI 的包装”,答案是: - -是,但要分语言看。 - -TypeScript SDK 基本就是对 `codex exec` 的包装。它把 SDK 方法转换成 CLI 参数,把 prompt 写进 stdin,再把 CLI 输出的 JSONL 事件转成 SDK events。 - -Python SDK 也是依赖 Codex CLI binary,但它不是包装 `codex exec`,而是把 Codex binary 当作 app-server 进程启动,并通过 JSON-RPC v2 操作线程、回合、流式通知和中断等能力。 diff --git a/docs/dify-graph-runtime.zh.md b/docs/dify-graph-runtime.zh.md deleted file mode 100644 index 0c548b7..0000000 --- a/docs/dify-graph-runtime.zh.md +++ /dev/null @@ -1,628 +0,0 @@ -# Dify 中的 Graph Runtime 分层说明 - -基于本地源码仓库 `references/dify` 分析,版本为 `90fe54c`,远端为 `https://github.com/langgenius/dify.git`。 - -这份文档只回答一个问题: - -**在 Dify 里,一个 workflow 可以拆成哪三层,以及用户点一次 Run 之后,这三层是怎么串起来的。** - -## 1. 三层模型 - -如果把 workflow 拆开看,可以分成三层: - -1. `Graph Definition` -2. `Graph Runtime State` -3. `Graph Runtime Engine` - -这三层不是一回事。 - -### 1.1 Graph Definition - -这是静态定义层,回答的是: - -- 图里有哪些节点 -- 节点之间怎么连 -- 每个节点的配置是什么 -- workflow 级功能和变量是什么 - -在 Dify 里,这一层主要由 `Workflow` 模型承载: - -- `graph` -- `features` -- `environment_variables` -- `conversation_variables` -- `rag_pipeline_variables` - -其中 `graph` 本身就是 workflow 画布 DSL,核心包含: - -- `nodes` -- `edges` -- `viewport` - -也就是说,Dify 的 workflow 不是把节点和边拆成很多张强关系表,而是直接把整份画布 JSON 持久化。 - -对应实现: - -- `references/dify/api/models/workflow.py` -- `references/dify/web/service/workflow.ts` - -## 1.2 Graph Runtime State - -这是动态状态层,回答的是: - -- 这一次 run 现在跑到哪了 -- 当前变量池里有什么值 -- 哪些节点已经完成 -- 哪些节点正在运行 -- 哪些节点正在等待 human input -- 当前 workflow 是 `running`、`paused`、`failed` 还是 `completed` - -在 Dify 里,这一层不是单一对象,而是两部分一起组成: - -### A. Graphon 的内存态 - -底层状态对象来自 `graphon`: - -- `GraphRuntimeState` -- `VariablePool` - -它们负责承载一次执行中的内存上下文。 - -### B. Dify 自己的持久化状态 - -Dify 又把运行中的关键状态落到自己的 repository / database 里,包括: - -- workflow run -- node execution -- pause state -- event snapshot - -这样做的目的不是多此一举,而是为了支持: - -- streaming 事件输出 -- run history -- pause / resume -- 客户端断线后事件重建 - -所以更准确地说: - -**Dify 的 Graph Runtime State = Graphon 内存运行态 + Dify 自己的持久化执行状态** - -对应实现: - -- `references/dify/api/core/app/apps/workflow/app_runner.py` -- `references/dify/api/services/workflow_event_snapshot_service.py` - -## 1.3 Graph Runtime Engine - -这是执行引擎层,回答的是: - -- 下一个该跑哪个节点 -- 节点输出怎么进入下游节点 -- 分支怎么选 -- loop 怎么继续 -- child graph 怎么创建 -- stop / cancel / resume 怎么处理 - -在 Dify 里,这一层真正的核心是: - -- `graphon.GraphEngine` - -Dify 并没有自己从零写一套图调度器,而是把图执行交给 `graphon`,自己负责外围包装。 - -对应实现: - -- `references/dify/api/core/workflow/workflow_entry.py` - -所以一句话总结三层关系: - -- `Graph Definition`:Dify 自己的 workflow DSL -- `Graph Runtime State`:`GraphRuntimeState + VariablePool + Dify 持久化状态` -- `Graph Runtime Engine`:`graphon.GraphEngine` - -## 2. Dify 里三层分别由什么实现 - -把上面的定义映射到具体代码,可以直接落成下面这张表。 - -| 层 | Dify 中的实现 | -| --- | --- | -| Graph Definition | `Workflow.graph` 及相关 features / variables | -| Graph Runtime State | `graphon.GraphRuntimeState`、`VariablePool`、workflow run / node execution / pause records | -| Graph Runtime Engine | `graphon.GraphEngine` | - -如果再往外包一层“产品级入口”,Dify 自己还加了三层应用包装: - -- `AppGenerateService` -- `WorkflowAppGenerator` -- `WorkflowAppRunner` - -它们不属于图引擎本身,但负责把 Dify 的 app、用户、SSE、Celery、数据库、追踪系统接到图引擎上。 - -## 3. 用户点一次 Run 之后,完整链路是什么 - -下面按顺序看一次 workflow run。 - -## 3.1 前端先取 Graph Definition - -用户在控制台里点击 Run 之前,前端画布维护的是 workflow draft。 - -前端会先拿到 draft workflow: - -- graph -- features -- variables - -如果用户刚改过节点,前端通常还会先同步一次 draft,再发起运行请求。 - -这一阶段本质上是在使用 `Graph Definition`,还没有真正开始图执行。 - -相关入口: - -- `references/dify/web/app/components/workflow-app/hooks/use-workflow-init.ts` -- `references/dify/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts` -- `references/dify/web/app/components/workflow-app/hooks/use-workflow-run.ts` - -## 3.2 请求进入 AppGenerateService - -不管请求来自哪里: - -- console debugger -- web app -- service API - -最后都会进入: - -- `references/dify/api/services/app_generate_service.py` - -这里先做的是 Dify 自己的产品级调度,而不是 graphon 的图调度: - -- 预留 quota -- app 级 rate limit -- 根据 `AppMode` 分发 -- 判断 `streaming` 还是 `blocking` - -对 workflow 来说,最重要的是: - -- `AppMode.WORKFLOW` -- `AppMode.ADVANCED_CHAT` - -这两个分支最后都会进入 workflow-based generator。 - -## 3.3 决定 streaming 还是 blocking - -这里 Dify 会分成两条路。 - -### A. Streaming - -如果是 streaming: - -1. 构造 `AppExecutionParams` -2. 客户端先订阅事件流 -3. 通过 Celery 任务启动真正执行 - -对应实现: - -- `workflow_based_app_execution_task.delay(payload_json)` - -也就是说,前端不是直接挂在执行线程上,而是订阅后续的事件流。 - -### B. Blocking - -如果是 blocking: - -1. 不进 Celery -2. 直接同步调用 `WorkflowAppGenerator().generate(...)` - -这种模式更接近“当前请求里同步跑完,然后直接返回结果”。 - -## 3.4 WorkflowAppGenerator 组装应用级上下文 - -不管是 blocking 还是 streaming,真正进入 workflow 执行包装层后,都会经过: - -- `references/dify/api/core/app/apps/workflow/app_generator.py` - -这一层做的不是底层图调度,而是 Dify 的“应用级封装”: - -- 解析文件输入 -- 预处理用户输入 -- 读取 app config -- 创建 trace manager -- 创建 workflow execution repository -- 创建 workflow node execution repository -- 创建 queue manager -- 可选挂上 `PauseStatePersistenceLayer` - -然后它会启动 worker thread,把更底层的图执行交给 `WorkflowAppRunner`。 - -所以这一层的职责是: - -**把“Dify 应用请求”翻译成“可执行的 workflow run 上下文”** - -## 3.5 WorkflowAppRunner 初始化 Graph Runtime State - -真正进入运行态装配的是: - -- `references/dify/api/core/app/apps/workflow/app_runner.py` - -这里开始初始化核心 runtime state: - -1. 创建 `VariablePool` -2. 注入 system variables -3. 注入 environment variables -4. 把 start node inputs 写入变量池 -5. 创建 `GraphRuntimeState` -6. 解析 root node -7. 初始化 graph -8. 创建 Redis command channel -9. 构造 `WorkflowEntry` - -所以如果只看“图运行状态从哪里开始建立”,答案就在这里: - -- `VariablePool` -- `GraphRuntimeState` - -这就是 Dify 一次 run 的内存运行态起点。 - -## 3.6 WorkflowEntry 创建 Graph Runtime Engine - -接下来进入: - -- `references/dify/api/core/workflow/workflow_entry.py` - -这里会直接创建: - -- `GraphEngine` - -同时挂上若干 layer: - -- `ExecutionLimitsLayer` -- `LLMQuotaLayer` -- `ObservabilityLayer` -- `DebugLoggingLayer` - -也就是从这里开始,真正的 **Graph Runtime Engine** 启动。 - -这一步之后,workflow 就不再只是“配置 + 状态”,而是进入真正的图调度执行。 - -## 3.7 节点体系如何接上引擎 - -图引擎只知道要执行节点,但节点类型本身需要注册。 - -在 Dify 里,这件事由: - -- `references/dify/api/core/workflow/node_factory.py` - -负责。 - -它会同时加载: - -- `graphon.nodes` -- `core.workflow.nodes` - -所以节点来源有两部分: - -1. graphon 提供的通用节点能力 -2. Dify 自己补充的 workflow 节点与 runtime adapter - -这也是 Dify 和 graphon 的边界所在: - -- graphon 负责通用图能力 -- Dify 负责 AI 应用平台相关节点和适配 - -## 3.8 运行中如何持续输出状态 - -图开始执行之后,Dify 不会等整张图全部跑完才返回。 - -它会不断产出事件,比如: - -- workflow started -- node started -- node finished -- text chunk -- workflow paused -- workflow failed - -如果是 streaming,这些事件会通过 Redis topic 和 SSE 持续送到客户端。 - -如果客户端断线或后来再连,Dify 会通过: - -- workflow run -- node execution snapshot -- pause state - -先构造一份历史快照事件,再接上实时流。 - -对应实现: - -- `references/dify/api/services/workflow_event_snapshot_service.py` -- `references/dify/api/core/app/apps/message_generator.py` -- `references/dify/api/tasks/app_generate/workflow_execute_task.py` - -所以 Dify 的 runtime 不只是“会跑图”,还包含一整套: - -- 事件流输出 -- 状态回放 -- 暂停恢复 -- 断线重连 - -## 4. 一条最短总结链 - -如果你只想记住一条最短链路,可以记这个: - -`Workflow.graph` --> `AppGenerateService` --> `WorkflowAppGenerator` --> `WorkflowAppRunner` --> `GraphRuntimeState / VariablePool` --> `WorkflowEntry` --> `graphon.GraphEngine` --> `事件流 + 持久化` - -其中: - -- 前半段是 Dify 的应用层封装 -- 中间两步是 runtime state 初始化 -- 真正跑图的是 `graphon.GraphEngine` - -## 5. 最后一句话 - -在 Dify 里,workflow 并不是直接“由 graphon 全包”。 - -更准确地说是: - -- **Dify 定义图** -- **Dify 组装应用上下文和持久化状态** -- **graphon 负责底层图执行** - -这就是 Dify 里三层的真正分工。 - -## 6. 常见运行时概念在 Dify 里分别是什么意思 - -前面讲的是三层结构。下面把几个常见 runtime 概念,直接映射到 Dify 的真实实现。 - -### 6.1 变量池 - -变量池就是一次 workflow run 的运行时变量上下文。 - -它不是 workflow 定义本身,也不是数据库里的历史记录,而是当前这次执行在内存里的变量空间。 - -在 Dify 里,变量池主要由 `VariablePool` 承担。workflow 启动时会先把这些内容注入进去: - -- system variables -- environment variables -- root node inputs - -后续节点再继续从里面取值、写值。 - -对应实现: - -- `references/dify/api/core/app/apps/workflow/app_runner.py` -- `references/dify/api/core/workflow/variable_pool_initializer.py` - -它的关键特点是: - -- 变量按 selector 存储 -- 常见形式类似 `(node_id, key)` -- 下游节点不是直接吃上游函数返回值,而是通过 selector 去取变量 - -### 6.2 当前执行状态 - -当前执行状态不是单一对象,而是两层一起组成: - -1. 内存里的 `GraphRuntimeState` -2. 持久化的 workflow run / node execution / pause state - -其中: - -- `GraphRuntimeState` 负责执行中的即时状态 -- 持久化记录负责 run history、断线恢复、pause / resume、事件回放 - -所以 Dify 的“当前执行状态”不能只理解成一个内存对象,也不能只理解成数据库表。 - -更准确地说,它是: - -**GraphRuntimeState + 持久化执行状态** - -### 6.3 节点输出到下游节点的传递 - -Dify 不是沿着 edge 直接传一个临时 payload 给下游节点。 - -它更像: - -1. 当前节点产生 `NodeRunResult(outputs=...)` -2. 输出进入 runtime 上下文 -3. 下游节点按 selector 从变量池读取自己依赖的值 - -所以它的核心传递机制不是“函数参数传递”,而是: - -**变量池 + selector 引用** - -这也是 graph 形式和线性 phase 形式的一个关键差别。 - -### 6.4 事件流 - -事件流是运行过程的观察通道。 - -它不是 workflow 真正执行逻辑的一部分,而是把执行过程中的状态变化持续推给客户端,例如: - -- workflow started -- node started -- node finished -- workflow paused -- workflow failed - -Dify 的事件流有两个特点: - -1. live 事件持续推送 -2. 断线后可以先回放 snapshot,再接 live 流 - -也就是说,Dify 不是“只做直播”,它还做了“补历史”。 - -对应实现: - -- `references/dify/api/services/workflow_event_snapshot_service.py` -- `references/dify/api/tasks/app_generate/workflow_execute_task.py` -- `references/dify/api/core/app/apps/message_generator.py` - -### 6.5 pause / resume - -在 Dify 里,pause 不是简单地把某个线程挂住。 - -它的本质是: - -1. workflow 收到 pause 事件 -2. 把当前 `graph_runtime_state` 序列化 -3. 连同 `generate_entity` 和 `pause_reasons` 一起落库 -4. 后面再把这份 snapshot 重新加载回来继续执行 - -所以 Dify 的 pause / resume 是: - -**可恢复执行状态持久化** - -不是纯内存暂停。 - -对应实现: - -- `references/dify/api/core/app/layers/pause_state_persist_layer.py` -- `references/dify/api/tasks/app_generate/workflow_execute_task.py` -- `references/dify/api/services/human_input_service.py` - -### 6.6 command channel - -command channel 是控制通道,不是数据通道。 - -它的作用是让外部或 layer 在 workflow 运行中发控制命令给 `GraphEngine`,例如: - -- `PAUSE` -- `ABORT` - -在 Dify 的真实 workflow run 里,这个 channel 通常是 Redis channel,key 形如: - -- `workflow:{task_id}:commands` - -对应实现: - -- `references/dify/api/core/app/apps/workflow/app_runner.py` -- `references/dify/api/core/workflow/workflow_entry.py` -- `references/dify/api/core/app/workflow/layers/llm_quota.py` -- `references/dify/api/core/app/layers/timeslice_layer.py` - -如果把上面六个概念压缩成一句话,可以记成: - -- `变量池` 是数据面 -- `当前执行状态` 是 runtime 状态面 -- `事件流` 是观察面 -- `command channel` 是控制面 -- `pause / resume` 是可恢复执行面 -- `节点输出传递` 是基于 selector 的变量引用传递 - -## 7. 这种 graph 形式适不适合当前这个 app - -先说结论: - -**适合做你这个 app 的下一阶段能力底座,但不适合直接原样替换你现在这套 workflow。** - -原因不是 Dify/graphon 不够强,而是你当前 app 的 workflow 形态和 Dify 的目标形态还不是一类系统。 - -### 7.1 你当前 app 更像什么 - -你现在这套 workflow,本质上更接近: - -- 有序 phase pipeline -- 每个 phase 有明确类型:`auto` / `checkpoint` -- phase 之间主要通过文件输出和状态文件衔接 -- 人工暂停、回退、重跑是产品主路径 -- runtime state 目前以本地 `workflow-state.json` 和 phase 文件为主 - -对应实现: - -- `packages/core-models/workflow.mjs` -- `packages/core-models/state.mjs` -- `apps/desktop/electron/workflow-runtime.mjs` - -它的核心优势是: - -- 简单 -- 可读 -- 很贴合 desktop-first、本地文件可见、任务制工作流 - -### 7.2 Dify/graphon 这种 graph 形式更适合什么 - -这种 graph runtime 更适合: - -- 节点类型持续扩张 -- 分支逻辑越来越多 -- 节点依赖不再只是严格线性顺序 -- 需要 loop、child graph、trigger、tool node、human input node 统一建模 -- 需要更强的运行时可控性和事件系统 - -也就是说,它更适合“流程编排平台”,不只是“按阶段推进的任务流”。 - -### 7.3 为什么不适合直接替换你当前实现 - -因为你当前 app 的主模型还是 phase,不是任意 graph。 - -你现在很多能力都建立在 phase 语义上: - -- phase 顺序 -- checkpoint 审核 -- reject target -- phase 内容文件 -- phase artifact -- phase sessionId -- worktree 生命周期 - -这些语义现在都直接写进了状态结构和 runtime 行为里。 - -如果直接改成 Dify/graphon 这种通用 graph: - -1. 现有产品语义会先被打散 -2. 需要重新定义 phase 和 graph node 的对应关系 -3. 本地文件、artifact、manual checkpoint、worktree 这些能力都要重新接入 runtime - -这会明显放大改造面。 - -### 7.4 更现实的建议 - -更合理的路线不是“立刻把 phase runtime 换成 graph runtime”,而是分两步: - -1. 先保留 phase 作为产品层模型 -2. 只在 runtime 层逐步吸收 graph 的能力 - -例如先引入这些能力,而不是先引入完整自由图: - -- phase 级变量池 -- 更标准化的事件流 -- pause snapshot / resume snapshot -- command channel -- 非线性跳转能力 -- 局部 loop / 子流程 - -这样做的好处是: - -- 不破坏当前 app 的 phase UX -- 可以继续保留本地文件和 worktree 语义 -- runtime 能逐步从“线性 phase engine”升级到“受控 graph engine” - -### 7.5 我对你当前 app 的判断 - -如果你的目标还是: - -- desktop-first -- task/worktree 驱动 -- 人工审阅和回退很重要 -- workflow 主要是固定阶段编排 - -那么当前阶段最合适的不是 Dify 这种自由 graph editor。 - -更合适的是: - -**保留 phase-first 的产品模型,按 graph runtime 的思路增强执行层。** - -如果未来你的目标变成: - -- 用户自己拖拽节点 -- 自定义分支和循环越来越多 -- workflow 不再主要按固定 phase 理解 - -那时再把底层逐步演进成真正的 graph runtime,会更顺。 diff --git a/docs/dify-workflow-architecture.zh.md b/docs/dify-workflow-architecture.zh.md deleted file mode 100644 index ae00308..0000000 --- a/docs/dify-workflow-architecture.zh.md +++ /dev/null @@ -1,413 +0,0 @@ -# Dify workflow 与整体架构分析 - -基于本地源码仓库 `references/dify` 分析,版本为 `90fe54c`,远端为 `https://github.com/langgenius/dify.git`。 - -## 1. 先说结论 - -Dify 不是一个“只有 workflow 引擎”的项目,而是一个围绕 AI 应用开发平台展开的完整产品仓库。它把真正通用的图执行能力尽量下沉到外部依赖 `graphon`,而把 Dify 自己的工作重点放在四层: - -1. 应用层:Workflow App、Advanced Chat、Agent Chat、Completion 等产品形态。 -2. 平台层:模型接入、知识库、工具、插件、配额、鉴权、观测、协作。 -3. 运行层:工作流执行、事件流、暂停/恢复、人类输入、单节点调试。 -4. 交付层:Web 控制台、公开 API、自托管 Docker、可裁剪 provider 插件体系。 - -这决定了它的架构不是“前端 + 一个简单后端”,而是“控制台 + API 服务 + 异步任务系统 + Redis 事件通道 + 可插拔 runtime/provider”。 - -## 2. 仓库分层 - -### 2.1 顶层模块 - -- `api/`:Python 后端,基于 Flask,承担 API、工作流运行、模型/工具/知识库接入、Celery 任务、Socket.IO 协作。 -- `web/`:Next.js 前端控制台,工作流编辑器、调试面板、应用管理 UI 都在这里。 -- `packages/`:前端共享包,比如 `@dify/contracts` 和 `@langgenius/dify-ui`。 -- `docker/`:官方自托管部署入口,包含 API、Web、Redis、数据库、向量库等运行所需配置。 -- `sdks/`:面向外部集成方的 SDK。 -- `api/providers/`:可插拔 provider,尤其是向量库和 trace provider。 - -从 workspace 角度看,前端 monorepo 由 `pnpm-workspace.yaml` 管理 `web`、`e2e`、`sdks/nodejs-client`、`packages/*`;后端 Python 则在 `api/pyproject.toml` 里用 `uv workspace` 管理 `providers/vdb/*` 和 `providers/trace/*`。 - -### 2.2 后端启动骨架 - -`api/app.py` 是 API 进程入口,它创建 Flask app,并包上一层 gevent WebSocket server;`create_app()` 在 `api/app_factory.py` 中初始化数据库、Redis、Celery、蓝图、OpenTelemetry、Socket.IO 等扩展,最后把 Flask app 包成 `socketio.WSGIApp`。 - -这说明 Dify 的后端从一开始就不是“纯 HTTP API”,而是同时包含: - -- 普通 REST API -- SSE 流式返回 -- Socket.IO 协作连接 -- Celery 异步执行 - -## 3. Workflow 的核心数据设计 - -### 3.1 Workflow 其实是一份 DSL 文档 - -`api/models/workflow.py` 里的 `Workflow` 模型是整个 workflow 系统的核心持久化对象。它直接把这些东西存进数据库: - -- `graph`:整个画布 DSL,JSON 字符串,包含 nodes、edges、viewport。 -- `features`:工作流级功能配置。 -- `environment_variables` -- `conversation_variables` -- `rag_pipeline_variables` -- `version`:`draft` 或发布版本号。 - -这说明 Dify 的 workflow 设计本质上是“画布 DSL 驱动”,而不是把每个节点、每条边拆成强关系表。这样的好处是: - -- 前端 React Flow 结构可以直接落库。 -- 发布版本可以做整份快照复制。 -- 节点 schema 迭代更灵活。 - -代价也很明确: - -- 图结构校验更多依赖运行前校验,不依赖数据库约束。 -- JSON 读写频繁,强类型边界不如关系模型硬。 - -### 3.2 Draft / Published 双轨 - -`api/services/workflow_service.py` 里,草稿通过 `sync_draft_workflow()` 更新,发布通过 `publish_workflow()` 复制出一个新版本。 - -这里有几个关键点: - -1. 草稿只有一份:`version == "draft"`。 -2. 发布不是原地改,而是复制草稿生成新版本快照。 -3. 草稿同步带 `hash`,用于乐观并发控制,避免多人或多标签页互相覆盖。 - -这套模型非常产品化。它优先保证: - -- 编辑态可以频繁保存 -- 运行态可以绑定已发布版本 -- 历史版本可以回放、恢复、审计 - -而不是只追求“图执行最快”。 - -## 4. Workflow 编辑器的前端设计 - -### 4.1 画布与本地状态 - -工作流页面入口非常薄:`web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx` 直接挂载 `WorkflowApp`。 - -真正的编辑器壳在 `web/app/components/workflow-app/index.tsx`,负责: - -- 初始化 workflow draft -- 拉取默认节点配置和已发布版本 -- 将后端 graph 转成 React Flow 的 nodes/edges -- 注入 workflow store、features store、trigger 状态 - -底层画布组件在 `web/app/components/workflow/index.tsx`,核心技术栈是: - -- `reactflow`:节点/边/viewport 画布 -- store:保存节点运行态、面板状态、变量、同步 hash 等 -- features provider:保存 opening、tts、moderation、file upload 等工作流级能力 - -### 4.2 草稿同步 - -`useWorkflowInit()` 启动时请求 `/apps/:id/workflows/draft`,拿到 graph、features、环境变量、会话变量;如果草稿不存在,会自动创建初始化草稿。 - -`useNodesSyncDraft()` 负责把当前 nodes、edges、viewport、features、variables 序列化后发回 `/apps/:id/workflows/draft`。它会: - -- 过滤前端临时字段 -- 带上 `syncWorkflowDraftHash` -- 在协作模式下区分 leader / follower -- 页面关闭时用 keepalive 补最后一次同步 - -这说明 Dify 的画布不是“本地状态优先、手动保存”,而是“近实时草稿同步”。 - -### 4.3 调试与运行入口 - -`useWorkflowRun()` 在运行前会先 `doSyncWorkflowDraft()`,然后发起 SSE 请求执行 draft workflow。它同时支持: - -- 整体运行 -- iteration 单节点运行 -- loop 单节点运行 -- trigger debug -- stop run - -这体现了 Dify workflow 编辑器的一个明显定位:它不是只负责“配置”,而是把“开发态调试器”直接嵌在画布里。 - -## 5. Workflow 运行链路 - -### 5.1 控制台 / API 入口 - -后端控制器有三类: - -- `controllers/console/app/workflow.py`:控制台草稿编辑、运行、单节点调试、人类输入表单预览。 -- `controllers/web/workflow.py`:面向 Dify 自己 Web App 的公开运行入口。 -- `controllers/service_api/app/workflow.py`:对外 API,支持 blocking / streaming 两种模式。 - -这三类入口最后都会汇入 `services/app_generate_service.py`。 - -### 5.2 统一分发器:AppGenerateService - -`AppGenerateService.generate()` 是 Dify 多种 app mode 的总调度器。它先做: - -- 配额预留 -- app 级并发限流 -- 根据 `AppMode` 分发到不同 generator - -对 workflow 来说: - -- `AppMode.WORKFLOW` -- `AppMode.ADVANCED_CHAT` - -这两条都会走 workflow-based generator,而不是传统 chat/completion 管线。 - -### 5.3 Streaming 与 Blocking 两条执行路径 - -在 workflow 模式下,`AppGenerateService` 明确区分两种运行方式: - -1. `streaming=true` - 把执行参数打包成 `AppExecutionParams`,通过 `workflow_based_app_execution_task.delay()` 扔到 Celery;前端先订阅事件流,后端异步推送事件。 -2. `streaming=false` - 直接同步调用 `WorkflowAppGenerator().generate(...)`,返回阻塞式结果。 - -这点很关键。Dify 并不是“所有 workflow 都异步队列化”,而是: - -- 面向交互式调试和 UI 预览时,可以同步跑 -- 面向正式流式运行时,走 Celery + Redis 事件通道 - -### 5.4 Celery worker 中真正的执行 - -Celery 任务定义在 `api/tasks/app_generate/workflow_execute_task.py`。 - -它的职责是: - -1. 反序列化 `AppExecutionParams` -2. 重新加载 `App`、`Workflow`、`User` -3. 构造 `PauseStateLayerConfig` -4. 调用 `WorkflowAppGenerator().generate(...)` -5. 如果是 streaming,就把事件发布到 Redis topic - -也就是说,SSE 客户端并不直接“盯住执行线程”,而是订阅一个 Redis topic。这样做有两个实际好处: - -- Web 请求和执行进程彻底解耦 -- 前端断线后可以走“事件重建 / resume”逻辑 - -### 5.5 WorkflowAppGenerator:应用态包装器 - -`api/core/app/apps/workflow/app_generator.py` 负责把“工作流引擎执行”包装成 Dify 应用运行。 - -它做的事情包括: - -- 解析文件输入 -- 读取 app config -- 预处理用户输入 -- 创建 trace manager -- 构建 workflow execution repository / node execution repository -- 创建 queue manager -- 挂 PauseStatePersistenceLayer -- 启动后台 worker thread -- 把内部事件转换成 blocking 响应或 stream 响应 - -这里能看出一个典型分层: - -- `WorkflowAppGenerator` 不直接实现图执行 -- 它负责“应用级上下文、仓储、队列、响应协议” -- 真正的图执行交给下游 runner + graph engine - -### 5.6 WorkflowAppRunner:运行时装配层 - -`api/core/app/apps/workflow/app_runner.py` 才真正把工作流跑起来。它会: - -1. 初始化 `VariablePool` -2. 注入 system variables、environment variables、start node inputs -3. 根据 graph config 找 root node -4. 创建 `GraphRuntimeState` -5. 初始化 graph -6. 创建 Redis command channel:`workflow:{task_id}:commands` -7. 构造 `WorkflowEntry` -8. 挂 `WorkflowPersistenceLayer` 和其他 graph layers -9. 遍历 `workflow_entry.run()` 产生的事件 - -这里能看出 Dify 的 workflow runtime 是由几块拼起来的: - -- graph config -- variable pool -- runtime state -- command channel -- persistence layer -- observability / quota / limits layers - -### 5.7 WorkflowEntry:图执行入口 - -`api/core/workflow/workflow_entry.py` 是 Dify 接到 `graphon` 的那一层。 - -它做的事情很明确: - -- 创建 `GraphEngine` -- 配置 worker 数量、伸缩阈值、超时限制 -- 挂 `ExecutionLimitsLayer` -- 挂 `LLMQuotaLayer` -- 在 OTel 开启时挂 `ObservabilityLayer` -- 需要时挂 `DebugLoggingLayer` -- 支持 child graph builder - -这里最重要的事实是:Dify 没有自己重新发明一套并行图执行引擎,而是把这块能力交给 `graphon`,自己负责 layer、node、runtime adapter 和平台集成。 - -### 5.8 Node 注册与 Dify 适配 - -`api/core/workflow/node_factory.py` 说明了节点体系的设计: - -- 先加载 `graphon.nodes` -- 再加载 `core.workflow.nodes` -- 两者共同注册到 Node registry - -这意味着: - -- 通用节点能力尽量来自 `graphon` -- Dify 自己只补“产品相关节点”或对 runtime 的适配 - -同时 `DifyNodeFactory` 还负责把这些 Dify 特有能力塞进节点运行时: - -- 模型访问 -- 工具运行 -- 文件管理 -- Human Input runtime -- Prompt serializer -- Code executor - -## 6. Pause / Resume / Human Input 设计 - -这是 Dify workflow 里很重要,也很像产品平台而不是纯引擎的部分。 - -### 6.1 Pause 状态持久化 - -在 blocking 路径和 worker 路径里,都会给 workflow execution 挂 `PauseStatePersistenceLayer`。这代表暂停不是只停在内存里,而是落库持久化。 - -### 6.2 事件流重建 - -`api/services/workflow_event_snapshot_service.py` 在客户端重连或 resume 时,会: - -- 读取 `WorkflowRun` -- 读取 `WorkflowPause` -- 读取 node execution snapshots -- 先构造 workflow_started / node_started / node_finished 的快照事件 -- 再接上 Redis topic 的实时事件 - -这说明 Dify 的 resume 不是简单“从头重放”,也不是只靠内存缓存,而是“数据库快照 + Redis 实时流”的拼接模型。 - -### 6.3 Human Input - -控制台里专门提供了 human input form preview / run 接口,相关逻辑在: - -- `controllers/console/app/workflow.py` -- `services/workflow_service.py` - -这表明 Human Input 在 Dify 里不是普通节点配置,而是带完整交互闭环的暂停点: - -- 先生成表单 -- 用户提交表单 -- workflow 继续执行 - -这个设计对真实业务流程更实用,因为很多 AI workflow 需要人工审批、确认、补录。 - -## 7. 协作模式设计 - -协作不是简单“共享草稿”,而是单独的一套实时系统。 - -### 7.1 前端协作 - -`web/app/components/workflow/collaboration/core/collaboration-manager.ts` 使用: - -- `loro-crdt` -- `socket.io-client` - -来同步 graph 变更、光标、评论、节点面板状态等。 - -### 7.2 后端协作 - -Socket.IO 入口在 `api/controllers/console/socketio/workflow.py`,服务层在 `api/services/workflow_collaboration_service.py`。 - -它的核心策略不是人人同时直接写库,而是: - -- 先基于 token 鉴权 -- 加入 workflow room -- 维护在线用户列表 -- 选举一个 leader -- follower 需要同步时向 leader 发 `sync_request` -- graph_update / collaboration_update 通过 room 广播 - -这套设计的重点不是“绝对强一致”,而是降低多人同时编辑 React Flow 画布时的冲突复杂度。CRDT 负责结构同步,leader/follower 负责把草稿持久化行为收敛到更少的写入源。 - -## 8. Provider 与可扩展性设计 - -### 8.1 后端 provider 插件化 - -`api/providers/README.md` 和 `api/providers/vdb/README.md` 说明,Dify 把不少“外部系统接入”做成独立 workspace package: - -- 向量库 provider -- trace provider - -通过 `entry_points` 动态发现,在运行时装载。 - -这背后的思路很清楚: - -- 核心平台保持稳定 -- 第三方依赖分散到 provider -- 自托管发行版可以裁剪 provider 集合 - -### 8.2 前端 contracts 与 UI 共享包 - -前端 monorepo 里: - -- `packages/contracts`:由后端 OpenAPI 生成 TS contract -- `packages/dify-ui`:共享 UI primitives - -这说明 Dify 在前端并不把所有东西都塞进 `web/`,而是已经开始把 API 合约和设计系统抽离成可复用包。 - -## 9. Dify 的 workflow 设计理念 - -从源码看,Dify 的 workflow 设计理念可以概括成下面几点。 - -### 9.1 Workflow 是 AI 应用编排层,不是底层引擎本身 - -真正的图执行能力交给 `graphon`;Dify 自己负责产品语义、节点生态、模型与工具接入、运行记录、调试、协作。 - -### 9.2 DSL 优先于关系建模 - -画布 JSON 直接作为权威数据结构。这样前后端围绕同一份 DSL 工作,换来开发效率和产品迭代速度。 - -### 9.3 运行时和交互态都很重要 - -它不只关心“最后跑完”。源码里大量能力都围绕开发态和运行中体验: - -- 单节点调试 -- SSE 流式事件 -- stop command -- pause / resume -- human input -- replay run - -### 9.4 平台化优先于单点功能 - -无论是模型、向量库、trace,还是工具、插件、协作,Dify 都在往平台型产品走。workflow 只是平台的一个核心表面,不是全部。 - -### 9.5 自托管和云化同时兼容 - -Docker、Celery、Redis、Flask、Next.js 这套组合比较传统,但它的好处是部署面宽,容易 self-host,也能支持云上扩展。 - -## 10. 这套架构的优点与代价 - -### 优点 - -- 产品闭环完整:编辑、调试、运行、回放、恢复、协作都打通了。 -- workflow 引擎与平台能力解耦得比较清楚。 -- 自托管能力强,部署和裁剪空间大。 -- streaming / blocking / resume 三种运行体验都覆盖。 - -### 代价 - -- 后端层级很多,初读成本高。 -- `api/` 内责任偏重,controller/service/generator/runner/layer/repository 之间跳转比较多。 -- graph JSON 方案灵活,但强类型和结构约束不如关系模型直观。 -- 协作、暂停恢复、事件重建这些高级能力,让整体系统复杂度明显上升。 - -## 11. 对你这个仓库可借鉴的点 - -如果你想从 Dify 借鉴 workflow 设计,我认为最值得看的不是 UI,而是这几条: - -1. 用一份统一 DSL 贯穿编辑、运行、版本化。 -2. 把“图执行引擎”和“产品平台能力”明确分层。 -3. 运行事件做成流,而不是只返回最终结果。 -4. 从一开始就给 pause / resume / human input 留接口。 -5. 协作持久化不要让所有客户端都直接争写后端。 - -如果你只是想借它的节点画布,那只学 React Flow 这一层价值不大;Dify 真正有参考价值的是“workflow 作为平台运行时”的那部分设计。 diff --git a/docs/langgraph-example.md b/docs/langgraph-example.md deleted file mode 100644 index 5d49746..0000000 --- a/docs/langgraph-example.md +++ /dev/null @@ -1,159 +0,0 @@ -主角:小明,今天接到一个小功能 -需求:“在用户头像旁边加一个在线状态小绿点” - -我们用 LangGraph 的 11 个核心概念,把小明这趟开发旅程串起来。 -① State(状态)—— 小明的“开发进度板” -整个开发过程中,所有信息都记在一块小黑板上,这就是 State: - -python -class DevState(TypedDict): - requirement: str # 需求描述 - plan: str # 实施方案 - code: str # 代码 - cr_comments: list # Code Review 意见 - status: str # 当前阶段 -小明每做完一步,就擦掉旧信息、写上新的。 - -② Nodes(节点)—— 每个开发步骤 -每个步骤就是一个 Node,也就是小明要做的一件事: - -需求分析:搞清楚到底加在哪个位置,是否区分在线/离线。 - -技术方案:决定用 CSS 伪类还是 websocket 推送,写个简单计划。 - -编码实现:吭哧吭哧写代码。 - -自测:本地跑起来看看绿点亮不亮。 - -提交 CR:把代码推到分支,创建 Pull Request。 - -根据 CR 修改:别人提了意见,再改一版。 - -这些都是独立的函数节点,接收 State,返回更新。 - -③ Edges(普通边)—— 固定流程 -有些步骤是固定顺序,就像流水线: - -需求分析 → 技术方案:想清楚才能干。 - -技术方案 → 编码实现:计划好了才动手。 - -编码实现 → 自测:写完当然先自己测。 - -根据 CR 修改 → 自测:改完再跑一遍。 - -这就是 普通边,小明必须按这个顺序走,没有分支。 - -④ Conditional Edges(条件边)—— 关键时刻的决策 -但有些地方需要判断,不能傻走。比如自测完了: - -如果自测发现 bug → 回到 编码实现(循环)。 - -如果自测通过 → 进入 提交 CR。 - -CR 结果回来以后: - -如果 reviewer 说“OK,可以合” → 走向 合并(结束)。 - -如果 reviewer 留了修改意见 → 走向 根据 CR 修改。 - -这个动态决策就是 条件边,它让开发流程不是死板的,bug 多了会循环,CR 不过会返工。 - -⑤ Graph(图)—— 小明的一整套开发 SOP -把上面所有节点和边(固定+条件)画成一张流程图,就是 Graph。它定义了小明处理一个需求的完整标准化流程: - -python -builder = StateGraph(DevState) -builder.add_node("需求分析", analyze_requirement) -builder.add_node("技术方案", make_plan) -builder.add_node("编码实现", write_code) -builder.add_node("自测", self_test) -builder.add_node("提交CR", create_pr) -builder.add_node("根据CR修改", revise_by_comments) -builder.add_node("合并", merge_pr) - -builder.set_entry_point("需求分析") -builder.add_edge("需求分析", "技术方案") -builder.add_edge("技术方案", "编码实现") -builder.add_edge("编码实现", "自测") - -# 自测后的分支 -builder.add_conditional_edges("自测", decide_after_test, { - "bug": "编码实现", - "pass": "提交CR" -}) - -# CR 后的分支 -builder.add_conditional_edges("提交CR", decide_after_cr, { - "need_revise": "根据CR修改", - "approved": "合并" -}) - -builder.add_edge("根据CR修改", "自测") -builder.add_edge("合并", END) -这就是小明的“开发大脑图”。 - -⑥ Compile(编译)—— 开启一天的干活模式 -图画好了只是张纸,Compile 就是小明早上坐到工位上,打开电脑,把这张 SOP 变成可以真正跑起来的工作流引擎。同时他还会挂载几个重要插件:记忆棒(checkpointer)和暂停开关(interrupt)。 - -⑦ Checkpointer(检查点)—— 停电能接着干 -小明在写代码时,突然公司断电了,或者电脑蓝屏了。因为有 Checkpointer,小黑板上的所有状态(做到哪一步、方案是什么、代码写了一部分)都被自动保存了。电一来,他输入同一个 thread_id(比如“需求#351”),工作流直接恢复到断电前的那一步,不用重新分析需求。 - -⑧ Streaming(流式输出)—— 项目经理的“实时看板” -项目经理想看小明干到哪儿了,不用站他身后,有个看板实时显示当前节点: - -text -✅ 需求分析 完成 -✅ 技术方案 完成 -⏳ 编码实现 进行中... -❌ 自测 发现 bug(已返回编码实现) -✅ 编码实现 完成(二次) -✅ 自测 通过 -⏳ 等待 Code Review... -这就是 Streaming,每完成一个节点就推一条状态,所有人心里有数。 - -⑨ Human-in-the-loop(人机协同)—— Code Review 环节 -开发流程中最经典的“人机协同”就是 Code Review。小明把代码推上去后,工作流在 提交CR 节点自动暂停了,因为 graph 编译时设了 interrupt_before=["提交CR"]。这时必须由 reviewer(人类)介入,看完代码,填写意见,然后才能决定下一步走向。在 LangGraph 里,这个“等待并获取外部输入”就是 Human-in-the-loop。 - -⑩ Command(命令)—— Reviewer 的“遥控器” -Reviewer 看完代码,留下两条意见:“1. 颜色用 #42b72a 别用 #00ff00;2. 加个动画”。他在 Review 系统里点“需要修改”,这个动作会被包装成一个 Command: - -python -Command(update={"cr_comments": ["颜色用#42b72a", "加动画"]}) -这个 Command 直接喂给工作流引擎,引擎一看:哦,该走 根据CR修改 分支了。于是小明继续吭哧改代码。如果 Reviewer 点的是“Approve”,Command 里就会标记 approved=True,工作流直接走到 合并。 - -⑪ Send(并行调度)—— 复杂需求时的并行开发 -如果需求被拆成“前端加绿点”和“后端推送在线状态”两个独立子任务,小明一个人干太慢。这时候 Team Lead 可以用 Send 把任务分发出去: - -python -def fanout(state): - return [ - Send("前端编码", {"component": "avatar"}), - Send("后端编码", {"api": "/status"}) - ] -两个同事同时开工,各自更新自己的小黑板,最后汇总。虽然这个小功能用不上,但流程里是支持这种并行模式的。 - -大结局:从接需求到 PR 合并的 LangGraph 之旅 -小明上午接到需求,状态机启动: - -需求分析(Node)→ 更新 State 中的 requirement 字段 - -技术方案(Node)→ 填好 plan - -编码实现(Node)→ 写出 code - -自测(Node)→ 发现绿点不亮,条件边判定有 bug,回到编码实现 - -再次自测通过,走到 提交CR(被 Human-in-the-loop 暂停) - -Reviewer 通过 Command 发来修改意见 - -小明走 根据CR修改 节点,改完又自测,再次提 CR - -这次 Reviewer 发了“Approve”的 Command,条件边导向 合并 - -工作流结束,小黑板上 status='done' - -整个过程,Checkpointer 保平安,Streaming 让进度透明,Human-in-the-loop 和 Command 让协作无缝,条件边 控制返工循环。 - -你看,一个开发者的日常,就是活生生的一个 LangGraph 有状态工作流。 \ No newline at end of file diff --git a/docs/open-agent-sdk-typescript.zh.md b/docs/open-agent-sdk-typescript.zh.md deleted file mode 100644 index 32a8f66..0000000 --- a/docs/open-agent-sdk-typescript.zh.md +++ /dev/null @@ -1,336 +0,0 @@ -# Open Agent SDK TypeScript 代码库说明 - -## 它是做什么的 - -`@codeany/open-agent-sdk` 是一个 TypeScript Agent SDK。它的核心目标是:在当前 Node.js 进程内运行完整的 agent loop,不依赖本地 CLI 子进程。 - -简单说,它把这些能力封装成一个可嵌入的 SDK: - -- 调用 Anthropic Messages API 或 OpenAI Chat Completions 兼容 API。 -- 让模型使用工具,比如读写文件、执行 Bash、搜索文件、访问网页、调用 MCP 工具等。 -- 支持多轮会话、会话持久化、恢复和 fork。 -- 支持自定义工具、MCP server、子 agent、skills、hooks、权限控制和自动上下文压缩。 - -典型用法是: - -```ts -import { createAgent } from "@codeany/open-agent-sdk"; - -const agent = createAgent({ model: "gpt-4o" }); -const result = await agent.prompt("What files are in this project?"); - -console.log(result.text); -``` - -也可以用流式接口: - -```ts -import { query } from "@codeany/open-agent-sdk"; - -for await (const msg of query({ - prompt: "Read package.json and tell me the project name.", - options: { - allowedTools: ["Read", "Glob"], - permissionMode: "bypassPermissions", - }, -})) { - console.log(msg); -} -``` - -## 整体架构 - -主要入口在 `src/index.ts`。它导出了高层 API、工具系统、provider、MCP、skills、hooks、session 等模块。 - -核心模块关系如下: - -```text -使用者代码 - | - | createAgent() / query() - v -Agent - | - | 创建 provider、组装工具、连接 MCP、恢复 session - v -QueryEngine - | - | agent loop - | 1. 构造 system prompt - | 2. 调用 LLM provider - | 3. 解析 tool_use - | 4. 执行工具 - | 5. 把工具结果写回 messages - | 6. 继续下一轮,直到模型不再调用工具 - v -Provider / Tools / MCP / Skills / Hooks / Session -``` - -## Agent 层 - -`src/agent.ts` 负责对外提供最常用的 API: - -- `createAgent(options)` -- `agent.query(prompt)` -- `agent.prompt(text)` -- `query({ prompt, options })` - -`Agent` 构造时主要做几件事: - -1. 读取配置和环境变量,比如 `CODEANY_API_KEY`、`CODEANY_MODEL`、`CODEANY_BASE_URL`。 -2. 根据 `apiType` 或模型名判断使用 Anthropic provider 还是 OpenAI-compatible provider。 -3. 初始化内置 skills。 -4. 根据 `tools`、`allowedTools`、`disallowedTools` 组装工具池。 -5. 连接 MCP server,把 MCP tools 合并到工具池。 -6. 如果设置了 `resume`,从磁盘加载历史 session。 - -`agent.prompt()` 是阻塞式封装,会内部消费 `agent.query()` 的流式事件,最后返回一个聚合结果。`agent.query()` 则直接返回 `AsyncGenerator`,适合做 CLI、Web UI 或实时日志。 - -## QueryEngine:核心工作循环 - -真正的 agent loop 在 `src/engine.ts` 的 `QueryEngine` 里。 - -它的执行流程是: - -1. 触发 `SessionStart`、`UserPromptSubmit` 等 hooks。 -2. 把用户 prompt 加入 `messages`。 -3. 构造 system prompt。 -4. 把工具定义转换成 provider 可理解的 schema。 -5. 调用 LLM API。 -6. 把 assistant 响应加入历史,并向外 yield `assistant` 事件。 -7. 如果响应里有 `tool_use`,执行对应工具。 -8. 把工具结果作为 `tool_result` 写回 `messages`。 -9. 继续下一轮 LLM 调用。 -10. 如果没有工具调用、达到最大轮数、预算耗尽或被中断,就结束并 yield `result`。 - -这就是它的核心机制:模型不是直接操作文件或系统,而是先输出结构化的 tool call,SDK 在本地执行工具,再把结果反馈给模型。 - -## Provider 层 - -Provider 抽象定义在 `src/providers/types.ts`。SDK 内部使用一种接近 Anthropic 的统一消息格式: - -```ts -interface LLMProvider { - readonly apiType: "anthropic-messages" | "openai-completions"; - createMessage(params: CreateMessageParams): Promise; -} -``` - -目前有两个实现: - -- `src/providers/anthropic.ts` - - 使用 `@anthropic-ai/sdk`。 - - 因为内部格式本来就接近 Anthropic,所以转换很薄。 -- `src/providers/openai.ts` - - 使用原生 `fetch` 调用 `/chat/completions`。 - - 把内部的 `tool_use` 转成 OpenAI `tool_calls`。 - - 把 OpenAI 的 `tool_calls` 再转回 SDK 内部的 `tool_use`。 - -这层的意义是把不同模型 API 的差异隔离掉,让 `QueryEngine` 只处理统一格式。 - -## 工具系统 - -工具定义类型在 `src/types.ts`: - -```ts -interface ToolDefinition { - name: string; - description: string; - inputSchema: ToolInputSchema; - call: (input: any, context: ToolContext) => Promise; - isReadOnly?: () => boolean; - isConcurrencySafe?: () => boolean; - isEnabled?: () => boolean; -} -``` - -内置工具在 `src/tools/index.ts` 统一注册,包括: - -- 文件和命令:`Read`、`Write`、`Edit`、`Bash`、`Glob`、`Grep` -- Web:`WebFetch`、`WebSearch` -- 多 agent:`Agent`、`SendMessage`、`TeamCreate`、`TeamDelete` -- 任务系统:`TaskCreate`、`TaskList`、`TaskUpdate` 等 -- MCP resource:`ListMcpResources`、`ReadMcpResource` -- planning、cron、LSP、config、todo、skill 等 - -执行工具时,`QueryEngine` 会把工具调用分成两类: - -- 只读工具:并发执行,默认最大并发数是 10。 -- 会修改状态的工具:串行执行,避免并发写入造成冲突。 - -权限控制通过 `canUseTool` 完成。如果返回 `deny`,工具不会执行,模型会收到权限拒绝结果。 - -## MCP 集成 - -MCP client 在 `src/mcp/client.ts`。 - -它支持三类外部 MCP transport: - -- `stdio` -- `sse` -- `http` - -连接 MCP server 后,SDK 会调用 `listTools()` 获取工具列表,并把每个 MCP tool 包装成 SDK 内部的 `ToolDefinition`。工具名会加命名空间: - -```text -mcp__{serverName}__{toolName} -``` - -另外,`src/sdk-mcp-server.ts` 支持 in-process MCP server。也就是用 `tool()` 定义工具,然后通过 `createSdkMcpServer()` 直接挂到 agent,不需要启动额外进程。 - -## Skills 系统 - -Skills 是可复用的 prompt 模板,代码在 `src/skills`。 - -注册中心在 `src/skills/registry.ts`,支持: - -- `registerSkill` -- `getSkill` -- `getAllSkills` -- `getUserInvocableSkills` -- `unregisterSkill` - -模型通过内置的 `Skill` 工具调用 skill。`src/tools/skill-tool.ts` 会找到对应 skill,执行 `getPrompt()`,然后把生成的 prompt 返回给模型。 - -内置 bundled skills 包括: - -- `simplify` -- `commit` -- `review` -- `debug` -- `test` - -它们本质上不是独立模型能力,而是把特定工作流整理成 prompt,让主 agent 按模板继续执行。 - -## Hooks 系统 - -Hooks 在 `src/hooks.ts`。它允许使用者在 agent 生命周期里插入逻辑,例如: - -- `SessionStart` -- `SessionEnd` -- `UserPromptSubmit` -- `PreToolUse` -- `PostToolUse` -- `PostToolUseFailure` -- `PreCompact` -- `PostCompact` - -`PreToolUse` 和 `UserPromptSubmit` 这类 hook 可以返回 `block: true`,用来阻止 prompt 或工具执行。 - -## Session 持久化 - -Session 逻辑在 `src/session.ts`。 - -会话默认保存到: - -```text -~/.open-agent-sdk/sessions/{sessionId}/transcript.json -``` - -每个 session 保存: - -- metadata:session id、cwd、model、创建时间、更新时间、消息数量等 -- messages:统一格式的对话历史 - -SDK 支持: - -- 保存 session -- 加载 session -- 列出 sessions -- fork session -- 重命名和打 tag -- 删除 session - -`Agent.close()` 时,如果 `persistSession` 没有显式关闭,会尝试保存当前会话。 - -## 上下文注入和压缩 - -`src/utils/context.ts` 会把一些项目上下文注入 system prompt: - -- 当前日期 -- git branch -- main branch -- git user -- `git status --short` -- 最近 5 条 commit -- `AGENT.md`、`CLAUDE.md`、`.claude/CLAUDE.md` 等项目说明文件 -- 当前工作目录 - -`src/utils/compact.ts` 和 `src/utils/tokens.ts` 负责上下文管理: - -- 估算 token 数量和成本。 -- 当上下文接近模型窗口上限时,触发 auto-compact。 -- 对超大的工具结果做 micro-compact,避免一次工具输出撑爆上下文。 - -## 自定义工具 - -SDK 提供两套工具定义方式。 - -低层方式是 `defineTool()`: - -```ts -const calculator = defineTool({ - name: "Calculator", - description: "Evaluate a math expression", - inputSchema: { - type: "object", - properties: { - expression: { type: "string" }, - }, - required: ["expression"], - }, - async call(input) { - return String(eval(input.expression)); - }, -}); -``` - -高层方式是 `tool()`,使用 Zod schema,代码在 `src/tool-helper.ts`: - -```ts -const getWeather = tool( - "get_weather", - "Get weather for a city", - { city: z.string() }, - async ({ city }) => ({ - content: [{ type: "text", text: `${city}: sunny` }], - }), -); -``` - -`tool()` 定义的工具可以通过 `createSdkMcpServer()` 变成 in-process MCP server。 - -## 一次请求的完整链路 - -以 `agent.prompt("Read package.json")` 为例: - -1. 使用者调用 `createAgent()` 创建 agent。 -2. Agent 读取配置,创建 provider,准备工具池。 -3. `prompt()` 调用内部的 `query()`。 -4. `query()` 创建 `QueryEngine`。 -5. `QueryEngine` 构造 system prompt,包括工具列表、git 状态、项目说明文件等。 -6. `QueryEngine` 调用 provider。 -7. 模型返回 `tool_use: Read`。 -8. `QueryEngine` 找到 `Read` 工具并执行本地文件读取。 -9. 工具结果作为 `tool_result` 放回对话历史。 -10. `QueryEngine` 再次调用模型。 -11. 模型根据文件内容生成最终回答。 -12. SDK 返回 `QueryResult`,包含文本、token usage、turn 数、消息历史等。 - -## 代码库定位 - -这个项目可以理解为“可嵌入应用里的 coding-agent/runtime SDK”。 - -它不是一个纯 API client,也不是简单聊天 SDK。它包含完整的 agent runtime: - -- 模型调用 -- tool calling -- 工具执行 -- 会话状态 -- 权限控制 -- MCP 扩展 -- skills 工作流 -- hooks 生命周期 -- 上下文注入和压缩 - -也正因为它把 agent loop 放在 SDK 内部运行,所以适合嵌入到 Web 服务、CI/CD、Docker、serverless 或自定义开发者工具中。 diff --git a/docs/references.md b/docs/references.md deleted file mode 100644 index f08140b..0000000 --- a/docs/references.md +++ /dev/null @@ -1,10 +0,0 @@ - - -- https://github.com/superagent-ai/vibekit -- https://github.com/codeany-ai/open-agent-sdk-typescript -- https://github.com/langgenius/dify - - -base: -- https://github.com/anthropics/claude-agent-sdk-typescript -- https://github.com/openai/codex