从底层看懂 Pi-Mono:Agent 与 AI 核心机制揭秘

本文分析 pi-mono 仓库中两个核心底层模块:
packages/agent:负责 Agent 运行时、消息状态和工具调用循环。packages/ai:负责统一不同 LLM Provider 的调用方式和流式响应协议。
如果把 pi 看成一个 AI 编码助手,那么 coding-agent 是产品层,agent 是智能体运行层,ai 是模型接入层。
一、整体调用关系
典型运行链路如下:
用户 / Slack / Web UI / 其他上层入口
-> packages/coding-agent 或 packages/mom
-> packages/agent
-> packages/ai
-> OpenAI / Anthropic / Google / Mistral / Bedrock / 其他模型服务
可以用一句话概括:
packages/agent 决定 Agent 怎么运行;
packages/ai 决定模型请求怎么发出去、模型响应怎么统一回来。
两者的边界比较清楚:
agent不关心 OpenAI、Anthropic、Google 的具体 API 差异。ai不关心 Agent 什么时候该继续、什么时候该执行工具。- 上层应用负责提供工具、会话、UI、鉴权和业务逻辑。
二、packages/agent:Agent 运行时
packages/agent 是通用 Agent Runtime。它不包含终端 UI,也不直接实现读文件、写文件、执行 Shell 这类业务工具。它只定义“工具应该长什么样”,并负责在模型要求调用工具时调度这些工具。
2.1 模块职责
packages/agent 主要负责:
- 保存 Agent 状态:系统提示词、当前模型、思考级别、工具列表、消息历史、流式状态、执行中的工具调用和错误信息。
- 接收新的用户输入,并启动一次 Agent 运行。
- 从已有上下文继续执行。
- 调用
packages/ai获取模型的流式响应。 - 识别模型返回的 tool call。
- 校验工具参数并执行工具。
- 把工具结果写回消息上下文。
- 向上层 UI 或 Session 层发出生命周期事件。
- 支持运行中插入 steering message 和运行结束前追加 follow-up message。
2.2 关键文件
packages/agent/src/index.ts:模块公共导出。packages/agent/src/types.ts:核心类型定义,例如AgentMessage、AgentTool、AgentEvent、AgentState。packages/agent/src/agent.ts:有状态的Agent类,是上层最常用的入口。packages/agent/src/agent-loop.ts:底层 Agent 循环,真正驱动“模型响应 -> 工具执行 -> 再次模型响应”。packages/agent/src/proxy.ts:可选的代理流实现,用于把模型请求转发到自建服务。
2.3 Agent 类
Agent 是 packages/agent 对外最核心的类。上层一般通过它启动和控制一次 Agent 会话。
它提供的主要能力:
prompt(...):从文本或消息开始一次新的 Agent 运行。continue():从当前消息历史继续执行。abort():中断当前运行。subscribe(...):订阅 Agent 生命周期事件。steer(...):在当前 assistant turn 结束、工具执行完成后插入新消息。followUp(...):在 Agent 本来准备停止时追加一条后续消息。waitForIdle():等待当前运行和事件监听器全部结束。
Agent 自己维护状态,但真正的循环执行会委托给 agent-loop.ts 中的 runAgentLoop() 或 runAgentLoopContinue()。
2.4 Agent Loop 运行流程
agent-loop.ts 是 Agent 行为的核心。简化后的流程如下:
1. 接收用户消息,把它加入上下文。
2. 发出 agent_start、turn_start、message_start、message_end 等事件。
3. 如果配置了 transformContext,先转换 AgentMessage[]。
4. 把 AgentMessage[] 转成 packages/ai 能理解的 Message[]。
5. 调用 streamSimple() 或自定义 streamFn 请求模型。
6. 流式接收 assistant message。
7. 如果 assistant message 包含 toolCall,则进入工具执行。
8. 工具执行完成后,生成 toolResult message。
9. 把 toolResult 加回上下文,再次请求模型。
10. 如果没有 toolCall、steering message、follow-up message,则结束。
11. 发出 agent_end 事件。
关键设计点是:agent-loop 内部一直使用 AgentMessage[],只有在调用 LLM 的边界才转换成 packages/ai 的 Message[]。
2.5 工具执行机制
工具由上层传入,类型是 AgentTool。packages/agent 不知道某个工具是不是“读文件”或“执行命令”,它只知道这个工具有名字、参数 schema 和执行函数。
一个工具通常包含:
name:模型调用时使用的工具名。description:工具描述。parameters:参数 schema。label:给 UI 展示的人类可读名称。execute(...):真正执行工具逻辑。prepareArguments(...):可选,用于在参数校验前修正模型给出的原始参数。executionMode:可选,用于指定工具是否必须顺序执行。
工具执行期间会发出这些事件:
tool_execution_start
tool_execution_update
tool_execution_end
默认情况下,同一个 assistant message 里的多个 tool call 可以并行执行。如果全局配置为顺序执行,或者某个工具声明 executionMode: "sequential",则会按顺序执行。
三、packages/ai:统一 LLM 调用层
packages/ai 是模型适配层。它不运行 Agent,也不执行工具;它负责把不同模型服务的 API 抽象成一套统一接口。
不同厂商的 API 差异很大,例如:
- 消息格式不同。
- tool call 格式不同。
- reasoning / thinking 的表达方式不同。
- 流式事件协议不同。
- usage 和 cost 统计不同。
- 图片、缓存、headers、OAuth、API key 处理方式不同。
packages/ai 的作用就是把这些差异收敛起来,让上层只面对一套统一协议。
3.1 模块职责
packages/ai 主要负责:
- 定义统一的模型、消息、工具、内容块、用量和流式事件类型。
- 提供
stream()、complete()、streamSimple()、completeSimple()。 - 注册内置 Provider。
- 懒加载各 Provider 实现。
- 提供模型查询能力,例如
getModel()、getModels()、getProviders()。 - 从环境变量或其他凭据来源解析 API key。
- 把各 Provider 的流式响应转换成统一的
AssistantMessageEventStream。 - 处理跨 Provider 兼容问题,例如图片降级、thinking block 转换、tool call id 归一化等。
3.2 关键文件
packages/ai/src/index.ts:模块公共导出。packages/ai/src/types.ts:统一类型定义,是理解该模块的入口。packages/ai/src/stream.ts:对外的模型调用入口。packages/ai/src/api-registry.ts:API Provider 注册表。packages/ai/src/models.ts:模型查询和 cost 计算。packages/ai/src/models.generated.ts:自动生成的内置模型元数据。packages/ai/src/env-api-keys.ts:环境变量和凭据检测。packages/ai/src/providers/register-builtins.ts:内置 Provider 的注册和懒加载。packages/ai/src/providers/*:各模型厂商的具体适配实现。
3.3 统一消息协议
核心类型在 types.ts 中。
统一消息分为三类:
UserMessage:用户消息。AssistantMessage:模型返回的 assistant 消息。ToolResultMessage:工具执行结果消息。
Assistant 消息内容又分为:
text:普通文本。thinking:推理或思考内容。toolCall:模型要求调用工具。
统一流式事件包括:
start
text_start / text_delta / text_end
thinking_start / thinking_delta / thinking_end
toolcall_start / toolcall_delta / toolcall_end
done
error
这套协议的意义在于:无论底层是 OpenAI、Anthropic、Google 还是 Bedrock,上层 packages/agent 都可以用同一种方式消费模型响应。
3.4 stream 与 streamSimple
stream.ts 提供两层接口:
stream(model, context, options):更底层,面向 Provider 特定参数。streamSimple(model, context, options):更通用,面向 Agent 和应用层。
packages/agent 默认调用的是 streamSimple()。
streamSimple() 支持的通用配置包括:
apiKeytemperaturemaxTokensreasoningthinkingBudgetssessionIdcacheRetentionheadersonPayloadonResponsesignal
调用分发流程如下:
streamSimple(model, context, options)
-> 根据 model.api 找到对应 Provider
-> 调用 provider.streamSimple(model, context, options)
-> Provider 适配器调用真实模型 API
-> 返回统一 AssistantMessageEventStream
3.5 Provider 注册机制
api-registry.ts 维护一个注册表:
api 名称 -> Provider 实现
内置映射大致如下:
openai-responses -> providers/openai-responses.ts
openai-completions -> providers/openai-completions.ts
anthropic-messages -> providers/anthropic.ts
google-generative-ai -> providers/google.ts
google-gemini-cli -> providers/google-gemini-cli.ts
google-vertex -> providers/google-vertex.ts
mistral-conversations -> providers/mistral.ts
bedrock-converse-stream -> providers/amazon-bedrock.ts
providers/register-builtins.ts 会注册这些内置 Provider,并且使用懒加载方式加载具体实现。这样做可以避免一启动就加载所有厂商 SDK,也能减轻浏览器环境或非 Node 环境的兼容压力。
3.6 模型查询机制
models.ts 从 models.generated.ts 初始化内置模型注册表。
常用方法:
getModel(provider, modelId):获取一个内置模型。getModels(provider):获取某个 Provider 下的所有内置模型。getProviders():获取所有内置 Provider。calculateCost(model, usage):根据模型价格计算 cost。supportsXhigh(model):判断模型是否支持xhigh推理级别。
需要注意的是,在 coding-agent 正常运行时,模型选择通常不直接用 getModel(),而是走 ModelRegistry。ModelRegistry 会基于 getModels() 和 getProviders() 构建模型列表,并叠加自定义模型、配置覆盖和鉴权状态。
四、两个模块如何协作
一次典型 Agent 运行可以拆成两层:
packages/agent:
管理消息上下文
判断是否需要继续
执行工具
发出生命周期事件
packages/ai:
选择 Provider
构造模型请求
调用真实模型 API
把响应转成统一事件流
更完整的链路如下:
用户输入
-> 上层应用创建 UserMessage
-> Agent.prompt()
-> runAgentLoop()
-> convertToLlm()
-> streamSimple()
-> provider.streamSimple()
-> LLM Provider
-> AssistantMessageEventStream
-> Agent 识别 toolCall
-> 执行 AgentTool
-> 生成 ToolResultMessage
-> 再次请求模型
-> 输出最终 AssistantMessage
这也是为什么这两个模块可以复用到不同产品入口里:
coding-agent用它们实现piCLI。mom用它们实现 Slack bot。web-ui可以复用它们的类型、模型调用和示例逻辑。
五、总结
理解这两个模块时,可以始终记住这个分工:
packages/agent
负责 Agent 状态、运行循环、工具调度和事件通知。
packages/ai
负责模型元数据、Provider 选择、真实 API 调用和响应归一化。
如果问题是“Agent 什么时候继续、什么时候调用工具、工具结果怎么进入上下文”,应该看 packages/agent。
如果问题是“某个模型请求怎么发给 OpenAI/Anthropic/Google、流式响应怎么被解析”,应该看 packages/ai。
这两个模块共同构成了 pi 编码助手的底层能力:agent 负责智能体行为,ai 负责模型连接。上层的 coding-agent 再把它们组合成一个可交互、可使用工具、可管理会话的 AI 编码产品。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)