你们有没有遇到过这种尴尬:大模型的 API 单独调起来很爽,可真想让它替你干活,比如定时抓取信息、整理成表格、再自动归档到本地,就发现要写一堆胶水代码。我琢磨这事挺久,后来趁几个周末,把平时常用的 Agent 逻辑收敛成了一个轻量框架,代号就叫 pentagi。名字听着挺玄乎,其实就是 Penta 加 GI,五个核心模块加通用智能。这个项目解决的核心问题很简单:在不上重型框架的前提下,给大模型一个能规划、能调用工具、能记住长期信息的“身体”。它适合个人开发者拿来做自动化助手,也适合小团队快速验证内部系统的自然语言入口,更推荐给想真正搞清楚 Agent 内部流转逻辑的人。
我写这篇不是给你念官方文档,而是把我设计时的取舍、实现时踩的坑、实际用下来觉得关键的地方都摊开讲。你读完至少能知道 pentagi 是怎么运转的、怎么部署一个最小实例,以及遇到“模型反复调用同一个工具”“召回内容看着对但时序是错的”这类问题时该怎么下手。
1. pentagi 到底是个什么东西
1.1 名字拆解:Penta + GI
先说命名。Pentagi 不是拍脑袋起的,Penta 指的是我设计里的五个能力模块:Planner 负责把大任务拆成可执行步骤,Executor 负责真正做事的动作,Memory 存储和召回上下文,Toolbox 统一管理外部工具,Coordinator 负责在多个 Agent 之间协调。GI 则是 General Intelligence 的缩写,强调这套东西面向的是通用任务,不是单一场景的 Demo。
这五个模块听起来和市面上各种 Agent 框架大同小异,但 pentagi 的差异点在于:它的 AbstractAgent 基类和工具协议都极简,所有模块之间通过标准输入输出通信,不搞复杂的链式绑定。你可以很轻松地把某个模块替换成自己的实现,而不需要理解整套框架的设计哲学。
另一个很实际的设计取向是“本地可跑优先”。我的目标环境不是 K8s 集群,而是一台普通笔记本或一台低配服务器。所以 pentagi 对显存和内存的控制很保守,默认不加载重模型,模型调用全走 API 或者本地推理服务的标准接口。
1.2 项目想解决的问题
网上有很多 Agent 框架,但我在用的时候一直有种割裂感:有的框架把抽象层级铺得太厚,文档里全是 Chain、Graph、Node 这种概念,新人想改个细节得翻半天源码;有的框架又太偏 Demo,演示视频里很酷,实际接第三方工具时发现格式协议完全是私有的。
pentagi 想解决的第一个问题就是“编排层太重”。我理想中的状态是,大模型负责思考和生成结构化动作指令,框架负责执行和返回结果,中间不要夹太多无关抽象。第二个问题是“工具接入太麻烦”,很多框架要求你严格继承类、实现固定生命周期方法,而我更希望一个注册函数就能把普通 Python 方法变成 Agent 可调用的工具。第三个问题是“记忆系统与任务状态割裂”,很多 AI Agent 只有短期上下文,任务一长就“失忆”,而 pentagi 把短期上下文和长期知识分开管,至少保证“现在在干什么”和“以前学过什么”两个维度不会被搞混。
1.3 适合谁,不适合谁
这个项目不是通用银弹。适合的人我觉得有三类:第一类是个人开发者,想做一个能定时汇总信息、自动写周报、整理 RSS 或网页内容的私人助手;第二类是小团队,想把内部几个只有 HTTP 接口的系统串起来,统一用一个自然语言入口对话;第三类是 AI 应用学习者,想看一个“麻雀虽小但五脏俱全”的 Agent 框架长什么样,源码量不大,一天能读完。
不适合的场景也很明确:如果你需要海量并发、分布式编排、复杂的状态机流转,pentagi 不是对手,请去用正经的工作流引擎。如果只是想在聊天界面里接一个上下文窗口,pentagi 也偏重,直接调 API 更省事。它解决的是“介于随手脚本和专业平台之间的那一段”。
2. 核心架构与设计思路
2.1 五边形能力模型
我最初画架构图的时候,随手画了一个五边形,Pentagi 这个名字就是那时候定下来的。这五个模块并不是一层套一层的工作流,更像大脑的不同功能区。
Planner 模块的职责是把用户输入转成一个带有明确依赖关系的执行计划。它输出的格式我定义成一个 JSON 数组,每个节点包含节点 ID、动作类型、输入参数、依赖哪些前置节点。这个格式是贯穿全项目的主线,模型只负责产出这个 JSON,后续的 Executor 或 Coordinator 都消费这个 JSON。
Executor 是真正干苦力的模块。它拿到 Planner 生成的节点后,逐个执行,并把执行结果写回任务状态表。如果某个节点失败,Executor 会把错误信息原样返回给 Planner 做重新规划,而不是直接让整个任务崩掉。这里有个关键点:Executor 本身不知道业务逻辑,它只会根据节点里的 action 去 Toolbox 注册表里查对应的工具函数。
Memory 模块我拆了两层。短期记忆就是当前任务里的上下文,存对话历史和节点执行结果,存在内存里,任务结束就清理。长期记忆则放向量数据库,存的是“跨任务有用的知识”,比如用户偏好、历史结论、工具返回的关键数据摘要。长期记忆的召回会影响 Planner 的初始规划,比如你之前告诉过系统“我不喜欢邮件里附带 Excel”,下次规划生成时它就会避免选择发送 Excel 附件。
Toolbox 是个注册表,每个工具是一个普通 Python 函数,它接收一个字典参数,返回一个可序列化的结果。用装饰器即可注册。这个设计是我刻意做的,目的就是让“接入一个工具”的成本降到最低。
Coordinator 只在多 Agent 场景下才活跃。它管理多个独立 Agent 的消息总线,防止几个 Agent 同时操作同一个文件或资源时打架。节点依赖关系、共享变量、锁,都由这个模块负责。
2.2 一次完整任务的流转
用一个例子串起来看。假设用户说了这么一句:“帮我整理一下最近三天技术社区里关于大模型推理优化的讨论,输出 Markdown 摘要,存到工作目录。”
任务进来后先经过一个轻量预处理层,它会判断这是个需要多步骤执行的复合任务,于是把原始文本转给 Planner。Planner 收到请求后,结合长期记忆里“用户输出摘要时习惯要标题带日期”的偏好,输出一段计划 JSON:首先是调用搜索工具抓取关键词相关内容;然后是解析内容并生成摘要;最后是写入文件。
Executor 拿到计划后,先检查步骤间的依赖关系,确定第一步搜索无前置依赖,于是从 Toolbox 注册表里找到 search_web 函数并执行。搜索工具返回一批 URL 列表,Executor 把结果作为参数传入下一步的 extract_and_summarize 节点。这一步可能由另一个 Agent 或同一 Agent 的二次规划来完成。最终 write_markdown 节点把摘要写入指定目录。全流程状态在 Console 日志里能看到:哪个节点成功、耗时多少、输出大小。
整个过程看起来就像一条流水线,但不确定性的地方在于 Planner 每次生成的计划可能数量不同、顺序不同、工具组合不同,这是 Agent 与固定工作流的核心区别。
2.3 与主流框架的取舍对比
很多人会拿 pentagi 和 LangChain、AutoGen 做对比。说实话我写 pentagi 的初衷并不是做一个“替代品”,而是想保留最少的必要抽象。LangChain 生态很全,但它的抽象层级多,一个简单的工具调用也会经过 model、prompt、parser、output 多个环节,改动成本高。AutoGen 在多 Agent 聊天式协作上很有想象力,但它的会话模型会让不熟悉状态机的开发者摸不着头脑。
我给 pentagi 定的设计原则是四个字:协议优先。全系统只有两个核心协议:一个是 Planner 输出的计划 JSON,一个是工具函数的输入输出格式。只要满足这两个协议,什么模型都能接,什么工具都能挂。这就让pentagi的核心代码非常窄,交给新手看也不会有压迫感。
当然这种设计也有代价:它没有内置太多高级功能,比如复杂的条件分支、人工介入审批、可视化编排等。如果你需要的是一台万能机器,pentagi 会让你失望;如果你只是想快速把“模型+工具+记忆”跑起来,并且后续方便自己改,那它很顺手。
3. 从零跑通一个最小可用实例
3.1 准备环境与依赖
我建议用 Python 3.10 以上的版本,主要是为了类型标注和 Pydantic 的兼容性。安装依赖很简单,核心包就几个:
python -m venv .venv source .venv/bin/activate pip install pydantic pyyaml httpx openai faiss-cpu sqlalchemy如果跑本地向量检索,faiss-cpu 就够用了,几百兆的语料完全没压力。SQLAlchemy 是用来做长期记忆和任务状态的落库,默认 sqlite,零配置文件。
模型端我这边测试比较多的有两类:一类是 DeepSeek 开放平台、通义千问这类国内可以直接访问的 API,只需配好 key;另一类是本地 Ollama 起的模型,比如 qwen2.5 7B,性能弱一些但数据不出内网。pentagi 的模型调用层兼容 OpenAI 接口规范,所以只要能转发这个协议的地址都能配进去。
3.2 配置文件怎么写
pentagi 把可调参数都集中在 settings.yaml,我建议刚开始不要乱动太多,先配这几个:
provider: type: openai_compatible base_url: "https://api.deepseek.com/v1" api_key: "sk-xxxx" model: "deepseek-chat" memory: top_k: 5 embedding_model: "BAAI/bge-m3" store_dir: "./data/memory_store" toolbox: whitelist: ["search_web", "read_url", "write_file"] timeout_seconds: 60 agent: max_iterations: 8 temperature: 0.2 verbose: trueprovider 段决定大模型从哪来。embedding_model 用 bge-m3 是权衡过精度的,英文中文都能覆盖,而且是本地跑的,可以离线用。toolbox.whitelist 这个配置很关键,它控制了当前任务实例可以调用哪些工具,防止规划器头脑发热去调用不相关的危险操作。verbose 打开后,你能看到每个节点的输入输出摘要,调试期建议开。
3.3 写一个自定义工具并把任务跑通
举一个实际例子,写一个“读取网页并提取正文”的简单工具。在 pentagi 里,你只需要写一个普通函数,然后加注册注解即可:
from pentagi.tools import register_tool import httpx from bs4 import BeautifulSoup @register_tool(name="read_url", description="读取一个网页链接的正文文本") def read_url(url: str) -> dict: resp = httpx.get(url, timeout=30, follow_redirects=True) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") for tag in soup(["script", "style"]): tag.decompose() text = soup.get_text(separator="\n", strip=True) return {"url": url, "content_preview": text[:2000]}就是这么简单,一个纯函数被注册成了 Agent 可以调用的工具。Toolbox 注册表会利用函数名和 docstring 生成一个 JSON Schema,放进系统提示词里,让 Planner 知道这个工具叫什么、有什么用、参数长什么样。
主程序入口更简单:
from pentagi import Agent from pentagi.memory import LocalMemory from pentagi import tools # 导入工具模块,触发注册 agent = Agent( settings_path="settings.yaml", memory=LocalMemory.from_settings("settings.yaml") ) result = agent.run("读取 https://example.com/blog 的内容,并总结成 3 条要点") print(result.output)跑起来后,verbose 模式会打印类似这样的日志:Planner 生成了计划,Executor 调用了 read_url,然后又让 Planner 做了一次总结,最终把结果输出。如果你给的工具越多样,Planner 能编排出的任务就越复杂。
3.4 启动调试的小技巧
调试 pentagi 的时候有几个小技巧非常实用。第一个是开 dry-run,它只让 Planner 产出计划而不真正调用工具,适合验证“模型是否写出了正确的工具调用参数”而不是真的去执行副作用。第二个是每次跑完任务后,查看 sqlite 里的 task_runs 表,里面记录了每个节点的输入输出 JSON,这样你能回溯模型在哪一步跑偏了。第三个是我自己加进去的“重放”模式,把上一次任务的所有 LLM 请求参数保存下来,调 prompt 时可以反复重放同一请求,方便对比修改效果,而不用真的重复调用工具。
调试阶段会遇到最多的问题不是“模型不会写代码”,而是“工具返回的结果太脏”。比如网页提取里混进了一大段导航文本,摘要质量直线下降。这种问题的解法不是简单改 prompt,而是让工具在返回前就做好清洗。工具内部的清洗永远比模型后处理更可控。
4. 关键参数与记忆系统调优
4.1 模型选择的权衡
很多人在模型选择上容易犯一个错误,就是只盯着推理模型挑,忽略了一个事实:Agent 框架里模型的职责不仅仅是推理,还要严格遵守输出格式。我实际横向对比过几个模型,在 pentagi 这种强制 JSON 输出的场景里,有些声称很聪明的模型反而更容易自由发挥,导致 Planner 输出的 JSON 不合规格。
我的建议是先看模型的函数调用稳定性,再谈聪明程度。如果你主要跑中文任务,可以优先考虑 DeepSeek 或通义千问,如果跑英文,也可以按自己的习惯选择。为了兼顾离线场景,我也建议本地部署一个中等规模的模型专门做规划和摘要,虽然速度慢一些,但胜在稳定可控。
4.2 影响输出质量的关键参数
在 settings.yaml 里,有几个参数对最终效果影响特别大。第一个是 temperature,我把它理解成模型的“发散程度”。做计划拆解时,我强烈建议调低到 0.1 到 0.3,因为计划阶段需要确定性和可复现性,不需要脑洞;但如果你让 Agent 写营销文案或周报标题,可以把温度调到 0.7,效果会明显更自然。pentagi 的一个特点在于,planning 和 writing 阶段使用的是两套 temperature 配置,这是我在实际使用中觉得非常有用的设计。
第二个是 max_iterations,它限制一个任务最多执行多少个节点,防止 Planner 陷入无限循环。我一般设 8 到 12,如果超出这个次数还没有产出结果,就该反思是工具设计问题还是计划拆解问题,而不是提高上限硬冲。
第三个是 tool timeout。每个工具都有自己的超时时间,忽略这个参数会导致一个很恶心的场景:某个外部接口卡住了,整个任务一直挂着,直到全局超时才发现。现在我把每个工具默认超时设为 60 秒,子类可以自行覆盖。
4.3 长期记忆该存什么、怎么召
很多 Agent 框架的长期记忆形同虚设,原因主要是存的东西太杂。你要明白向量检索不是万能的,更不能把所有聊天记录全塞进去,否则每次召回都会混入大量无关信息。
我的经验是长期记忆只存三类内容:一类是用户明确表达的偏好,比如“报告喜欢 PDF 格式”“邮件要抄送给组长”;第二类是工具执行的关键结论,比如“上次搜索得到的竞品价格表里,最低档是 199 元”;第三类是重要实体的关系,比如“用户名张三关联的项目编号 P2024-013”。这些内容每条在入库前都会经过一次信息压缩,用大模型把原文提炼成一句话,然后才做向量化存储。
召回的时候要注意时间权重。我试过只看相似度排名,结果经常把三个月前的一段结论当最新事实用,后来给每条记忆加了 time_decay 系数,相似度分数乘以 exp(-age/30),相当于给记忆加了一个 30 天半衰期,分数会随时间自然衰减。这个策略在“信息时效性高”的任务里很有用,但对“用户口味偏好”这种长期稳定的记忆不太合适,所以我在记忆条目上又加了一个 category 字段,time decay 只作用在 news、data 这类类别上。
5. 常见问题与排查实录
5.1 现象、原因、解法速查表
我整理了一张速查表,都是你在使用 pentagi 时大概率会遇到的问题,对应排查思路都已经被我验证过:
| 常见现象 | 可能原因 | 排查与解法 |
|---|---|---|
| 同一个工具被连续调用多次 | Planner 没有意识到该工具已执行过 | 检查 Executor 是否把结果写回 task state;为工具名增加“已完成”的上下文摘要 |
| 模型总是输出不符合 JSON 规范的计划 | prompt 里格式说明不够刚性 | 把 JSON schema 直接放进系统提示词;开启 response_format json_object |
| 长期记忆召回的内容明显过时 | 时间衰减权重太小或 category 没设对 | 调大 time_decay 系数;检查入库前是否做了信息压缩 |
| 调用外部 HTTP 工具一直超时 | 工具没有单独设置 timeout_seconds | 给每个工具单独设定超时时间,不要只靠全局配置 |
| 多 Agent 同时写一个文件,内容相互覆盖 | 没有走 Coordinator 的资源锁 | 对文件输出类工具增加单例互斥;或让 Coordinator 串行调度写入节点 |
| 任务运行中途模型报 context length exceeded | 短期上下文里塞了太多历史节点输出 | 开启上下文压缩,每次写回前用 LLM 做一次摘要更新 |
5.2 几个容易踩的坑
第一个坑是把系统提示词写得像作文而不是约束。我一开始给 Planner 写了大段“你是一个聪明的规划助手,擅长拆解用户需求”之类的描述,结果模型特别喜欢在输出里附加解释文本,而不是纯粹输出 JSON。后来我把 system prompt 精简成三句话:你是规划器,只输出 JSON 计划,禁止输出任何解释。效果立竿见影。
第二个坑是工具函数没有做输入校验。因为 Planner 工具调用的参数是 LLM 生成的,不是人敲死在代码里的,所以你无法保证它传进来的参数合法。比如 read_url 的 url 参数,它可能传入一个列表而不是字符串,如果没有在函数入口做类型检查,Pydantic 的报错会直接炸穿整个执行链。我的习惯是每个工具函数入口都用 isinstance 或 Pydantic BaseModel 做一次强校验,宁可抛出可读的中文错误信息,也不要让底层异常裸奔出来。
第三个坑是向量库里存入了太多原始聊天内容。我最初图省事,把用户每条输入都存进长期记忆,结果是每次检索都召回一堆“嗯”“好的”这种无意义内容。后来做了源码级过滤和压缩,才把长期记忆的质量提上来。这个过程的教训就是:长期记忆的价值在于“精”,不在“全”,它更像人的长期记忆,只留少而关键的事,而不是对话录音。
5.3 关于工具协议设计的一点体会
工具协议是 pentagi 的地基,我把每个工具统一成“输入一个 dict,输出一个 dict”,这个看似简单粗暴的设计极大简化了所有模块的对接逻辑。建议你在自己的使用过程中也不要轻易打破这个协议,哪怕某个工具很特殊,也尽可能包装成这种纯函数形式。
这样做有三个好处:第一,所有工具输出可以直接序列化写入任务状态表,方便追溯;第二,模型在生成工具参数时更容易对齐 JSON 格式;第三,如果你想让某个 Agent 能力扩展,只需要注册新函数,其他部分完全不用动。
以我自己最近加的一个“分析本地 CSV 并输出统计摘要”的工具为例,从写函数到注册进 Agent 跑通,一共不到 20 行代码。这种轻量扩展的感觉,是 pentagi 最让我满意的地方。如果你也想在小项目里快速拥有一个能规划会调用工具的智能体,可以试着用这个思路做一个最小实现,实践一圈下来,你对 Agent 内部机制的认知会比读十篇概念文章更扎实。