瑞芯微RK3576开发板部署模型|端侧Agent工具调用实现拆解

大家好,这里是触觉智能,一家专注于瑞芯微RK平台嵌入式方案商。本文分享RK3576开发板Linux系统端侧Agent工具调用实现拆解,对于受限于内存和SDK能力的开发者,这份实战经验很有参考价值。建议收藏。

一句话讲清这个功能

不用Function Calling,只靠"一条协议+一个中断",就能让不支持工具调用的模型在RK3576 Debian12设备上自主执行shell命令:

本功能实现"模型自主调用设备shell工具 "的agent循环:用户用自然语言提问,模型按ReAct 协议输出<tool>命令</tool>,运行时在回调检测到闭合标签即中断生成 ,经posix_spawn安全执行命令(15s超时、4KB输出限长、命令去重防循环),再将真实结果以"作答任务"prompt回喂模型,使其基于真实设备数据作答;命令执行失败会自动换命令重试,模型内置的长思考内容做隐藏处理并展示占位提示,避免用户误以为程序假死。

实测视频演示:https://www.bilibili.com/video/BV1ivbY6bE2B/

它想解决什么问题?

在触觉智能RK3576开发板部署运行DeepSeek-R1-Distill-1.5B轻量化模型时,我们遇到明显落地痛点:模型原生不支持Function Calling (R1蒸馏版本未针对工具调用场景专项微调),同时配套RKLLM 旧版SDK缺少回调暂停、推理续跑这类高级原生能力。

常规实现方案是上层程序预先封装、主动替模型查询设备信息,智能化程度很低。我们期望达成自主交互效果:

|----------------------------------------------------------------------|
| 用户:当前内存占用多少? 模型(自主决策):需要执行free -h 获取硬件数据→ 拉取真实系统输出 → 整合信息给出最终答案。 |

这是标准ReAct工具调用Agent循环。阻碍在于:RKLLM同步推理接口rkllm_run为阻塞式调用,推理一旦启动,无法在流式生成中途插入shell命令执行逻辑。因此核心目标明确:在RK3576 Debian12端侧流式推理过程中截获工具调用指令,执行系统命令后回填结果,继续完成推理作答。

整体流程:一条"截流-执行-回喂"的循环

复制代码
用户提问   │   ▼┌─ 第1轮 rkllm_run ───────────────────────────┐│  模型流式输出,回调逐 token 累积             ││  检测到 <tool>free -h</tool>(完整闭合标签) ││      ↓ 调用 rkllm_abort 中断本轮生成         │└─────────────────────────────────────────────┘   │ 提取命令:free -h   ▼posix_spawn 执行(15s 超时 / 4KB 限长 / 去重)→ 得到Debian12系统真实数据   │   ▼┌─ 第2轮 rkllm_run ───────────────────────────┐│  全新"作答任务"prompt:                       ││  用户原始问题 + [free -h Debian系统输出]      ││  "严禁再调用工具,直接给出最终答案"           ││  → 模型基于硬件真实数据作答                   │└─────────────────────────────────────────────┘   │   ▼向客户端输出答案 + token 统计

流程关键节点在首轮推理末尾:调用 rkllm_abort 在完整工具标签解析完成瞬间中断生成 ,完成推理截流;模型刚输出需要执行的系统命令,程序立刻接管调度Debian12系统shell。

核心设计逐段拆解

  • 协议:用<tool>命令</tool>让模型 "开口要工具"

模型本身无原生工具调用能力,通过系统提示词统一约定交互协议:需要查询RK3576板卡硬件、系统信息时,输出成对闭合专用标签。

复制代码
static const char *SYSTEM_PROMPT =
    "你是RK3576 Debian12设备上的智能助手,通过执行 shell 命令查看设备状态后作答。\n"
    "需要查询硬件/系统信息时,严格输出且成对闭合(命令单行):\n"
    "<tool>COMMAND</tool>\n"
    "你可以执行Debian系统任意 shell 命令来获取所需设备信息。\n"
    "输出 <tool> 标签后必须立即停止,等待系统返回结果,禁止自行编造内容。\n";

程序侧配套解析逻辑,仅识别完整闭合、内容长度合规的标签文本,正文内偶然出现tool字符不会误触发工具逻辑:

复制代码
// 在整段 cur 里找第一个【完整闭合】的 <tool>...</tool>
static bool extract_tool_tag(const std::string &cur, std::string &cmd, size_t &tag_start)
{
    size_t open = cur.find("<tool>");
    if (open == std::string::npos) return false;
    size_t close = cur.find("</tool>", open + 6);
    if (close == std::string::npos) return false;
    if (close - open > MAX_TAG_LEN) return false;   // 标签内容过长,视为普通正文
    tag_start = open;
    cmd = trim(cur.substr(open + 6, close - open - 6));
    return true;
}
  • 中断:在回调里 "截流"

模型输出以逐Token流式回调推送,每次触发RKLLM_RUN_NORMAL回调时,拼接新增文本至全局缓存,同步执行标签解析;一旦捕获完整工具调用标签,设置原子标记并调用rkllm_abort终止当前推理流程:

复制代码
case RKLLM_RUN_NORMAL:
    if (ctx->tool_pending.load()) return;   // abort后延迟到达的回调直接丢弃
    ctx->run_tokens++;
    ctx->cur += result->text;               // 累积本轮回调全部输出文本
    if (extract_tool_tag(ctx->cur, found_cmd, found_start)) {
        ctx->pending_cmd = found_cmd;       // 缓存待执行shell命令
        ctx->tool_pending.store(true);      // 标记存在待执行工具任务
        rkllm_abort(llmHandle);             // ★ 中断RKLLM推理,回到主循环调度Debian shell
        break;
    }
    // ... 无标签的普通文本正常流式下发客户端

工程容错设计:不依赖rkllm_abort 返回值做业务判断(RKLLM SDK 未明确定义 abort 后标准行为),以原子变量 tool_pending 作为唯一业务裁决依据;即使 abort 未即时终止生成、模型输出达到 Token 上限,本轮推理结束后全量文本兜底扫描,仍能正确识别工具调用标签,适配 RK3576 Debian12 端侧不稳定推理场景。

  • 安全执行:为什么必须用posix_spawn

RK3576板卡内存仅4GB,早期使用popen()(底层fork实现)执行Debian shell命令时,频繁出现OOM内存溢出崩溃 。根源:fork会完整复制父进程全部地址空间,而父进程常驻1.5B模型权重与大量缓存,内存占用接近2GB。
替换为posix_spawn彻底解决内存问题,glibc下该接口基于clone(CLONE_VM)实现,子进程与父进程共享虚拟内存,不做全量拷贝,适配低内存端侧设备:

复制代码
// fork会复制LLM进程巨大地址空间 → RK3576 Debian环境极易OOM;posix_spawn共享内存无拷贝
posix_spawn_file_actions_t fa;
posix_spawn_file_actions_init(&fa);
posix_spawn_file_actions_adddup2(&fa, fds[1], STDOUT_FILENO);  // 子进程标准输出重定向管道
posix_spawn_file_actions_adddup2(&fa, fds[1], STDERR_FILENO);
posix_spawn(&pid, "/bin/sh", &fa, &attr, argv, environ);

两层安全防护规避Debian系统风险:

(1)15秒执行超时:拦截top、持续输出类长耗时命令,防止板卡卡死;

(2)4KB输出长度限制:避免dmesg打印GB级系统日志占用内存;
通过select轮询读取管道,超时 / 超限直接发送 SIGKILL 销毁子进程。

  • 防循环:去重+上限 + 失败重试

1.5B小模型存在逻辑缺陷:获取系统返回数据后仍重复发起相同shell调用,造成无效循环。配套三层限制机制适配RK3576 Debian部署:

(1)命令去重:标准化空格格式(free -h与free -h判定为同一条),记录单次请求已执行命令,重复调用直接返回提示文本,不再二次调度 shell;

复制代码
// fork会复制LLM进程巨大地址空间 → RK3576 Debian环境极易OOM;posix_spawn共享内存无拷贝
posix_spawn_file_actions_t fa;
posix_spawn_file_actions_init(&fa);
posix_spawn_file_actions_adddup2(&fa, fds[1], STDOUT_FILENO);  // 子进程标准输出重定向管道
posix_spawn_file_actions_adddup2(&fa, fds[1], STDERR_FILENO);
posix_spawn(&pid, "/bin/sh", &fa, &attr, argv, environ);

(2)调用次数硬上限:单次提问最多允许3次工具调用,杜绝无限循环推理;

(3)执行失败重试:检测shell命令退出码,区分执行成功 / 失败;存在至少一条成功命令则进入最终作答流程;若全部命令执行报错,构造重试 Prompt 引导模型更换适配Debian12的查询指令。

  • 作答任务:不给模型历史,打破"照抄循环"

初期方案直接拼接模型上一轮完整输出继续推理,R1蒸馏模型会重复复制自身历史文本,出现无限复读卡死。重构为独立作答Prompt,仅保留用户原始问题+Debian系统命令输出,剔除模型全部历史思考、标签文本,强制模型直接输出答案:

复制代码
std::string norm = normalize_cmd(cmd);   // 统一命令空格格式
for (auto &ec : ctx.executed_cmds)
    if (ec == norm) { dup = true; break; }
if (dup)
    result = "[提示: 命令 '" + cmd + "' 之前已在本请求执行过。请更换Debian shell命令或直接基于已有数据作答。]";

主循环根据任务状态自动切换三类Prompt模板:

复制代码
static std::string build_answer_prompt(const AgentCtx &ctx)
{
    std::string p = BOS; p += U_OP; p += SYSTEM_PROMPT;
    p += "\n\n用户问题:"; p += ctx.question;
    p += "\n\n以下shell命令已在RK3576 Debian12设备执行,下方为系统真实输出数据:\n";
    for (auto &r : ctx.tool_results) { p += "结果:" + r + "\n"; }
    p += "\n请根据以上Debian硬件数据直接、简洁地用中文回答用户问题。\n";
    p += "严禁再调用工具,禁止输出 <tool> 标签,直接给出最终答案。";
    p += A_MK;
    return p;
}
  • 体验:隐藏思考+占位符

DeepSeek-R1系列模型推理前会输出大量长文本思考内容,直接推送给客户端会刷屏,用户容易误以为RK3576程序卡死。增加前端显示过滤逻辑:完全屏蔽内部文本,进入思考区块时仅下发思考中...占位提示,提升 Debian 端侧交互体验:

复制代码
if (ctx.tool_results.empty())
    prompt = build_prompt_round0(ctx);     // 首轮推理:允许发起shell工具调用
else if (ctx.have_good_result)
    prompt = build_answer_prompt(ctx);     // 存在有效Debian系统数据:进入纯作答流程
else
    prompt = build_retry_prompt(ctx);      // 所有命令执行失败:引导模型更换查询命令重试

占位提示仅做展示层过滤,模型完整思考文本仍会在程序内部缓存,不影响工具标签识别与推理逻辑。

可以怎么拓展

多工具接入 :除Debian原生shell外,可拓展文件读写、HTTP网络请求、外设传感器采集、GPIO硬件控制等工具,让模型从查询板卡状态升级为自主操控RK3576硬件;

升级新版RKLLM SDK :新版SDK原生支持Function Calling、KV缓存续推理,省去每轮推理完整Prefill开销,大幅提升RK3576 Debian设备推理速度;

Prompt缓存+多模型切换 :缓存固定系统提示词降低预处理耗时;简单任务使用小尺寸快速模型,复杂设备任务使用1.5B推理模型,平衡端侧性能与效果;

定时巡检Agent :后台定时器常驻运行,模型定期自动执行Debian系统监控命令,主动上报RK3576内存、温度、负载异常,实现无人值守设备运维。

得借鉴的设计范式****

|--------------------------|----------------------------------------------------------------|
| 设计 | 解决什么问题 |
| <tool> 标签协议+回调截流 | 在 RK3576 无原生 Function Calling 的模型上实现工具调用,仅依赖 Prompt 约定,无模型改造成本 |
| tool_pending 状态机 + 兜底扫描 | 不依赖 RKLLM SDK 未标准化的 abort 接口,端侧推理流程稳定可控 |
| 全新独立 "作答任务"Prompt(不回喂历史) | 根治R1小模型复读、无限循环问题,适配RK3576 Debian推理场景 |
| posix_spawn 替代fork 创建子进程 | 解决4GB内存RK3576开发板执行shell时OOM崩溃问题,低内存端侧通用 |
| 命令去重 / 调用次数上限 / 失败重试 | 约束1.5B小模型逻辑缺陷,保证Debian端侧Agent流程收敛 |
| 思考内容隐藏 + 占位提示 | 优化 RK3576 设备人机交互,消除用户程序假死误判 |

这套截流式工具调用+中断续跑 方案通用性极强:无需模型原生工具微调、不依赖高端SDK特性,完全依靠应用层自定义协议与状态机实现。其中posix_spawn规避fork内存溢出、输出长度限流防日志雪崩等设计,对所有RK系列、内存受限Linux/Debian端侧LLM工程落地均具备参考价值。

结语

RK3576端侧硬件资源有限,大模型工具调用落地处处受限,但本套落地方案证明:即便仅使用1.5B蒸馏推理模型、老旧同步RKLLM SDK、4GB内存嵌入式开发板,依靠分层协议、内存优化、状态机容错的工程设计,依然可以搭建一套能够自主调用Debian 系统shell、操控本地硬件的完整 Agent。端侧大模型发展不只追求更大参数量,更要依靠精细化工程方案,让模型真正能够读懂、使用身边嵌入式设备。

触觉智能Purple Pi OH2开发板,基于触觉智能RK3576核心板SOM7609系列+底板架构,搭载4核A72+4 核A53+M0多核异构处理器,主频2.2GHz

相关推荐
武子康9 小时前
小智断网后还能做什么?沿一次唤醒看清设备与服务端的分工
人工智能·llm·agent
星栖与芯10 小时前
LiteOS-M 切换汇编逐行图解(4):中断三件套与 HalTaskSchedule 触发
汇编·stm32·单片机·嵌入式硬件·harmonyos
米小虾12 小时前
RSI 走到哪一步了:拆开递归自我改进的三个可写面、五条定律,和那个没人做的对照实验
人工智能·agent
编程的一拳超人13 小时前
大模型研发重难点全栈手册:第 0 章 导览:从预训练、混合精度、3D 并行、吞吐优化,到对齐微调、推理部署、RAG/Agent 与评测(最全完整版)
agent·预训练·rag·混合精度·吞吐优化·对齐微调
JaydenAI15 小时前
[DeepSeek Harness插件内核-15]再谈Cordis的事件总线
ai·agent·plugin·deepseek·harness·cordis
hyunbar15 小时前
LangChain 实战:中间件Summarization详解
langchain·agent·harness
猿小猴子16 小时前
主流 Agent 之「OpenClaw」与「Hermes-Agent」介绍
ai·agent·openclaw·hermes·hermes-agent
sarasuki16 小时前
如何让LLM 能在半夜偷偷打开网易云呢?
人工智能·设计模式·agent
G***技16 小时前
告别物联网碎片化:杰和LH707+LM2-100-V0联合方案给出标准答案
人工智能·嵌入式硬件