# pi-agent **Repository Path**: tp5test/pi-agent ## Basic Information - **Project Name**: pi-agent - **Description**: pi-agent-go - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-31 - **Last Updated**: 2026-08-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # pi-agent(Go + Eino) 基于 [CloudWeGo Eino](https://github.com/cloudwego/eino) 的本地智能体(Agent)。 核心理念源自极简 pi-agent 设计:**ReAct 循环 + 统一 Tool 抽象 + 会话即状态**, 用 Eino 的 `react.NewAgent` 作为运行时,把 Native / MCP / Skill 三类工具统一对 Agent 透明。 > 运行时:`eino` / `eino-ext` >= 0.9(本工程已验证 v0.9.13 / eino-ext mcp v0.0.8)。 --- ## 特性 - **ReAct 闭环**:`eino/flow/agent/react` 自动处理 Think → Act → Observe → END,支持工具调用。 - **模型接入**:OpenAI 兼容协议,默认 DeepSeek(`https://api.deepseek.com`),可在 `config.yaml` 切换。 - **统一工具生态**: - Native:`bash` / `read` / `write` / `edit`(自动推断参数 schema,带工作目录越界防护)。 - MCP:通过 mark3labs/mcp-go 接入任意 MCP server(stdio / sse / streamable_http)。 - Skills:YAML 编排的"工具序列",整体包装为一个工具对 Agent 透明。 - **会话即状态**:`session.Store` 以 JSONL(每会话一文件)存储,支持分支(`/new`)、回溯、压缩。 - **记忆策略**:工作记忆滑动窗口(注入最近 N 轮);语义/长期记忆为可插拔 `Retriever` 接口(本期不接向量库)。 - **可观测**:零外部依赖的**文件 JSONL 追踪日志**,按 `trace_id` 串起一次请求的完整调用链 (请求到达 → 模型思考 → 工具调用 → 工具结果 → 模型输出 → 请求结束,含总耗时), 与 zap 异常日志分离写入独立文件,后续可自行采集接入平台。 追踪分三层、各司其职(见 `internal/trace/` 与 `internal/agent/tool_callback.go`): - **全局组件回调**(`trace/callback.go`):`callbacks.AppendGlobalHandlers` 注册一次,覆盖所有 eino 组件(ChatModel/Tool/Graph)的 start/end/error(含 token_usage)。 - **工具回调**(`agent/tool_callback.go`):每次请求注册,捕获工具调用/结果用于 SSE 推送与会话持久化,并写入请求级 trace。 - **HTTP 回调**(`server/http.go` 的 SSE writer):把流事件转成 SSE 帧。三层由 `session_id` + `trace_id` 串联。 - **双入口共用一套逻辑**:本地 `chat` CLI 与 HTTP `serve` 共用 `agent.Service`。 --- ## 快速开始 ### 1. 准备 ```bash go >= 1.22 # 设置 API Key(DeepSeek 默认) export DEEPSEEK_API_KEY="sk-xxxx" # 可选:HTTP 鉴权 Token(留空则不鉴权) export PI_AGENT_TOKEN="" ``` ### 2. 本地交互(CLI) ```bash go run ./cmd/pi-agent chat # 或先编译 go build -o pi-agent ./cmd/pi-agent && ./pi-agent chat ``` CLI 命令:`/new` 新建会话 · `/tools` 查看工具 · `/help` 帮助 · `/exit` 退出。 任意其它输入作为用户消息发送给 Agent,支持工具调用与流式输出。 ### 3. HTTP 服务 ```bash ./pi-agent serve ``` **聊天页面**:浏览器打开 `http://localhost:8082/chat` 即可使用图形化聊天界面: - 页面为**独立文件** `web/chat.html`(项目根目录),可直接编辑样式/交互,**无需重新编译**即生效(服务需从项目根目录启动,与 `config.yaml` 同理) - SSE 流式输出,正文 / 思考过程 / 工具调用 / 工具结果**按颜色区分**(蓝=思考、橙=工具调用、绿=工具结果) - 思考与工具面板渲染时展开、**流结束自动收起**,点击标题可重新展开查看 - 顶部可输入 Session ID 切换会话,点「历史」加载该会话过往记录 > **DeepSeek 流式工具调用修复**:DeepSeek 在准备调用工具时,往往先流式输出一句"我先看看…"再发 `tool_calls`。eino react 默认判定器只看首 chunk,会误判"无工具调用"导致流在句子后中断。已通过 `react.AgentConfig.StreamToolCallChecker`(读完整个流再判定)修复,工具正常执行并返回完整答案。验证见 `internal/agent/checker_repro_test.go`。 接口: ```bash # 非流式 curl -X POST http://localhost:8082/v1/chat \ -H "Content-Type: application/json" \ -d '{"session_id":"u1","message":"用 bash 看一下当前目录"}' # 流式(SSE,结构化事件:content/thinking/tool_call/tool_result/done/error) curl -X POST http://localhost:8082/v1/chat \ -H "Content-Type: application/json" \ -d '{"session_id":"u1","message":"你好","stream":true}' # 获取会话历史 curl http://localhost:8082/v1/sessions/u1/history ``` `/health` 可用于探活。鉴权开启时需在 Header 带 `Authorization: Bearer `。 --- ## 配置(config.yaml) 关键字段(完整见 `config.yaml`): | 段 | 说明 | |----|------| | `llm` | `base_url` / `api_key`(支持 `${ENV}`) / `model` / `temperature` / `max_steps` | | `session` | `dir` 会话 JSONL 目录;`compression` 压缩阈值与保留轮数 | | `memory.working` | 滑动窗口 `window`;`semantic` 语义记忆(本期关闭) | | `tools.native` | 是否启用;`workspace` 工具根目录约束 | | `tools.mcp` | 是否启用;`config_file` 指向 `mcp_servers.yaml` | | `tools.skills` | 是否启用;`dir` 为 Skill YAML 目录 | | `trace` | `enabled` / `file`(JSONL,按天滚动)。记录请求级调用链(独立文件,不与异常日志混) | | `server` | `host` / `port` / `auth_token` / `rate_limit` | --- ## 扩展 ### 新增 Skill 在 `skills/` 目录放一个 YAML(见 `skills/file-summary.yaml`)或 Markdown(见 `skills/code_explainer.md`)。 **YAML(纯结构化):** ```yaml name: my_skill description: 做什么 input_schema: type: object properties: path: { type: string, description: 文件路径 } required: [path] steps: - tool: read args: { path: "{{path}}" } output: content - tool: __llm__ prompt: "概括:{{content}}" output: summary return: "{{summary}}" ``` **Markdown(frontmatter + 可读正文):** 当 Skill 的说明 / prompt 较长时更友好。 结构化字段写在 `---` 包裹的 YAML frontmatter 里,frontmatter 之后的正文存入 `Notes`(仅作人类可读说明,不参与执行): ```markdown --- name: my_skill description: 做什么 input_schema: type: object properties: path: { type: string } required: [path] steps: - tool: read args: { path: "{{path}}" } output: content return: "{{content}}" --- # 我的 Skill 这里是给维护者看的详细说明…… ``` 步骤间用 `{{var}}` 传递;`__llm__` 为内置一步(复用同一 ChatModel)。 ### 接入 MCP 编辑 `mcp_servers.yaml`(见示例),把 `config.yaml` 的 `tools.mcp.enabled` 设为 `true`。 --- ## 目录结构 ``` cmd/pi-agent/main.go Cobra 根命令 + 装配(chat / serve) internal/ config/ 配置加载(Viper + ${ENV} 展开) agent/ agent.go Service:react agent 封装 + 会话/记忆编排(CLI/HTTP 共用) tool_callback.go 工具回调(toolEventCallback):捕获工具调用/结果,SSE 事件 + trace session/ 业务层 JSONL 会话存储(分支/压缩) memory/ 工作记忆窗口 + 语义 Retriever 接口(空实现) tools/ 统一注册表 native/ bash/read/write/edit mcp/ MCP 客户端接入 skills/ YAML Skill 编排 → InvokableTool trace/ file.go 基础层:Entry 结构 + FileWriter(按天滚动 JSONL)+ ctx trace_id callback.go 全局组件回调:NewHandler(eino 5 个 timing) + RegisterGlobal request.go 请求级链路:RequestTracer(请求→思考→工具→输出→结束) server/ Gin /v1/chat SSE + 鉴权/限流 cli/ 本地 REPL ``` --- ## 设计取舍(已确认) - **Agent 无状态**:历史由 `session.Store` 管理,每次调用传全量;对齐 eino 官方 ch03 最佳实践(Memory/Session/Store 属业务层)。 - **Trace**:仅文件日志,不接 CozeLoop 等,后续自行采集。 - **MCP SDK**:采用 mark3labs 版。 - **向量库(语义记忆)**:本期不实现,仅留 `Retriever` 可插拔接口。 - **工具沙箱/白名单**:已做最基础路径越界防护,完整沙箱列为后续任务。