1. 为什么需要“自己的” AI Agent 发行版
这两年 AI Agent 这个词被聊烂了。打开任何一个技术社区,满屏都是“从0到1搭建 AI Agent”“AI Agent 开发实战”“多智能体协作”之类的标题。但真正动手做过几个项目之后你会发现一个很尴尬的事实:搭一个能跑的 Demo 只要一个下午,把它变成一个能交付、能维护、能给别人用的“发行版”,可能要花上几个月。
我最早接触 Agent 是在一个内部工具项目里,当时用 Python 写了个简单的 ReAct 循环,接了个大模型 API,能查数据库、能调接口,跑起来挺唬人。结果一上生产环境就露馅了:不同同事想要不同的工具集,有人要接内部知识库,有人要接工单系统,还有人只想用它写周报。每次改需求都要动核心代码,改到最后那个main.py变成了八百行的意大利面条。
后来我才想明白一件事:Agent 本身不是产品,Agent 的“发行版”才是。就像 Linux 内核和 Ubuntu 的关系——内核只有一个,但发行版可以有无数个,每个发行版针对不同的用户群体、预装不同的软件、配置不同的默认行为。AI Agent 也一样,核心的推理循环、工具调用协议、上下文管理这些是“内核”,而 Profile(配置档案)、插件集、权限策略、部署方式这些才是“发行版”的差异所在。
这篇文章要聊的,就是怎么把你手里那个“能跑的 Agent”升级成一个“可发行的 Agent 发行版”。核心抓手是Profile 定制,落脚点是生产部署。适合已经写过至少一个 Agent Demo、现在想把它工程化、产品化的开发者。如果你还在纠结“Agent 和 LLM 有什么区别”这种问题,建议先补一下基础——简单说,LLM 是大脑,Agent 是给大脑装上手脚和记忆之后的完整系统,而 Profile 是给这个系统定制性格和技能的“人格档案”。
整个思路我会拆成四块:先讲发行版的整体设计思路和 Profile 机制的设计哲学,再拆解 Profile 的核心细节和实操要点,然后是完整的构建与部署流程,最后是我踩过的坑和排查技巧。全程按“从业者交流”的口吻来,不整那些虚的。
2. 发行版整体设计与 Profile 机制拆解
2.1 从“一个 Agent”到“一套发行版”的思维转变
大部分人写 Agent 的起点是这样的:定义一个 system prompt,注册几个 tool,写个 while 循环,调用模型,解析输出,执行工具,把结果塞回上下文,循环直到模型说“我完事了”。这个结构本身没问题,问题出在所有东西都硬编码在一起。
发行版思维要求你把 Agent 拆成三层:
- 内核层(Kernel):推理循环、消息协议、工具调用解析、上下文窗口管理、错误重试。这层是稳定的,不应该随业务需求频繁变动。
- 配置层(Profile):system prompt、可用工具集、模型参数(temperature、max_tokens)、权限边界、输出格式约束。这层是发行版差异化的核心。
- 运行时层(Runtime):部署环境、密钥管理、日志与可观测性、并发控制、资源限制。这层决定了 Agent 能不能在生产环境活下去。
为什么要这么分?因为变更频率不同。内核层可能几个月才动一次,配置层可能每天都要调,运行时层则跟着基础设施走。如果你把这三层混在一起,每次调个 prompt 都要重新部署整个服务,那效率低得让人想砸键盘。
我见过一个团队的做法特别典型:他们把 system prompt 直接写在代码里,每次产品经理说要改个语气,就得走一遍完整的 CI/CD 流程。后来他们引入了 Profile 机制,把 prompt 抽成 YAML 文件,改完热加载,五分钟生效。这就是发行版思维带来的直接收益。
2.2 Profile 到底是什么:一份“人格 + 技能 + 边界”的声明
Profile 这个词在不同语境下含义不一样。在浏览器里它是用户配置,在性能分析工具里它是采样配置,但在 AI Agent 发行版的语境下,我把它定义为:一份描述 Agent 在特定场景下应该如何思考、能做什么、不能做什么、以什么风格输出的声明式配置。
一份完整的 Profile 通常包含这几个部分:
| 配置项 | 作用 | 典型取值示例 |
|---|---|---|
name | Profile 标识 | code-reviewer、customer-support |
model | 使用的模型及参数 | deepseek-chat, temperature=0.3 |
system_prompt | 人格与行为准则 | 角色定义、输出规范、禁忌事项 |
tools | 可用工具白名单 | [search, read_file, run_test] |
permissions | 权限边界 | 只读、可写、需审批 |
memory | 记忆策略 | 会话级、持久化、向量检索 |
output_schema | 输出格式约束 | JSON Schema 或 Markdown 模板 |
这里有个关键设计决策:Profile 应该是声明式的,而不是命令式的。什么意思?你不应该写“先调用搜索工具,再调用总结工具”,而应该写“你可以使用搜索和总结工具,目标是回答用户问题”。前者是流程编排,后者是能力声明。声明式的好处是模型可以根据实际情况灵活组合,而不是被你的硬编码流程框死。
当然,声明式也有代价——可控性下降。所以生产环境里通常是声明式为主,关键路径用少量命令式约束兜底。比如客服 Agent,你可以声明“你可以查询订单、发起退款”,但退款这个动作必须走审批流程,这就是命令式兜底。
2.3 为什么 Profile 机制能解决“改一处崩一片”的问题
回到我开头说的那个意大利面条main.py。引入 Profile 之后,代码结构变成了这样:
# kernel.py - 稳定不变 class AgentKernel: def __init__(self, profile: Profile, runtime: Runtime): self.profile = profile self.runtime = runtime self.tools = load_tools(profile.tools) def run(self, user_input: str): messages = [{"role": "system", "content": self.profile.system_prompt}] messages.append({"role": "user", "content": user_input}) while True: response = self.runtime.call_model( model=self.profile.model, messages=messages, tools=self.tools.schemas() ) if response.has_tool_call: result = self.tools.execute(response.tool_call) messages.append(result) else: return response.content# profiles/code-reviewer.yaml name: code-reviewer model: name: deepseek-chat temperature: 0.2 system_prompt: | 你是一名资深代码审查员。你的任务是找出代码中的 bug、 安全隐患和可维护性问题。输出使用 Markdown 列表, 每条问题标注严重程度(高/中/低)。 tools: - read_file - search_codebase - run_linter permissions: write: false execute: false看到区别了吗?新增一个“代码审查员”发行版,只需要加一个 YAML 文件,内核代码一行不动。这就是 Profile 机制的核心价值:把变化隔离在配置层,让内核保持稳定。
2.4 发行版的“发行”二字意味着什么
“发行版”这个词借自 Linux 世界,它隐含了几个要求:
- 可分发:别人能拿到你的发行版并跑起来,不需要你手把手教。
- 可版本化:v1.0 和 v1.1 之间有明确的变更记录,能回滚。
- 可定制:用户能在你的基础上做二次配置,而不是只能全盘接受。
- 有默认值:开箱即用,不配置也能跑,配置了更好用。
对应到 AI Agent 上,就是:Profile 要能打包分发(比如一个 Git 仓库或一个压缩包),要有版本号,要允许用户覆盖默认配置,要有一套合理的默认 Profile 让新手直接能用。
我个人的做法是每个发行版一个 Git 仓库,目录结构长这样:
my-agent-distro/ ├── kernel/ # 内核代码 ├── profiles/ # 内置 Profile │ ├── default.yaml │ ├── code-reviewer.yaml │ └── customer-support.yaml ├── plugins/ # 可选插件 ├── runtime/ # 部署配置 │ ├── docker-compose.yml │ └── k8s/ ├── docs/ └── VERSION用户拿到这个仓库,docker compose up就能跑起来默认 Profile,想换 Profile 就改环境变量或者挂载自己的 YAML。这就是“发行版”该有的样子。
3. Profile 核心细节与实操要点
3.1 system_prompt 的写法:从“许愿”到“契约”
大部分人写 system prompt 的方式是“许愿式”的:“你是一个专业的助手,请认真回答用户问题,保持友好。”这种 prompt 不能说没用,但效果极不稳定。模型今天心情好就友好,明天心情差就敷衍。
生产级的 system prompt 应该是一份契约,包含四个部分:
- 角色定义:你是谁,你的专业领域是什么。
- 能力边界:你能做什么,不能做什么,遇到边界外的问题怎么处理。
- 输出规范:格式、长度、语气、必须包含/避免的元素。
- 失败处理:信息不足时怎么办,工具调用失败时怎么办。
举个例子,一个客服 Agent 的 prompt 可能是这样:
你是 XX 公司的客服助手,负责处理订单查询、退换货和产品咨询。 能力边界: - 你可以查询订单状态、发起退货申请、查询产品库存。 - 你不能修改价格、不能承诺赔偿、不能处理投诉升级。 - 遇到能力边界外的问题,回复"这个问题我需要转接人工客服",并调用 escalate 工具。 输出规范: - 使用简洁的中文,每次回复不超过 3 句话。 - 涉及金额时保留两位小数。 - 不要使用"亲""呢"等过度亲昵的语气词。 失败处理: - 如果订单查询工具返回空结果,回复"未找到该订单,请确认订单号是否正确"。 - 如果工具调用超时,回复"系统繁忙,请稍后重试",不要编造结果。这份 prompt 比“你是一个专业助手”长了十倍,但效果稳定得多。关键经验:prompt 里的每一条约束,都应该是你在实际测试中发现问题后加上的。不要一上来就写一千字,先写个最小版本,跑起来,看它哪里出错,然后针对性地加约束。这样长出来的 prompt 每一条都有实际意义。
3.2 工具集设计:少即是多,权限要分层
新手做 Agent 最容易犯的错是工具给太多。我见过一个 Agent 注册了三十多个工具,结果模型经常选错,或者在一个简单问题上反复调用不相关的工具。
工具集设计的第一原则是:只给当前 Profile 真正需要的工具。代码审查员不需要发邮件的工具,客服不需要执行 shell 命令的工具。Profile 的tools字段就是干这个的——白名单机制,没列出来的工具模型根本看不到。
第二原则是权限分层。工具按危险程度分三级:
- 只读级:搜索、查询、读取文件。可以自由调用。
- 写入级:创建文件、修改数据、发送消息。需要记录审计日志。
- 危险级:删除数据、执行命令、发起支付。需要人工审批或二次确认。
在 Profile 里可以这样声明:
tools: - name: search_knowledge_base level: read - name: create_ticket level: write - name: delete_record level: dangerous require_approval: true运行时根据 level 决定是否拦截。危险级工具调用时,Agent 不是直接执行,而是返回一个“待审批”状态,由人工确认后再继续。这个机制在生产环境里救过我很多次——有一次测试时模型抽风想批量删除数据,被审批机制拦下来了。
3.3 模型参数调优:temperature 不是唯一要调的
很多人调模型参数只知道调 temperature,其实还有几个参数对 Agent 行为影响很大:
| 参数 | 作用 | Agent 场景建议 |
|---|---|---|
temperature | 随机性 | 工具调用场景 0.1-0.3,创意场景 0.7-0.9 |
max_tokens | 单次输出上限 | 根据输出规范设定,避免模型啰嗦 |
top_p | 采样范围 | 一般保持默认,与 temperature 二选一调 |
frequency_penalty | 重复惩罚 | Agent 循环场景适当调高,避免复读 |
stop | 停止序列 | 结构化输出时设置,强制截断 |
这里有个实操心得:Agent 场景下 temperature 不宜过高。因为 Agent 需要稳定地选择工具、解析参数、遵循格式,随机性太大会导致工具调用失败率飙升。我一般把工具调用密集的 Profile 设在 0.1-0.2,纯对话类设在 0.5-0.7。
另外,不同模型对参数的敏感度不一样。有些模型 temperature 从 0.1 调到 0.3 行为就大变,有些则很迟钝。换模型时一定要重新做参数标定,不能直接沿用旧配置。我吃过这个亏——从一个模型换到另一个,temperature 没改,结果工具调用成功率从 95% 掉到 70%,排查了半天才发现是参数不兼容。
3.4 记忆策略:会话记忆、持久记忆、检索记忆
Agent 的“记忆”分三种,Profile 里要明确声明用哪种:
- 会话记忆(Session Memory):只在当前对话内有效,对话结束就清空。适合一次性任务,比如代码审查、文档总结。
- 持久记忆(Persistent Memory):跨会话保存,通常存用户偏好、历史决策。适合长期助手,比如个人助理。
- 检索记忆(Retrieval Memory):从外部知识库动态检索,不常驻上下文。适合知识密集型场景,比如客服、技术支持。
配置示例:
memory: type: hybrid session: max_turns: 20 strategy: sliding_window persistent: enabled: true store: redis ttl: 30d retrieval: enabled: true source: vector_db top_k: 5注意事项:记忆不是越多越好。上下文窗口是有限资源,塞太多历史会挤占当前任务的思考空间。我的经验是会话记忆保留最近 10-20 轮,更早的做摘要压缩;检索记忆 top_k 控制在 3-5 条,太多会引入噪声。
3.5 Profile 的继承与覆盖机制
生产环境里 Profile 往往需要“基础配置 + 场景覆盖”。比如所有 Profile 都共用一套安全策略,但各自的工具集不同。这时候继承机制就很有用:
# profiles/base.yaml safety: pii_filter: true max_tool_calls: 10 timeout: 60s # profiles/customer-support.yaml inherit: base name: customer-support tools: - query_order - create_ticket子 Profile 继承父 Profile 的所有字段,同名字段覆盖,新字段追加。这样安全策略改一处,所有 Profile 生效,不用逐个改。
实现上,加载 Profile 时做一次深度合并(deep merge)即可。注意数组字段的合并策略要明确——是替换还是追加。我一般用替换,因为工具白名单追加容易出意外。
4. 从零构建发行版的完整实操流程
4.1 环境准备与依赖选型
先说技术栈选型。Agent 开发主流有两条路:Python 和 Java。Python 生态成熟,LangChain、LlamaIndex 这些框架拿来即用;Java 生态这两年追得很快,Spring AI 已经能覆盖大部分场景,企业级项目用 Java 的好处是能复用现有的微服务体系。
我的建议是:个人项目和小团队用 Python,企业级集成用 Java。如果你所在的公司本来就是 Spring Cloud 技术栈,那用 Spring AI 开发 Agent 是最顺的,不用为了一个 Agent 引入一套全新的技术栈。
Python 侧的核心依赖:
pip install fastapi uvicorn pydantic pyyaml httpx redis这里我刻意没列具体的模型 SDK,因为不同模型厂商的 SDK 不一样,而且经常变。用 httpx 直接调 HTTP API 反而更稳定,少一层依赖少一个坑。
Java 侧如果用 Spring AI:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>1.0.0</version> </dependency>目录结构按前面说的三层来分。我习惯把 Profile 放在独立的profiles/目录,运行时通过环境变量AGENT_PROFILE指定加载哪个。
4.2 内核实现:一个最小可用的推理循环
内核不需要复杂,核心就是一个循环。但有几个细节必须处理好:
import json from typing import Any class AgentKernel: def __init__(self, profile, model_client, tool_registry): self.profile = profile self.model = model_client self.tools = tool_registry self.max_iterations = profile.get("max_iterations", 10) def run(self, user_input: str, session_id: str) -> str: messages = self._build_initial_messages(user_input, session_id) for i in range(self.max_iterations): response = self.model.chat( messages=messages, tools=self.tools.schemas(self.profile.tools), temperature=self.profile.model.temperature, max_tokens=self.profile.model.max_tokens ) if not response.tool_calls: return response.content messages.append(response.to_message()) for call in response.tool_calls: if not self._check_permission(call): messages.append(self._permission_denied_message(call)) continue try: result = self.tools.execute(call.name, call.arguments) messages.append(self._tool_result_message(call.id, result)) except Exception as e: messages.append(self._tool_error_message(call.id, str(e))) return "达到最大迭代次数,任务未完成。" def _check_permission(self, call) -> bool: tool_config = self.profile.get_tool_config(call.name) if tool_config.level == "dangerous" and tool_config.require_approval: return self._request_approval(call) return True几个关键点:
- max_iterations 必须有。没有这个限制,模型可能陷入无限循环,烧钱又烧时间。我一般设 10,复杂任务设 20。
- 工具执行必须 try-except。工具报错不能让整个 Agent 崩溃,要把错误信息返回给模型,让它自己决定怎么办。
- 权限检查在工具执行前。危险操作先拦截,再决定是否放行。
4.3 Profile 加载与热更新
Profile 加载要支持从文件、环境变量、远程配置中心三个来源读取,优先级从高到低。这样本地开发用文件,生产环境用配置中心,灵活切换。
import yaml from pathlib import Path class ProfileLoader: def __init__(self, profile_dir: Path): self.profile_dir = profile_dir self._cache = {} def load(self, name: str) -> dict: if name in self._cache: return self._cache[name] path = self.profile_dir / f"{name}.yaml" with open(path, encoding="utf-8") as f: config = yaml.safe_load(f) if "inherit" in config: base = self.load(config["inherit"]) config = self._deep_merge(base, config) self._cache[name] = config return config def _deep_merge(self, base: dict, override: dict) -> dict: result = base.copy() for key, value in override.items(): if key in result and isinstance(result[key], dict) and isinstance(value, dict): result[key] = self._deep_merge(result[key], value) else: result[key] = value return result热更新怎么做?简单方案是监听文件变化,复杂方案是接配置中心。我一般用文件监听加一个/reload接口,改完 Profile 调一下接口就生效,不用重启服务。
4.4 工具注册与执行沙箱
工具注册用装饰器模式最方便:
class ToolRegistry: def __init__(self): self._tools = {} def register(self, name: str, schema: dict, level: str = "read"): def decorator(func): self._tools[name] = { "func": func, "schema": schema, "level": level } return func return decorator def execute(self, name: str, arguments: dict) -> Any: tool = self._tools.get(name) if not tool: raise ValueError(f"未知工具: {name}") return tool["func"](**arguments)执行沙箱是生产环境的必备项。如果 Agent 能执行代码或 shell 命令,一定要放在隔离环境里跑——Docker 容器、子进程加资源限制、或者专门的沙箱服务。我见过 Agent 执行rm -rf把测试环境删了的案例,虽然数据能恢复,但那个下午整个团队都在救火。
沙箱的基本配置:CPU 限制、内存限制、超时时间、网络隔离、文件系统只读挂载。这些在 Docker 里都是现成的,--cpus、--memory、--network none几个参数搞定。
4.5 生产部署:容器化与可观测性
部署这块,容器化是标配。一个最小可用的 Dockerfile:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY kernel/ ./kernel/ COPY profiles/ ./profiles/ COPY runtime/ ./runtime/ ENV AGENT_PROFILE=default EXPOSE 8000 CMD ["uvicorn", "kernel.api:app", "--host", "0.0.0.0", "--port", "8000"]可观测性三件套:日志、指标、追踪。
- 日志:每次模型调用、工具调用都记一条结构化日志,包含 session_id、耗时、token 数、工具名、结果状态。
- 指标:QPS、P95 延迟、工具调用成功率、token 消耗速率。这些用 Prometheus 采集。
- 追踪:一次用户请求触发的所有模型调用和工具调用串成一条 trace,方便排查问题。
我特别想强调token 消耗监控。Agent 的 token 消耗比普通对话高一个数量级,因为每轮都要把完整上下文发过去。不监控的话,月底账单能吓你一跳。我的做法是设一个日消耗上限,超了就降级到更便宜的模型或者直接拒绝服务。
4.6 灰度发布与版本回滚
发行版要有版本号,部署要支持灰度。最简单的灰度方案是按流量比例切分:新版本先接 5% 流量,观察指标正常后逐步放大到 100%。
Profile 的版本管理用 Git tag 就行,v1.0.0、v1.1.0这样。回滚就是切回旧 tag 重新部署。关键是每次发布都要记录变更内容——改了哪个 Profile、调了什么参数、预期效果是什么。不然回滚的时候你都不知道该回滚到哪个版本。
5. 常见问题与排查技巧实录
5.1 工具调用失败排查速查表
工具调用是 Agent 最容易出问题的环节。我把常见问题和排查方法整理成表:
| 现象 | 可能原因 | 排查方法 | 解决 |
|---|---|---|---|
| 模型不调用工具 | prompt 没说明工具用途 | 检查 system_prompt | 在 prompt 里明确工具使用场景 |
| 工具参数格式错 | schema 描述不清 | 打印模型原始输出 | 完善参数 description,加示例 |
| 调用不存在的工具 | 工具列表与 prompt 不一致 | 对比 tools 和 prompt | 统一工具注册来源 |
| 工具执行超时 | 外部依赖慢 | 看工具执行日志 | 加超时和重试,或异步化 |
| 反复调用同一工具 | 结果没被正确理解 | 看上下文消息 | 优化工具返回格式,加摘要 |
5.2 上下文爆炸的三种应对策略
Agent 跑久了上下文会越来越长,最后超出模型窗口。三种应对:
- 滑动窗口:只保留最近 N 轮,简单粗暴但有效。适合对话类。
- 摘要压缩:把早期对话用模型总结成一段话,保留关键信息。适合长任务。
- 外部记忆:把历史存到向量库,需要时检索。适合知识密集场景。
我一般组合使用:最近 5 轮保留原文,5-15 轮做摘要,15 轮以上存向量库。这样上下文长度可控,关键信息不丢。
5.3 模型“幻觉调用”的识别与拦截
所谓幻觉调用,就是模型编造了一个不存在的工具名,或者编造了工具返回结果。识别方法很简单:工具执行前校验工具名是否在注册表里,工具返回后校验结果是否来自真实执行。
拦截策略:
def validate_tool_call(self, call): if call.name not in self.tools: return False, f"工具 {call.name} 不存在" if not self._validate_arguments(call.name, call.arguments): return False, "参数格式错误" return True, None参数校验用 JSON Schema,每个工具注册时带上 schema,调用时校验。这样能在执行前拦掉大部分错误调用。
5.4 生产环境的三条血泪经验
第一条:永远不要相信模型的输出格式。你让它输出 JSON,它可能输出带 markdown 代码块的 JSON,可能输出 JSON 加一段解释,可能字段名大小写不一致。解析时一定要做容错,用正则提取、用宽松解析器、加 fallback。
第二条:工具要有幂等性。Agent 可能因为超时重试而重复调用同一个工具。如果工具是“创建订单”,重复调用就出大事了。所以写操作类工具要么支持幂等键,要么在 Agent 层做去重。
第三条:给 Agent 设预算。不只是 token 预算,还有时间预算、工具调用次数预算。我见过一个 Agent 因为一个 bug 循环调用了 200 次搜索工具,虽然每次都不贵,加起来也是一笔钱。预算超了就强制终止,返回一个友好的错误信息。
5.5 Profile 调试的实用技巧
调试 Profile 最有效的方法是单步回放。把一次完整的 Agent 执行过程的所有消息、工具调用、模型响应都存下来,然后可以逐条回放,看模型在哪一步做了错误决策。
我一般会在开发环境开一个 debug 模式,把每次执行的完整 trace 存成 JSON 文件。出问题时打开文件,从后往前看,通常很快就能定位到是哪一步的上下文导致了错误输出。
另一个技巧是A/B 对比。改 Profile 时不要直接覆盖,而是新建一个版本,用同一批测试用例跑两个版本,对比结果。这样能客观评估改动效果,而不是凭感觉。
6. 发行版的扩展与生态建设
6.1 插件机制:让用户自己加工具
一个发行版要活得久,必须允许用户扩展。插件机制的核心是约定大于配置:用户把插件放在指定目录,实现指定接口,发行版自动加载。
# plugins/my_plugin.py from agent_sdk import tool @tool(name="query_weather", level="read") def query_weather(city: str) -> dict: """查询指定城市的天气。 Args: city: 城市名称,如"北京" """ return {"city": city, "temp": 25, "condition": "晴"}发行版启动时扫描plugins/目录,动态导入,注册工具。用户不需要改内核代码,加个文件就行。
6.2 Profile 市场:共享与复用
当你的发行版有了一定用户量,可以搞一个 Profile 市场——用户上传自己写的 Profile,别人下载使用。这其实就是热词里提到的dsh plugin --profile web add那种思路:通过命令行从远程仓库拉取 Profile 并安装。
实现上很简单:Profile 就是一个 YAML 文件,打包成 tar.gz,上传到对象存储,维护一个索引文件。用户执行agent profile install code-reviewer就从索引里找到下载地址,拉下来解压到profiles/目录。
6.3 多智能体协作的 Profile 设计
单个 Agent 能力有限,复杂任务需要多个 Agent 协作。这时候 Profile 的设计要考虑角色分工和通信协议。
比如一个软件开发场景,可以有这几个 Profile:
architect:负责需求分析和架构设计,输出设计文档。coder:负责根据设计文档写代码。reviewer:负责审查代码,输出审查意见。tester:负责写测试用例并执行。
它们之间通过消息队列或者共享文件系统通信。每个 Profile 有自己的工具集和权限,architect 只能读不能写代码,coder 能写代码但不能改架构文档。这种分工明确的多智能体系统,比一个全能 Agent 稳定得多。
6.4 企业级部署的额外考量
企业环境和个人项目差别很大,几个额外要点:
- 权限对接:Agent 的工具调用要接入企业现有的权限系统,不能自己搞一套。
- 审计合规:所有操作要有审计日志,能追溯到具体的人和时间。
- 数据隔离:不同部门的数据要隔离,Agent 不能跨部门访问。
- 私有化部署:很多企业要求模型和数据都在内网,不能调外部 API。
这些需求在 Profile 设计时就要考虑进去,比如加一个tenant字段标识租户,加一个data_scope字段限定数据访问范围。
7. 我个人的一些实操体会
做 Agent 发行版这件事,技术难度其实没有想象中那么高,难的是工程化的耐心。把一个大而全的 Agent 拆成内核、Profile、运行时三层,每一层都做到可配置、可测试、可回滚,这个过程很枯燥,但收益是长期的。
我最大的体会是:Profile 的设计要克制。一开始总想加很多配置项,觉得越灵活越好。但实际上配置项越多,用户越不知道怎么配,维护成本也越高。好的 Profile 应该是“默认值覆盖 80% 场景,剩下 20% 通过少量配置解决”。
另一个体会是:测试用例比代码更重要。Agent 的行为是概率性的,同样的输入可能得到不同的输出。所以你需要一套测试用例,覆盖典型场景和边界情况,每次改 Profile 都跑一遍,看通过率有没有下降。这套用例是发行版质量的保障。
最后分享一个小技巧:给每个 Profile 写一份“使用说明”,不是给开发者看的,是给最终用户看的。说明这个 Profile 适合什么场景、有什么限制、怎么调优。这份文档能大幅降低用户的上手成本,也是发行版专业度的体现。
这个方向后续还可以扩展的地方很多,比如 Profile 的自动化调优(用测试集自动搜索最优参数)、多模态 Profile(支持图片和语音输入)、以及 Profile 的版本对比工具。但那是下一步的事了,先把当前这套跑通、跑稳,比什么都重要。