小蜜陪护机器人 - Agent扩展开发指南
Agent扩展开发指南
概述
小蜜陪护机器人采用多 Agent 协作架构,通过任务规划专家将用户意图路由到专业 Agent 执行。本文详细介绍如何通过增加工作 Agent 和相应的工具来扩展系统功能,无需修改核心 C++ 代码,仅需配置 JSON 文件和编写 JavaScript 脚本。
与插件开发指南的区别:
- 插件开发指南:侧重 C++ DLL 插件开发,封装底层功能
- Agent 扩展指南:侧重 JSON/JS 配置层面,将已有功能暴露给 Agent 使用
一、Agent 架构简述
1.1 整体架构
系统采用分层协作架构,数据流向如下:
用户输入(语音/文字)
│
▼
TaskPlanner(任务规划专家)—— 解析意图,生成执行计划
│
└──→ WorkerAgent(专业 Agent)—— 执行具体任务
│
└──→ ToolManager(工具管理器)—— 加载和执行工具脚本
│
└──→ ScriptManager(脚本管理器)—— 运行 JavaScript
│
└──→ Plugin(插件)—— 底层功能实现
1.2 核心组件职责
| 组件 | 职责 | 文件位置 |
|---|---|---|
| TaskPlanner | 任务规划专家,负责意图解析和路由 | bin/agentconfigs/agents/任务规划专家.agent |
| WorkerAgent | 专业 Agent,执行具体任务 | bin/agentconfigs/agents/*.agent |
| ToolManager | 工具管理器,加载和执行工具 | bin/agentconfigs/tools/*.tool |
| ScriptManager | JavaScript 引擎,运行工具脚本 | src/scriptmanager.cpp |
| Plugin | C++ DLL 插件,封装底层功能 | bin/agentconfigs/Plugins/*.dll |
1.3 完整调用流程
用户说:"明天天气怎么样?"
│
▼
1. ASR 语音识别 → "明天天气怎么样?"
│
▼
2. TaskPlanner(任务规划专家)
└── 解析意图:天气相关 → 路由到「天气专家」
│
▼
3. WorkerAgent(天气专家)
└── 根据指令,决定使用「天气查询」工具
│
▼
4. ToolManager
└── 加载并执行「天气查询.tool」的 JavaScript 脚本
│
▼
5. ScriptManager
└── 运行脚本,调用全局对象 weatherController.getTomorrowWeather()
│
▼
6. Plugin(WeatherPlugin)
└── 返回明天的天气数据
│
▼
7. 结果返回
└── Tool → Agent → TaskPlanner → TTS → 用户
二、扩展系统功能的步骤
2.1 扩展流程概览
要为系统增加新功能,需要完成以下三个核心步骤:
步骤1: 创建专业 Agent 配置文件
│
▼
步骤2: 创建工具定义文件(包含 JS 脚本)
│
▼
步骤3: 在任务规划专家中注册新 Agent
2.2 步骤详解
步骤1:创建专业 Agent 配置文件
在 bin/agentconfigs/agents/ 目录下创建新文件,命名为 {Agent名称}.agent,格式如下:
{
"name": "Agent名称",
"instructions": "Agent的角色定义和行为指令(System Prompt)",
"tools": ["工具1", "工具2"]
}
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | Agent 名称,用于路由和识别 |
instructions |
string | System Prompt,定义 Agent 的角色、决策规则、输出格式等 |
tools |
array | Agent 可以使用的工具列表 |
步骤2:创建工具定义文件
在 bin/agentconfigs/tools/ 目录下创建新文件,命名为 {工具名称}.tool,格式如下:
{
"name": "工具名称",
"description": "工具功能描述(供 LLM 理解何时调用)",
"params": [
{
"type": "string/number",
"name": "参数名",
"description": "参数说明",
"required": true
}
],
"auto_verified": true,
"script_content": "JavaScript 脚本内容"
}
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 工具名称(中文,Agent 可见) |
description |
string | 工具功能描述,帮助 LLM 判断何时调用 |
params |
array | 参数列表,定义输入参数 |
auto_verified |
boolean | 是否自动验证(true = 工具结果无需模型再次验证) |
script_content |
string | JavaScript 脚本,实现工具逻辑 |
步骤3:在任务规划专家中注册
修改 bin/agentconfigs/agents/任务规划专家.agent,需要更新两处:
- 路由表:在路由规则中添加新意图到新 Agent 的映射
- agent 数组:将新 Agent 名称添加到
agent数组中
三、工具调用插件的机制
3.1 脚本引擎与全局对象
ScriptManager 维护一个单例 JavaScript 引擎,插件加载时会将其 QObject 实例注册为全局对象:
// PluginManager 注册流程
if (plugin->scriptObject()) {
QString objName = plugin->scriptObjectName();
m_scriptManager->registerGlobalObject(objName, plugin->scriptObject());
}
3.2 已注册的全局对象
| 全局对象名 | 插件 | 功能 |
|---|---|---|
weatherController |
WeatherPlugin | 天气查询 |
taskScheduler |
TaskSchedulerPlugin | 定时任务 |
musicPlayer |
MusicPlayerPlugin | 音乐播放 |
radioTvPlayer |
RadioTvPlayerPlugin | 广播/电视播放 |
volumeController |
VolumeControlPlugin | 音量控制 |
networkInfo |
NetworkInfoPlugin | 网络信息查询 |
3.3 工具脚本调用插件的方式
工具脚本中直接调用全局对象的 Q_INVOKABLE 方法:
// 天气查询工具脚本示例
function 天气查询() {
var obj = JSON.parse(params);
var queryDate = obj.查询日期 || 'today';
// 调用插件暴露的全局对象
var result = weatherController.getTodayWeather();
// 格式化结果
result = JSON.parse(JSON.stringify(result));
return JSON.stringify({
success: true,
result: result
});
}
天气查询();
3.4 工具脚本编写要点
- 参数解析:从
params全局变量获取 JSON 格式的参数 - 全局对象调用:直接使用插件注册的全局对象
- 返回格式:必须返回 JSON 字符串,包含
success字段 - 异常处理:捕获并返回错误信息
- 日志输出:使用
printlog(level, message)输出调试信息
四、完整示例:添加「笑话专家」Agent
4.1 功能需求
创建一个「笑话专家」Agent,能够:
- 讲一个随机笑话
- 根据主题讲笑话(如动物、职场、校园等)
4.2 步骤1:创建笑话专家 Agent 配置
创建文件 bin/agentconfigs/agents/笑话专家.agent:
{
"name": "笑话专家",
"instructions": "你是一个幽默风趣的笑话专家,负责给用户讲笑话。\n\n## 决策规则\n1. 用户说「讲个笑话」或类似请求 → 调用讲笑话工具\n2. 用户指定主题 → 调用工具并传入主题参数\n3. 用户要求讲多个笑话 → 多次调用工具\n4. 用户只是闲聊 → 直接回应,不调用工具\n\n## 输出格式\n工具调用:{\"status\":\"tool_call\",\"tool_name\":\"讲笑话\",\"arguments\":{\"主题\":\"主题名称\"}}\n最终答案:{\"status\":\"done\",\"result\":\"笑话内容\"}\n\n## 回复风格\n- 笑话讲完后,可以加一句俏皮话或表情符号\n- 如果用户不笑,可以换一个继续讲\n- 保持轻松幽默的语气",
"tools": [
"讲笑话"
]
}
4.3 步骤2:创建讲笑话工具定义
创建文件 bin/agentconfigs/tools/讲笑话.tool:
{
"name": "讲笑话",
"description": "讲一个笑话,可以指定主题:动物、职场、校园、夫妻、冷笑话",
"params": [
{
"type": "string",
"name": "主题",
"description": "笑话主题:动物、职场、校园、夫妻、冷笑话,不指定则随机",
"required": false
}
],
"auto_verified": true,
"script_content": "function 讲笑话() {\n var obj;\n try { obj = JSON.parse(params); } catch(e) { return JSON.stringify({success: false, result: '参数格式错误'}); }\n var topic = obj.主题 || '';\n \n var jokes = {\n '动物': [\n '为什么企鹅只有肚子是白的?因为手太短,洗澡只能洗到肚子!',\n '大象和蚂蚁结婚,第二天大象死了。蚂蚁哭着说:这辈子再也不干这么累的活了!',\n '乌龟和兔子赛跑,兔子中途睡着了。等它醒来,乌龟已经到终点了,兔子说:早知道我就不戴墨镜了!'\n ],\n '职场': [\n '老板问员工:你觉得你值多少钱?员工说:我觉得我值年薪100万。老板:那我给你年薪50万,你干两份活。',\n '程序员的老婆让他去买酱油,他回来说:超市里没有酱油接口,我无法完成购买请求。',\n 'HR问面试者:你最大的缺点是什么?面试者:诚实。HR:我不觉得这是缺点。面试者:我不在乎你怎么想。'\n ],\n '校园': [\n '老师:小明,你知道为什么闪电总是比雷声快吗?小明:因为眼睛长在耳朵前面!',\n '学生问老师:为什么要学数学?老师:因为数学能帮你在菜市场不被坑。学生:可是我可以用计算器啊!',\n '考试时,小明偷看同桌的答案。老师走过来问:你在看什么?小明:我在看他的答案是不是和我的一样。'\n ],\n '夫妻': [\n '老婆:你知道我为什么嫁给你吗?老公:因为我长得帅?老婆:因为你老实。老公:那现在呢?老婆:因为你傻。',\n '老公回家晚了,老婆问:你去哪了?老公:加班。老婆:我刚才给你们公司打电话,他们说你早就走了。老公:那是因为我加班到一半太累了,去隔壁公司休息了一下。',\n '老婆:如果你中了五百万,你会怎么样?老公:我会分你一半。老婆:那如果你中了一千万呢?老公:那我就分你五百万。'\n ],\n '冷笑话': [\n '为什么海象总是很开心?因为它有一颗海象的心!',\n '什么动物最容易摔倒?狐狸,因为它太狡猾(脚滑)了!',\n '为什么苹果手机不会感冒?因为它有iOS(爱奥西斯)!'\n ],\n 'default': [\n '一位程序员走进酒吧,要了一杯酒。服务员问:需要加冰吗?程序员说:不用了,我自带了。',\n '医生问病人:你哪里不舒服?病人说:我睡不着觉。医生:为什么?病人:因为我是程序员,我的生物钟是夜猫子模式。',\n '甲:你知道为什么程序员不喜欢过情人节吗?乙:为什么?甲:因为他们分不清0和1哪个是真爱。',\n '老师让同学们用「如果」造句。小明:如果我有一百万,我就买个大房子。小红:如果我有一百万,我就买好多好吃的。小刚:如果这是个问题,我就回答它。'\n ]\n };\n \n var pool = jokes[topic] || jokes['default'];\n var joke = pool[Math.floor(Math.random() * pool.length)];\n \n return JSON.stringify({\n success: true,\n topic: topic || '随机',\n result: joke\n });\n}\n讲笑话();"
}
4.4 步骤3:在任务规划专家中注册
修改 bin/agentconfigs/agents/任务规划专家.agent:
更新路由表(在路由规则中添加):
"路由表": "数学/计算/算术 → 数学专家\n时间/日期/星期/几号 → 时间专家\n定时/提醒/闹钟/倒计时 → 定时任务专家\n网络/IP/子网掩码/网关 → 网络专家\n广播/电视/收音机/电台/电视台/频道 → 广播电视播放专家\n音乐/歌曲/唱歌/听歌/切歌 → 音乐播放专家\n音量/声音/静音/TTS音量 → 音量控制专家\n天气/下雨/刮风/温度/气温/湿度 → 天气专家\n笑话/幽默/搞笑/段子 → 笑话专家\n其他/聊天/问候/不确定 → 贴心聊天助手"
更新 agent 数组(添加「笑话专家」):
"agent": ["数学专家","贴心聊天助手","时间专家","定时任务专家","网络专家","广播电视播放专家","音乐播放专家","音量控制专家","天气专家","笑话专家"]
4.5 预期交互效果
用户:讲个笑话
│
▼
任务规划专家 → 识别意图:笑话/幽默 → 路由到「笑话专家」
│
▼
笑话专家 → 决定调用「讲笑话」工具
│
▼
工具脚本 → 返回随机笑话:"为什么程序员不喜欢过情人节吗?因为他们分不清0和1哪个是真爱。"
│
▼
笑话专家 → 整理回答:"好的,给你讲一个:为什么程序员不喜欢过情人节吗?因为他们分不清0和1哪个是真爱。😂"
│
▼
TTS → 语音输出
五、扩展技巧与最佳实践
5.1 Agent 指令编写技巧
- 明确角色定位:让 Agent 清楚自己的职责范围
- 定义决策规则:告诉 Agent 何时调用工具、何时直接回答
- 指定输出格式:必须包含
status字段(tool_call或done) - 提供示例:帮助 LLM 理解期望的输出格式
5.2 工具脚本编写技巧
- 参数校验:检查参数是否完整、格式是否正确
- 错误处理:捕获异常并返回明确的错误信息
- 日志输出:使用
printlog()记录关键步骤,便于调试 - 返回格式统一:始终返回包含
success字段的 JSON - 脚本独立:每个工具脚本应独立运行,不依赖其他脚本
5.3 调试方法
- 查看日志:检查
bin/logs/目录下的日志文件 - 脚本调试:在工具脚本中使用
printlog()输出调试信息 - 测试工具:通过修改 Agent 指令,强制调用特定工具
- 验证注册:确认任务规划专家的路由表和 agent 数组已正确更新
5.4 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Agent 未被调用 | 任务规划专家未注册该 Agent | 检查 agent 数组和路由表 |
| 工具未被调用 | Agent 指令未正确定义调用规则 | 检查 instructions 中的决策规则 |
| 脚本执行失败 | JavaScript 语法错误 | 检查 script_content 的语法 |
| 参数解析失败 | 参数格式不正确 | 确保传入的参数是合法 JSON |
| 全局对象未定义 | 插件未正确加载 | 检查插件 DLL 是否在正确目录 |
六、无插件场景:纯脚本工具
并非所有工具都需要插件支持。如果功能可以完全通过 JavaScript 实现(如计算、数据处理、本地文件操作等),可以直接编写纯脚本工具,无需开发 C++ 插件。
6.1 纯脚本工具示例
计算器工具(无需插件,纯 JavaScript 实现):
{
"name": "计算器",
"description": "执行基本数学运算:加法、减法、乘法、除法",
"params": [
{
"type": "string",
"name": "操作",
"description": "要执行的数学操作,可选值:加、减、乘、除",
"required": true
},
{
"type": "number",
"name": "a",
"description": "第一个操作数",
"required": true
},
{
"type": "number",
"name": "b",
"description": "第二个操作数",
"required": true
}
],
"auto_verified": true,
"script_content": "function 计算器() { var obj = JSON.parse(params); var a = parseFloat(obj.a); var b = parseFloat(obj.b); var result; switch(obj.操作) { case '加': result = a + b; break; case '减': result = a - b; break; case '乘': result = a * b; break; case '除': if(b !== 0) { result = a / b; } else { return JSON.stringify({success: false, result: '除数不能为零'}); } break; default: return JSON.stringify({success: false, result: '不支持的操作'}); } return JSON.stringify({success: true, result: String(result)}); } 计算器();"
}
6.2 何时需要开发插件
| 场景 | 是否需要插件 | 说明 |
|---|---|---|
| 网络请求 | 需要 | JavaScript 无法直接发起 HTTP 请求 |
| 系统调用 | 需要 | JavaScript 无法直接调用系统 API |
| 文件操作 | 需要 | JavaScript 文件操作能力有限 |
| 数据计算 | 不需要 | 纯 JavaScript 即可实现 |
| 数据处理 | 不需要 | 纯 JavaScript 即可实现 |
| 逻辑判断 | 不需要 | 纯 JavaScript 即可实现 |
总结
通过 Agent + Tool 的配置方式,无需修改核心 C++ 代码即可扩展系统功能:
- 创建 Agent 配置:定义专业 Agent 的角色和行为
- 创建工具脚本:实现具体功能,可调用插件或纯 JS 实现
- 注册到规划专家:更新路由表和 agent 数组
这种设计使得系统具备高度的可扩展性和灵活性,非程序员也能通过修改 JSON 和 JS 文件定制系统行为。
相关文档
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)