构建在 pi-ai 之上的有状态 Agent,提供工具执行和事件流。

安装

npm install @earendil-works/pi-agent-core

快速开始

import { Agent } from "@earendil-works/pi-agent-core";
import { getModel } from "@earendil-works/pi-ai";
 
const agent = new Agent({
  initialState: {
    systemPrompt: "You are a helpful assistant.",
    model: getModel("anthropic", "claude-sonnet-4-20250514"),
  },
});
 
agent.subscribe((event) => {
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});
 
await agent.prompt("Hello!");

核心概念

AgentMessage vs LLM Message

Agent 内部使用 AgentMessage,包含:

  • 标准 LLM 消息(user、assistant、toolResult)
  • 通过声明合并扩展的自定义 app 消息类型

LLM 只理解标准类型,convertToLlm 函数在每次 LLM 调用前负责过滤和转换。

消息处理管线

AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM
                    (可选,裁剪/注入)                   (必须,过滤自定义类型)

事件流

无工具调用

prompt("Hello")
├─ agent_start
├─ turn_start
├─ message_start   { message: userMessage }
├─ message_end     { message: userMessage }
├─ message_start   { message: assistantMessage }
├─ message_update  { assistantMessageEvent: { type: "text_delta", delta: "..." } }
...
├─ message_end     { message: assistantMessage }
├─ turn_end        { message, toolResults: [] }
└─ agent_end       { messages: [...] }

带工具调用

prompt("Read config.json")
├─ agent_start
├─ turn_start
├─ message_start/end   { userMessage }
├─ message_start/end   { assistantMessage with toolCall }
├─ tool_execution_start   { toolCallId, toolName, args }
├─ tool_execution_update  { partialResult }      // 工具支持流式时
├─ tool_execution_end     { toolCallId, result }
├─ message_start/end   { toolResultMessage }
├─ turn_end            { message, toolResults: [toolResult] }
│
├─ turn_start                                    // 下一轮:LLM 处理工具结果
├─ message_start/end   { assistantMessage }
├─ turn_end
└─ agent_end

工具执行模式

模式行为
parallel(默认)预检串行,执行并发;tool_execution_end 按完成顺序发出,toolResult 消息和 turn_end.toolResults 按 LLM 源顺序排列
sequential工具逐个执行,保留历史兼容行为

若批次中任一工具设置了 executionMode: "sequential",整批退化为串行执行。

Hooks

  • beforeToolCall:在 tool_execution_start 之后、参数验证完毕后运行,可阻止执行(返回 { block: true, reason: "..." })
  • afterToolCall:在工具执行结束后、tool_execution_end 事件发出前运行,可修改结果或返回 { terminate: true }

终止行为

工具可以返回 terminate: true 跳过后续 LLM 调用。仅当批次中所有工具结果都设置 terminate: true 时,循环才提前终止;混合批次照常继续。

continue()

从现有 context 恢复,不添加新消息(用于错误后重试):

await agent.continue();

要求:context 最后一条消息必须是 user 或 toolResult(不能是 assistant)。

完整事件类型

事件说明
agent_startAgent 开始处理
agent_end最终事件,携带所有消息
turn_start新轮次开始(一次 LLM 调用 + 工具执行)
turn_end轮次结束,携带 assistant 消息和 toolResults
message_start任意消息开始(user/assistant/toolResult)
message_update仅 assistant,携带 assistantMessageEvent(delta)
message_end消息完成
tool_execution_start工具开始,携带 toolCallId、toolName、args
tool_execution_update工具流式进度
tool_execution_end工具完成,携带 result、isError

Agent Options

const agent = new Agent({
  initialState: {
    systemPrompt: string,
    model: Model<any>,
    thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh",
    tools: AgentTool<any>[],
    messages: AgentMessage[],
  },
  convertToLlm: (messages) => messages.filter(...),
  transformContext: async (messages, signal) => pruneOldMessages(messages),
  steeringMode: "one-at-a-time" | "all",
  followUpMode: "one-at-a-time" | "all",
  streamFn: streamProxy,          // 自定义流函数(代理后端)
  sessionId: "session-123",       // Provider 缓存用
  getApiKey: async (provider) => refreshToken(),  // 动态 API Key(OAuth token 刷新)
  toolExecution: "parallel" | "sequential",
  beforeToolCall: async ({ toolCall, args, context }) => { ... },
  afterToolCall: async ({ toolCall, result, isError, context }) => { ... },
  thinkingBudgets: { minimal: 128, low: 512, medium: 1024, high: 2048 },
});

shouldStopAfterTurn(低层 agentLoop)

低层调用者可通过 shouldStopAfterTurn 在当前轮次结束后优雅停止:

const stream = agentLoop(prompts, context, {
  shouldStopAfterTurn: async ({ message, toolResults, context, newMessages }) => {
    return shouldCompactBeforeNextTurn(context.messages);
  },
});

在 turn_end 发出后运行;返回 true 时发出 agent_end 并退出,不启动下一轮 LLM 调用。

相关页面:pi-ai | pi-coding-agent