最近把一个折腾了两周的智能体项目跑通了,趁热把过程整理出来。项目本身不算复杂:基于 openJiuwen 这个轻量级大模型应用框架,接上华为云的大模型 API,搭了一个带知识库检索和工具调用能力的 AI 智能体,主要用来做公司内部的业务知识助手。
为什么不用现成的对话机器人平台?因为我要的不是一个"聊天窗口",而是一个能查库存、能翻制度文档、能给出准确答案的"数字员工"。普通对话机器人只能根据训练数据或预设话术回答,遇到"帮我查一下 A001 物料还有多少库存"这种实时数据请求就无能为力了。而 openJiuwen 的价值,就是让大模型具备工具调用的能力,把"回答问题"升级成"执行任务"。
这篇博文我尽量按实操顺序写,从账号准备、依赖安装,到配置修改、插件开发,再到上线后的性能调优和避坑经验,都会覆盖到。适合有一定 Python 基础、想快速搭建智能体但不太想从零写编排框架的开发者参考。读完你至少能搞清楚一件事:把一个大模型 API 接进自己的应用,让它帮你干活,比想象中简单,但坑也比想象中多。
1. 为什么自己搭智能体:openJiuwen 和华为云大模型的组合思路
1.1 智能体和普通对话机器人到底差在哪里
先厘清一个概念。很多人觉得"智能体"就是高级一点的聊天机器人,这个理解不完整。普通对话机器人是线性结构:输入一句文本,模型生成一句回复,结束。而智能体是循环结构:模型不仅要理解用户的意图,还要自主决定"下一步做什么"——是直接回答,还是调用某个工具,还是先查一下知识库再回答。
拿我做的业务知识助手举例,用户问"报销流程怎么走",模型直接从知识库检索相关制度文档,整理成步骤回复,这是普通对话就能搞定的。但如果用户问的是"仓库里 B002 物料还有多少",模型本身没有这个数据,它必须知道该去调库存查询接口,然后基于接口返回的数据组织回答。这个"判断该做什么、调用什么工具、根据结果继续回答"的循环,才是智能体的核心。
这个能力能不能靠提示词硬写?我试过,效果很差。把库存数据一股脑塞进 system prompt 里,数据会过期;不塞进去,模型就只会瞎编。所以必须有框架层面的支持:让模型能够输出结构化工具调用指令,由框架执行具体函数,再把结果回传给模型。openJiuwen 就是干这个的。
1.2 框架选型:为什么选轻量的 openJiuwen 而不是重型编排框架
市面上已经有 LangChain 这类大而全的编排框架,功能确实强大,但学习成本也不低。概念多得让人眼花:Chain、Agent、Tool、Memory、Retriever……中小团队用起来,光是理解这些抽象概念就要花不少时间。我最后选了 openJiuwen,核心原因有三条:
第一,轻量。它的核心是一个配置驱动的路由加工具注册器,没有太多晦涩的抽象。你只需要关注:怎么定义 agent、怎么配模型、怎么注册工具,整个使用路径清晰。
第二,透明。代码量整体不大,出了问题可以直接翻源码定位,不需要在各种中间层里猜。我自己就改过它的一个消息格式处理逻辑,因为要适配华为云 API 返回的特殊字段,由于代码结构清楚,几分钟就改完了。
第三,模型无关。它的 Provider 设计做得不错,通过统一接口接入不同厂商的大模型。华为云大模型、其它主流国内模型、甚至本地部署的开源模型,都能通过配置切换。这意味着什么?以后如果你想换模型供应商,不需要动业务代码,改几行配置就行。这个收益在长期维护中非常明显。
也有人问,为什么不干脆自己写一个调度层?我当时算过:工具循环、上下文管理、错误重试、消息格式转换,工作量不算大但非常琐碎,而且容易出各种边角 bug。与其在这些基础能力上耗时间,不如站在现成框架的肩膀上,把精力花在业务插件上。后来的排错经历证明,这个选择替我省了不少事。
1.3 大模型服务为什么选华为云
大模型的选择其实是另一个维度的决策:是直接调用云端 API,还是自己部署开源模型?我选了华为云,原因是实际需求决定的。
首先,免运维。自己部署一个可用的大模型服务,光硬件和模型权重就够折腾一阵子,更不用说后续的更新维护。云端 API 按量计费,调用就花钱,不调用就不花钱,对中小团队来说成本可控得多。
其次,能力完整。我需要的几个关键能力——对话补全、流式输出、function calling、稳定的鉴权机制——华为云的大模型 API 都支持,而且文档描述清晰,接入的时候没有遇到"文档和实际行为不一致"的情况。特别是 function calling 的支持,这是智能体实现工具调用的前提。
有一点需要说明:不是说其它云不行,而是从我实测的角度,这个组合从协议兼容性到响应稳定性都符合项目需求。选型核心就三条:协议标准、文档准确、稳定性达标。这三点在个人体验中都是加分项。
2. 动手前准备:账号、凭证与本地环境
2.1 开通云上大模型服务并获取凭证
第一步当然是去华为云控制台开通大模型服务。这里我不展开控制台界面的具体操作,因为界面会迭代,但核心信息你必须搞清楚三样东西:
- 服务域名(endpoint):这是调用 AI 服务的地址,后面所有请求都打到这里。
- 模型名称/版本号:你的鉴权账号能访问的具体模型标识。不同模型能力侧重点不同,选型时注意看模型是否支持 function calling。
- 访问凭证:推荐用 AK/SK 方式,而不是短时 Token。AK/SK 可以长期有效,直接写在配置里或环境变量里,管理方便。
关于凭证,有一个必须要说的教训:不要多人共用一个密钥,也不要把它写进代码仓库。我当时偷懒,在本地配置里直接写死了测试 AK/SK,结果同事围观演示的时候,密钥在屏幕上暴露了几秒钟。虽然立刻吊销重置了,但那几分钟里心里是真没底。密钥这种东西,不管项目多小,都要走环境变量或配置中心,绝不能进 Git 历史。
2.2 华为云大模型 API 的调用要点
从协议层面看,华为云大模型 API 调用整体是标准的 HTTPS POST 加 JSON Body,鉴权头字段比较严格。核心请求字段包括:
- model:模型版本标识。
- messages:对话消息列表,按照 system/user/assistant/tool 的角色顺序组织。
- tools:可选,工具定义列表,使用 JSON Schema 格式描述。
- tool_choice:可选,是否强制模型使用某个工具。
- temperature / max_tokens / top_p:生成参数,控制随机性和输出长度。
这里有个特别容易踩的坑:国内不同云厂商的大模型 API 虽然都宣称兼容 OpenAI 格式,但鉴权签名算法各有各的实现细节。直接拿其它家的 SDK 改个 host 去调,大概率会卡在签名校验上。我的建议是:先到控制台的在线调试功能里发一次真实请求,把请求头和签名逻辑对照文档弄清楚,再用 requests 从零模拟。这一步看起来多花了十几分钟,实际上能省下数小时的排查时间。
2.3 本地依赖安装
本地环境我推荐 Python 3.10 及以上。安装 openJiuwen 之外,还需要两个常用库:requests 和 python-dotenv。前者负责 HTTP 调用,后者负责从 .env 文件读取密钥环境变量。
python -m venv .venv source .venv/bin/activate pip install openjiuwen requests python-dotenv然后把 AK/SK 写进项目根目录的 .env 文件里:
HUAWEI_CLOUD_AK=your_ak_here HUAWEI_CLOUD_SK=your_sk_here记得把 .env 加进 .gitignore。这是第一次提交前就该做好的事情,不要等到代码 push 出去之后才想起来。
3. 读懂 openJiuwen:核心概念、配置加载与工具定义
3.1 项目目录结构速览
安装完 openJiuwen,初始化工作区之后,会生成一套固定结构。我的项目目录大致长这样:
- agents/:智能体定义目录,每个智能体对应一个 agent.yaml 文件。
- plugins/:自定义工具目录,每个工具是一个 Python 模块。
- storage/:会话状态与记忆存储,含向量索引和对话历史。
- knowledge/:知识库文档目录,放你需要检索的原始资料。
- config.yaml:全局配置,模型 Provider、密钥读取方式、日志级别都在这里。
理解这套结构的关键词是"配置驱动、动作分离"。所有不需要改逻辑的东西全部用 yaml 描述;需要写逻辑的部分,集中在 Python 插件里。改模型、改温度、改人设,不用动代码;新加一个业务流程,也只动 Python 插件。这对后续维护者非常友好——哪怕他不熟悉整个系统,光看配置文件也能大致理解系统行为。
3.2 配置一个华为云大模型 Provider
配置文件的写法决定了你对接模型的方式。我在 config.yaml 里把模型 Provider 指向了华为云:
provider: type: custom_openai_compatible base_url: https://your-endpoint.example.com/v1 api_key: ${HUAWEI_CLOUD_AK} api_secret: ${HUAWEI_CLOUD_SK} model: your-chat-model-name temperature: 0.3 max_tokens: 2048 timeout: 30为什么 type 用 custom_openai_compatible?因为华为云的 API 和标准 OpenAI 协议在鉴权细节上有差异。openJiuwen 里有一个兼容层:如果是标准 OpenAI 兼容接口,用 openai_compatible 就能直接跑;如果签名逻辑特殊,就需要一个自定义 handler 来处理 AK/SK 签名。我建议不要偷懒,先把 handler 的代码读一遍,确认签名逻辑,再填配置。我见过有人在 base_url 里写错一个路径前缀,结果所有请求都 404,排查了很久才发现是配置问题。
关于 temperature,我选了 0.3。知识助手这类场景要的是稳定、可控、不胡说,低温能明显减少模型自由发挥的程度。如果你做的是小红书文案生成器、创意写作助手,那可以把温度调到 0.7-0.9,让输出更有创造性。后面实操章节我会再展示具体差异。
3.3 工具声明:用 JSON Schema 告诉模型它能调什么
function calling 的原理一句话解释:模型本身不执行任何工具,它只是在对话中判断"现在需要调用工具 X,参数是 Y",然后输出一个结构化 JSON。框架负责解析这个 JSON、调用真正的函数、把结果作为新消息回传给模型,模型再根据结果组织最终回答。
所以,工具怎么描述给模型,直接决定模型能否正确调用它。我做的库存查询工具,定义如下:
{ "type": "function", "function": { "name": "query_inventory", "description": "查询仓库中某个物料的当前库存数量。输入物料编码,返回库存数据。物料编码示例:A001、B002。", "parameters": { "type": "object", "properties": { "material_code": { "type": "string", "description": "物料编码,如 A001" } }, "required": ["material_code"] } } }这里的 description 非常关键。我一开始图省事,写的是"查询库存函数",结果模型经常把参数填错,甚至把整个问题当作参数传进去。后来改成带样例描述的版本,工具调用准确率立刻提上来了。经验就是:把 description 当成给实习生写的操作说明,越具体、越多样例,模型表现越好。
4. 从零实操:搭建一个业务知识助手
4.1 准备知识库:切好文档比选对模型更重要
智能体的知识来自知识库。流程是:把 FAQ、制度文档、流程说明等文本切成小块,做向量化,存入向量库。用户提问时,先检索出最相关的几个片段,拼进上下文,让模型基于这些片段回答。
切分这一步最容易被低估。我第一次做的时候,把一整本几十页的操作手册直接按固定长度 800 字切分,结果很多地方把标题和正文切散了。用户问"请假流程是什么",检索出来的片段只有半句话,模型没法组织出完整回答。
后来改成两步切分:先按层级标记(标题、数字列表、表格标题)拆出大段,再对大段按 500-800 字做二次拆分,并保留段落的标题信息作为元数据。这样检索命中时,模型能拿到完整的上下文,回答质量提升非常明显。数据预处理这个环节没有捷径,就是把脏活累活做细。
4.2 设计系统提示词:把行为边界写清楚
agent 的定义在 agents/agent.yaml 里。核心是 system_prompt 怎么写。我用的版本:
name: biz_assistant description: 回答公司内部业务问题,支持库存查询 system_prompt: | 你是公司的业务知识助手。请用简洁、友好的中文回答问题。 - 当用户询问流程、制度类问题时,先检索知识库再进行回答; - 当用户询问实时数据(如库存、数量)时,必须调用工具获取数据后再回答; - 如果知识库和工具都无法确认答案,请明确表示不知道,不要编造; - 回答中不要输出内部代码或密钥。特别要强调"必须调用工具获取数据后再回答"这句话。不加这句,模型很可能在用户问"B002 还有多少库存"时,凭印象直接编一个"大约 100 件"。这种回答看起来合理,实际完全不可靠。加了这句之后,模型在涉及实时数据的场景下会更倾向先走工具调用,实测工具调用率几乎翻倍。
4.3 写一个自定义工具插件
在 plugins/inventory.py 里写真正的工具函数:
import json def query_inventory(material_code: str) -> str: """查询物料库存(模拟实现)""" inventory_map = { "A001": {"name": "电阻器", "stock": 3200, "unit": "件"}, "B002": {"name": "单片机", "stock": 88, "unit": "件"} } data = inventory_map.get(material_code) if not data: return json.dumps({"code": 404, "message": f"物料 {material_code} 不存在"}) return json.dumps({"code": 0, "data": data})openJiuwen 的插件注册机制很简单——在工具函数上添加一个装饰器标记,启动时自动扫描 plugins 目录并注册到可用工具列表:
from openjiuwen import tool @tool def query_inventory(material_code: str) -> str: # 实际项目中替换为对仓库管理系统的 HTTP 调用 ...这里有个非常容易踩的坑:函数返回值必须是一个字符串。我刚写的时候直接返回了 dict,openJiuwen 虽然会自动序列化,但模型拿到的消息可能带 Python 的 repr 格式,组织回答时容易把数据结构原样吐出来,观感很差。统一返回 JSON 字符串,模型处理起来最稳定。
4.4 启动智能体并测试一轮完整对话
配置写好之后,启动服务:
python -m openjiuwen run --agent biz_assistant第一次建议加上 --verbose 参数,把日志全开。然后通过 curl 测试:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "你好,请查一下 A001 还有多少库存?"}'正常链路会看到三段日志:第一段是模型判断需要调用工具,第二段是框架执行 query_inventory 并拿到结果,第三段是模型基于工具结果组织最终回答。最终返回应该是"物料 A001(电阻器)当前库存为 3200 件"这类自然语言。
如果只看到模型直接编了个数字出来,没有走工具调用,那大概率是 system_prompt 里的边界没写清楚,或者工具描述让模型没理解到"该调它"。
4.5 把智能体接入外部应用
openJiuwen 默认自带一个 Web 接口,POST /chat 就是入口。如果你要接企业微信机器人、钉钉机器人或者自己的前端页面,就是把这个接口包装一层,做认证和权限控制。
需要提醒的是:不要把 openJiuwen 服务直接暴露到公网。我上线时用 Nginx 限了网段,只允许内网访问,前端通过带 Token 的网关转发。这一步不是核心功能,但不做的话,等于把公司内部数据接口裸奔在公网上,风险太大了。
5. 踩坑实录:鉴权、超时、上下文限制与成本控制
5.1 常见问题速查表
我把这次接入过程中遇到的主要问题整理成一个表格,方便对照排查:
| 现象 | 原因 | 解法 |
|---|---|---|
| 调用 API 报 401/403 | 鉴权签名不对,或 AK/SK 写反 | 用控制台在线调试比对请求头,确认签名逻辑,检查 AK/SK 是否填反 |
| 响应超时,模型迟迟不返回 | 云端延迟高,或客户端超时时间设太短 | 把超时从默认 10 秒调大到 30 秒,并开启流式输出 |
| 同一个问题,模型反复调用工具 | 工具返回结果格式异常,模型看不懂 | 确保工具返回字符串是合法 JSON,并附带明确结果字段 |
| 上下文长度超限 | 对话轮次太多,messages 过长 | 启用 openJiuwen 的滑动窗口策略,只保留最近 4-6 轮核心信息 |
| 模型回答"不知道",但知识库里明明有 | 文档切分粒度太大,检索命中差 | 改进切分策略,调整为基于标题层次切分,并调整 top_k |
5.2 成本控制与 Token 优化
大模型 API 的成本最终都体现在 Token 消耗上。我实测下来,三个手段最有效:
第一,检索压缩。知识库检索出来的原始片段往往很长,直接全部塞进上下文既费 Token 又稀释注意力。可以只保留命中片段中与问题最相关的部分,比如只截取目标段落而不是整篇文档。这一步能省下相当可观的 Token。
第二,控制历史轮次。业务问答场景下,模型不需要记住整场对话的全部细节。保留最近 4-6 轮就够了,再早的内容该丢就丢。我的配置里把历史窗口设成了 6 轮,单次请求的 Token 消耗明显下降。
第三,语义缓存。高重复性问题的答案其实不会有变化,比如"报销流程""请假规则",直接在本地缓存命中即可,完全不用再调一次大模型。我加上语义缓存之后,日常使用成本大约降了四成,并且命中缓存的响应速度比走大模型快得多。
5.3 温度参数与提示词调优的实测感受
同一个知识库,把 temperature 从 0.3 调到 0.9,输出风格的差异非常明显。低温状态下,模型回答更保守,遇到不确定的内容会更主动说"不知道";高温状态下,模型语气自信很多,但也更容易延伸出上下文没有的内容,也就是俗称的"幻觉"。
更微妙的是,temperature 过高还会影响 function calling 的稳定性。模型偶尔会输出不合法的 JSON,导致工具调用解析失败。所以我的建议是:生产环境里,对话生成部分控制在 0.3-0.5,创意文案场景再上调。不要为了追求"有趣"而牺牲工具调用的可靠性,智能体首先得"靠谱",其次才是"有趣"。
5.4 扩展方向:记忆持久化与多智能体协作
openJiuwen 目前的定位是"单智能体加工具调用"。如果你的业务流程复杂起来,可以考虑两个扩展方向。
第一个是持久化记忆。把每次重要对话总结成摘要写入向量库,下次用户再次提起相关话题时,自动把记忆片段检索出来带入上下文。这样用户不用每次都重复背景信息,智能体的体验会连贯很多。
第二个是多智能体分工。比如客服助手和数据助手各自维护一套提示词和工具集,然后在应用层做一个路由,根据用户意图把请求分发到不同的智能体。这种架构在业务复杂后比堆一个超级智能体更清晰,也更利于团队分模块维护。
这两点不需要在第一天就全部实现。先把单智能体的链路跑稳定,把工具调用、知识库检索、上下文管理这三大基础模块调顺,再考虑扩展。智能体的复杂度每上一个台阶,排查问题的难度也会上一个台阶。
最后说一点个人体会吧。这个项目做下来,我最大的感受是:智能体项目的成败,70% 取决于数据和边界的定义,只有 30% 取决于模型和框架。prompt、工具声明、知识库切分,本质上都是把人的业务知识结构化地传达给模型。openJiuwen 帮你省掉了编排层的体力活,但业务细节没有银弹。
如果你也想做类似的东西,建议动手之前先花半天时间梳理一下业务问题清单:哪些问题靠知识库就能回答,哪些必须调用工具,哪些其实根本不需要智能体介入。这个清单想得越清楚,后面的弯路走得越少。