2026-05-17

05月17日

一、今日完成情况

  • 中兴简历提交
  • 通读Pi的源码

二、今日感悟

  • 核心业务数据​:
  • ​今日工作总结:​
  • ​明日工作计划:
  • ​今日学习成长:​

三、备注

四、架构梳理

概览

Pi 是一个基于 ReAct(Reasoning + Acting)框架的代码生成代理项目,采用 monorepo 结构,包含 5 个核心包:

职责
packages/ai 统一的多提供商 LLM API(OpenAI、Anthropic、Google、Azure 等)
packages/agent Agent 运行时核心,实现 ReAct 循环、工具调用、状态管理
packages/coding-agent 交互式 CLI 应用,封装 agent 和 ai 包
packages/tui 终端 UI 库(差分渲染)
packages/web-ui Web 组件

ReAct 框架核心实现(packages/agent)

双层循环结构

外部循环 (Outer Loop)
├── 继续条件:followUp 队列有消息
└── 退出:没有 followUp 消息时退出

    内部循环 (Inner Loop)
    ├── 继续条件:hasMoreToolCalls || pendingMessages > 0
    ├── 处理 steering 消息注入
    ├── streamAssistantResponse() 获取助手响应
    ├── executeToolCalls() 执行工具调用
    └── 触发 shouldStopAfterTurn 检查

关键设计:AgentMessage → Message[] 转换边界

核心设计理念:在 LLM 调用边界才将 AgentMessage 转换为 Message[]

// agent-loop.ts - streamAssistantResponse()
async function streamAssistantResponse(context, config, ...) {
  // 1. 上下文转换(可选)
  let messages = context.messages;
  if (config.transformContext) {
    messages = await config.transformContext(messages, signal);
  }
  
  // 2. AgentMessage[] → LLM Message[](关键转换点)
  const llmMessages = await config.convertToLlm(messages);
  
  // 3. 调用 LLM
  const response = await streamSimple(config.model, { messages: llmMessages, ... }, config);
}

工具执行模式

  • 顺序执行:工具串行执行,一个完成再执行下一个
  • 并行执行:预检全部工具后,允许的工具并发执行,tool_execution_end 按完成顺序触发

核心类型(types.ts)

  • AgentMessage:Agent 层面的消息(支持自定义扩展)
  • AgentContext:传递给循环的上下文(systemPrompt、messages、tools)
  • AgentLoopConfig:循环配置(convertToLlm、transformContext、getApiKey、getSteeringMessages、getFollowUpMessages、beforeToolCall、afterToolCall 等)
  • AgentEvent:生命周期事件(agent_start/agent_end、turn_start/turn_end、message_start/message_end、tool_execution_start/update/end)

AI 层架构(packages/ai)

多提供商支持

通过 ApiKnownProvider 类型系统支持多种 LLM:

type Api = "openai-completions" | "anthropic-messages" | "bedrock-converse-stream" | ...;
type KnownProvider = "anthropic" | "google" | "openai" | "amazon-bedrock" | ...;

核心导出

  • streamSimple():简化的流式调用(封装了完整的流处理逻辑)
  • stream():底层流式调用
  • complete() / completeSimple():非流式调用
  • EventStream:基于事件的流处理

Coding Agent 层(packages/coding-agent)

架构层次

src/
├── core/          # 核心模块(Agent、Session、Messages、ModelResolver)
├── cli/           # CLI 参数解析
├── modes/         # 运行模式(交互、打印、RPC)
├── utils/         # 工具函数
└── main.ts        # CLI 主逻辑

与下层的关系

// core/sdk.ts
const agent = new Agent({
  streamFn: async (model, context, options) => {
    return streamSimple(model, context, options);
  },
  convertToLlm: transformMessages,
  // ...
});

状态模型与生命周期

根据 agent-harness.md,AgentHarness 将状态分为:

  1. Harness Config:运行时配置(model、thinking level、tools、resources)
  2. Turn Snapshot:单次 LLM 调用使用的快照
  3. Session:持久化的会话条目
  4. Pending Session Writes:操作期间排队的写入

关键概念:

  • phase:操作阶段(idle、turn、compaction、branch_summary、retry)
  • save point:助手回复+工具结果完成后保存快照
  • shouldStopAfterTurn:决定是否继续的钩子

数据流总结

用户输入
    ↓
coding-agent (CLI) → AgentHarness (高级封装)
    ↓
agent-loop (ReAct 循环)
    ├─ getSteeringMessages() / getFollowUpMessages()
    ├─ transformContext() [可选]
    ├─ convertToLlm() [AgentMessage → Message]
    ├─ streamSimple() [调用 LLM]
    ├─ 处理 tool_calls
    └─ shouldStopAfterTurn() / prepareNextTurn()
    ↓
packages/ai (多提供商 LLM API)
    ├─ OpenAI
    ├─ Anthropic
    ├─ Google
    ├─ Azure
    ├─ Amazon Bedrock
    └─ ...其他提供商

Pi 不仅仅是对话,它是一个完整的编码代理(coding agent)。

内置工具

pi-test.sh --help 可以看到,pi 内置了以下工具:

工具 功能
read 读取文件内容
bash 执行 shell 命令
edit 编辑文件(修改部分内容)
write 写入/创建文件

ReAct 框架的能力

基于 ReAct 循环,pi 可以:

  1. 理解用户需求(如”帮我修改 README.md 中的某段文字”)
  2. 调用 read 工具读取文件
  3. 分析内容后决定如何修改
  4. 调用 editwrite 工具修改文件
  5. 验证修改结果

内置工具位置

工具 文件路径
read packages/coding-agent/src/core/tools/read.ts
bash packages/coding-agent/src/core/tools/bash.ts
edit packages/coding-agent/src/core/tools/edit.ts
write packages/coding-agent/src/core/tools/write.ts

工具注册入口

  • 统一导出packages/coding-agent/src/core/tools/index.ts
  • 工具定义创建:在 index.ts 中通过 createToolDefinition() 注册
// packages/coding-agent/src/core/tools/index.ts
export type ToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls";

工具调用流程

  1. LLM 返回 tool_callpackages/ai/src/providers/openai-completions.ts 解析 OpenAI 格式的 tool_calls
  2. Agent 循环处理packages/agent/src/agent-loop.tsexecuteToolCalls()
  3. 工具执行packages/coding-agent/src/core/tools/ 下的各工具文件
  4. 结果返回 LLMToolResultMessage 格式

当前卡住原因

问题在步骤 1:Qwen3 的 qwen3_xml parser 输出的 tool_call 格式与 pi 解析器期望的格式不匹配。

如果你需要调试工具调用,可以查看 packages/ai/src/providers/openai-completions.ts 中的 ensureToolCallBlock() 和工具解析逻辑。

扩展机制

项目还支持通过 packages/coding-agent/src/core/extensions/ 扩展更多工具:

  • 内置扩展:prompt-url-widget.ts, redraws.ts, tps.ts
  • 支持用户自定义扩展

配置方式

工具可以在启动时配置:

  • -nt / --no-tools:禁用所有工具
  • -nbt / --no-builtin-tools:禁用内置工具,保留扩展工具
  • -t <tools>:白名单模式,只启用指定工具

简单说:pi 是一个能够读写文件、执行命令、帮你写代码的 AI 助手,不是纯对话聊天机器人。

五、vllm参数 – 工具调用

1、模型启动

Qwen3-32B正确的工具解析器是:qwen3_xml

脚本启动方式.sh当中的参数修改如下:

exec vllm serve "${MODEL}" \
  --host "${HOST}" \
  --port "${PORT}" \
  --served-model-name "${SERVED_MODEL_NAME}" \
  --reasoning-parser qwen3 \
  --max-model-len "${MAX_MODEL_LEN}" \
  --gpu-memory-utilization "${GPU_MEMORY_UTILIZATION:-0.92}" \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_xml

启动xml工具,才可以让大模型可以读取本地的项目或者方案,添加参数之后重新启动:

pkill -f vllm

# 重新启动脚本
/root/Documents/code/python/scripts/serve_qwen3_32b.sh

2、xml格式工具调用

curl -s "https://llm.litearch.cn/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer LITEARCH_LLM_2026_SAFE_KEY" \
  -d '{
    "model": "Qwen/Qwen3-32B",
    "messages": [
      {"role": "user", "content": "请读取当前目录下的README.md文件"}
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "read_file",
          "description": "读取文件内容",
          "parameters": {
            "type": "object",
            "properties": {
              "file_path": {"type": "string", "description": "文件路径"}
            },
            "required": ["file_path"]
          }
        }
      }
    ],
    "tool_choice": "auto"
  }' | jq .
{
  "id": "chatcmpl-9445e83d212c1c4b",
  "object": "chat.completion",
  "created": 1779007966,
  "model": "Qwen/Qwen3-32B",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "\n\n<tool_call>\n{\"name\": \"read_file\", \"arguments\": {\"file_path\": \"README.md\"&#125;&#125;\n</tool_call>",
        "refusal": null,
        "annotations": null,
        "audio": null,
        "function_call": null,
        "tool_calls": [],
        "reasoning": "\n好的,用户让我读取当前目录下的README.md文件。我需要使用提供的read_file工具来完成这个任务。首先,我需要确认工具的参数要求。工具需要一个file_path参数,类型是字符串,描述是文件路径。用户提到的文件是当前目录下的README.md,所以文件路径应该是相对于当前工作目录的。\n\n接下来,我需要确保路径的正确性。如果用户是在项目根目录下执行这个请求,那么路径可能只是“README.md”。但如果当前目录有子目录,可能需要调整路径。不过用户明确说“当前目录下”,所以应该直接使用“README.md”作为文件路径。然后,我需要构造一个符合工具要求的JSON对象,调用read_file函数,参数是file_path: \"README.md\"。最后,将这个调用包装在指定的XML标签中返回。确保没有其他多余的信息,只返回工具调用的部分。\n"
      },
      "logprobs": null,
      "finish_reason": "stop",
      "stop_reason": null,
      "token_ids": null
    }
  ],
  "service_tier": null,
  "system_fingerprint": null,
  "usage": {
    "prompt_tokens": 163,
    "total_tokens": 380,
    "completion_tokens": 217,
    "prompt_tokens_details": null
  },
  "prompt_logprobs": null,
  "prompt_token_ids": null,
  "kv_transfer_params": null
}

这是 Qwen3 模型专属的 XML 工具调用格式,证明:

  • vLLM 启动参数 --tool-call-parser qwen3_xml 正常工作
  • 模型成功识别了你的工具请求

"name": "read_file"
模型精准听懂了你的指令:

请读取当前目录下的 README.md 文件

自动选择了 read_file(读取文件工具)

"arguments": {"file_path": "README.md"}
模型自动填充了正确参数

  • 文件路径:README.md
  • 完全符合你定义的工具规则
  • 逻辑完全正确

"finish_reason": "stop"
模型正常完成推理,没有报错、没有崩溃、没有卡住

reasoning 字段:
这是模型的思考过程,它明确告诉你:

我要调用 read_file 工具,读取 README.md 文件

六、PI项目.json配置文件

{
  "providers": {
    "litearch": {
      "name": "LiteArch LLM",
      "baseUrl": "https://llm.litearch.cn/v1",
      "apiKey": "LITEARCH_LLM_2026_SAFE_KEY",
      "api": "openai-completions",
      "compat": {
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false,
        "supportsStrictMode": false,
        "supportsStore": false,
        "thinkingFormat": "qwen"
      },
      "models": [
        {
          "id": "Qwen/Qwen3-32B",
          "name": "Qwen3-32B",
          "reasoning": true,
          "contextWindow": 32000,
          "maxTokens": 8192
        }
      ]
    }
  }
}
CleanShot 2026-05-17 at 16.28.06@2x

测试语言模式:

 cp .pi/models.json ~/.pi/agent/models.json
./pi-test.sh --provider litearch --model "Qwen/Qwen3-32B" -p "你好,做个自我介绍" -nt

回复答案如下:

(APIServer pid=9693) INFO:     39.144.137.50:0 - "POST /v1/chat/completions HTTP/1.0" 200 OK

I am a coding assistant operating within the pi coding agent harness. My capabilities include:

- Reading/writing files (showing full paths)
- Executing commands in the terminal
- Editing code with type-aware suggestions
- Creating new files and directories
- Following strict development rules for this project

I adhere to your project's guidelines for:
- Code quality (no `any` types, standard imports)
- Git safety (atomic commits, no force pushes)
- Testing (vitest, faux provider)
- Documentation (CHANGELOG format)

I can help with:
- Implementing features
- Debugging issues
- Writing tests
- Maintaining code quality
- Following your contribution workflow

What would you like me to help with today?

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 kipleyarch@gmail.com
Archive PDF预览 PPTX Obsidian