> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 使用 MCP 工具调用在 Dragonwing IQ9075 上构建发货代理

> 在 Qualcomm Dragonwing IQ9075 EVK 上构建一个 LLM 应用程序，用于检查本地状态、选择动作、执行工具并对结果作出反应。

<div
  style={{
width: "100%",
borderRadius: "14px",
overflow: "hidden",
backgroundImage: "url('https://mintlify.s3.us-west-1.amazonaws.com/qualcomm-prod/images/tutorials/my-post-hero.png')",
backgroundSize: "cover",
backgroundPosition: "center",
height: "260px",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
marginBottom: "1.5rem"
}}
>
  <div
    style={{
position: "absolute",
bottom: "16px",
left: "50%",
transform: "translateX(-50%)",
background: "rgba(255,255,255,0.15)",
border: "1px solid rgba(255,255,255,0.4)",
color: "#fff",
fontSize: "0.75rem",
fontWeight: 700,
letterSpacing: "1px",
padding: "5px 14px",
borderRadius: "20px",
textTransform: "uppercase",
whiteSpace: "nowrap",
zIndex: 1
}}
  >
    Dragonwing · Qualcomm
  </div>
</div>

<div style={{ marginBottom: "2rem" }}>
  <div
    style={{
fontSize: "0.72rem",
fontWeight: 700,
color: "#31017D",
letterSpacing: "1.5px",
textTransform: "uppercase",
marginBottom: "0.5rem"
}}
  >
    类别
  </div>

  <p style={{ fontSize: "0.95rem", color: "#555", lineHeight: 1.7, margin: "0 0 0.75rem" }}>
    本文的简短介绍。请与教程首页的卡片摘要保持一致。
  </p>

  <div style={{ fontSize: "0.85rem", color: "#888", display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}>
    <span>作者或团队</span>
    <span>·</span>
    <span>YYYY 年 MM 月 DD 日</span>
    <span>·</span>
    <a href="/zh/tutorial" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>← 全部文章</a>
  </div>
</div>

<hr style={{ border: "none", borderTop: "1px solid #eee", margin: "0 0 2rem" }} />

## 概述

**作者：** [Eivind Holt](https://www.linkedin.com/in/eivholt/)，2026 年 7 月
**代码仓库：** [github.com/eivholt/qai-nemotron](https://github.com/eivholt/qai-nemotron)
**目标平台：** [Qualcomm Dragonwing IQ-9075 EVK / QCS9075 / Hexagon v73](https://www.qualcomm.com/developer/hardware/qualcomm-iq-9075-evaluation-kit-evk)。硬件由 Qualcomm 慷慨赞助 🙏\
**模型：** [mistralai/Ministral-3-3B-Instruct-2512](https://huggingface.co/mistralai/Ministral-3-3B-Instruct-2512) Q4\_K\_M GGUF，在设备端 NPU 上运行

能够在边缘设备上运行的大型语言模型已经越过了一个界限：它们可以用工具推理来取代传统的硬编码控制流逻辑。它们可以检查本地状态、选择动作、执行工具，并对结果作出反应。
本教程将该循环构建为一个运行在 Qualcomm Dragonwing IQ9075 EVK 上的小型控制台应用程序。

示例是一个出库运输协调器。它读取一个待处理的运单，检查承运人和装货码头，并从以下三种结果中选择一种：

* 安排一个合规的承运人和码头；
* 当临时问题可能得到解决时，暂缓运输；或
* 当不存在安全方案时，上报升级处理。

<img src="https://mintcdn.com/qualcomm-prod/3Z1uoVMzu8S0bsSS/images/tutorials/shipping_agent_banner.png?fit=max&auto=format&n=3Z1uoVMzu8S0bsSS&q=85&s=a1525092423b93ebead18c0d714e990b" alt="Qualcomm Dragonwing IQ-9075 EVK" width="1536" height="1024" data-path="images/tutorials/shipping_agent_banner.png" />

所有的操作工具都是确定性的 Python 模拟。它们代表仓库管理系统、承运人 API、路线服务和调度渠道。所有六个工具在模型第一轮就全部可见。模型独立选择要检查哪些内容、要记录哪种处置方式、要使用哪些资源，以及何时通知调度。工具代码在改变状态之前会验证硬性运营约束。

## 相关教程

[在 Dragonwing IQ9075 上部署 NVIDIA Llama-3.1-Nemotron-Nano-8B-v1](https://dragonwingdocs.qualcomm.com/tutorials/deploy-nemotron-nano-on-dragonwing-iq9075) 更详细地介绍了 EVK、WSL2 存储、Qualcomm AI Hub 凭证、QAIRT 安装以及 Genie 基础知识。

[Dragonwing IQ9075 上的代理型 LLM](https://github.com/eivholt/qai-nemotron/blob/main/tutorial_agentic_models_iq9075.md) 对比了多种模型的函数调用和物流行为。这个基准测试工作促成了本文使用的模型和客户端设计。

## 示例中包含哪些内容

实现代码位于代码仓库的以下路径：

| 路径 | 用途 |
| - | - |
| `shipping_agent/app.py` | Pydantic AI 客户端、模型指令、工具和控制台运行器 |
| `shipping_agent/runtime.py` | 模拟仓库状态、硬性验证和严格评分 |
| `shipping_agent/mcp_server.py` | 真实的 MCP stdio 服务器，暴露相同的六个工具 |
| `shipping_agent/test_runtime.py` | 状态转换、评分和传输的单元测试 |
| `shipping_agent/test_genie_cpp_adapter.py` | 针对 C++ 适配器的协议和捆绑布局测试 |
| `shipping_agent/prepare_bundle.py` | 创建确定性的 Genie 和 C++ 服务配置 |
| `shipping_agent/genie_cpp_adapter.py` | 将常驻 C++ Genie 响应适配为 OpenAI 工具调用 |
| `shipping_agent/run_ministral_cpp_server.sh` | 启动常驻 C++ 实时演示后端 |
| `shipping_agent/run_ministral_server.sh` | 启动可检查的 `genie-t2t-run` 回归后端 |
| `shipping_agent/qai_appbuilder_86ce07a_shipping.patch` | 针对固定 Qualcomm 服务的 Linux 编译修复和回环绑定 |
| `shipping_agent/requirements.txt` | 固定的 Python 客户端依赖 |
| `scripts/export_ministral3_3b_iq9075_gguf.py` | 构建 IQ9075 HTP Genie 包 |

运行时包含四个场景：

| 场景 | 决策 |
| - | - |
| 常规托盘 | 使用容量充足的卡车和可用的标准码头 |
| 冷藏药品 | 使用冷藏车和冷藏码头来维持冷链处理 |
| 天气封路 | 识别可恢复的路线封锁并暂缓 |
| 危险品 | 识别不存在合规承运人并上报升级 |

## 为什么选择 Ministral 3B

示例使用 [Mistral Ministral-3-3B-Instruct-2512](https://huggingface.co/mistralai/Ministral-3-3B-Instruct-2512) 及其官方 Q4\_K\_M GGUF 权重。在之前的 IQ9075 对比测试中，这个模型是在有界工具使用方面最稳定的加速器后端选择。它在共享的非 Web BFCL 选择上得分 132/170，在严格的自定义医院物流测试中通过 9/14 个案例。

IQ9075 Q4 包在保存的代理配置文件中生成约 5 tokens/s。这比某些 QAI Hub W4A16 模型慢，但该模型在多轮工具使用中更可靠。因此模型选择应考虑完成的工作量，而不仅是 tokens/s。

Mistral 官方的使用说明要求在源模型使用 vLLM 托管时使用原生 Mistral 工具调用解析器。Genie 本身不提供该 OpenAI 适配器。代码仓库中的 `mistral_tool` 渲染器和解析器在常驻 C++ 服务和 `genie-t2t-run` 两侧都执行相同的转换。

## 各部分如何协作

运行路径故意保持精简：

```mermaid theme={null}
flowchart TD
    A["控制台任务"] --> B["Pydantic AI 代理"]
    B -->|"OpenAI 兼容的聊天请求"| C["模型特定的 Python 适配器<br/>127.0.0.1:8001"]
    C -->|"原生 Mistral 提示"| D["常驻 Qualcomm GenieAPIService<br/>127.0.0.1:8911"]
    D -->|"QNN 上下文"| E["IQ9075 HTP/NPU"]
    E -->|"生成的 token"| D
    D -->|"模型文本"| C
    C -->|"解析后的 OpenAI 工具调用"| B
    B -->|"模型工具选择"| F{"工具传输"}
    F -->|"直接"| G["已注册的 Pydantic 函数"]
    F -->|"MCP"| H["MCP stdio 调用"]
    G --> I["模拟发货运行时"]
    H --> I
    I -->|"观察结果或验证错误"| B
```

两个 HTTP 层都只监听 `127.0.0.1`。Python 适配器使用原生 Mistral 模板完整渲染一次请求，将该文本通过 Qualcomm C++ `GenieAPIService` 中的身份提示发送，解析返回的模型文本，并将其作为普通的 OpenAI 兼容响应交给 Pydantic AI。Pydantic AI 然后直接执行所选的工具，或者通过 MCP 发现并调用它，然后将结果包含在下一次模型回合中。

C++ 进程一次性加载模型和 QNN 上下文。它为每个 HTTP 请求重置 Genie 对话，因为 Python 适配器已经提供了完整对话，但该重置不会重新加载模型。将渲染保留在一个地方，也避免在模型的原生 Mistral 提示之上再套用一个通用服务模板。

代码仓库还保留了围绕 `genie-t2t-run` 构建的第二个高度可检查的桥接。它为每个模型回合启动一个新的 CLI 进程，并记录提示、原始响应、解析器决策和 Genie 配置文件。这使它成为模型集成测试和解析器调试的有用工具，而常驻 C++ 路径是更好的实时演示后端。

## 资源预期

已验证的导出使用 QAIRT 2.47，在桌面 PC 上耗时约 25 分钟。

| 项目 | 观测到的大小或时间 |
| - | -: |
| 官方 Q4\_K\_M GGUF | 约 2.15 GB |
| QAIRT 构建缓存 | 约 30 GB |
| 保存的 QAIRT 容器 | 约 3.3 GB |
| 最终 Genie 导出 | 约 3.3 GB |
| 并列的 EVK QAIRT 2.47 运行时 | 约 0.54 GB |
| 导出时间 | 约 25 分钟 |

请为完整的构建预留至少 45 到 50 GB 的快速桌面存储。成功的桌面机器有 192 GB 内存，但未捕捉到导出的峰值 RAM，因此这项工作并未确立更小的下限。生成的包本身可以轻松容纳在 EVK 的系统内存中。

## 准备桌面导出环境

创建一个隔离的环境：

```bash theme={null}
source "$HOME/miniconda3/etc/profile.d/conda.sh"

conda create -y -n qairt-dev-gguf \
  python=3.12 \
  cmake make clang clangxx llvmdev \
  libcxx libcxxabi libunwind flatbuffers

conda activate qairt-dev-gguf
python -m pip install "qairt-dev==0.8.1" "huggingface_hub"
```

让 QAIRT 版本管理器安装其预期的依赖并获取 QAIRT 2.47：

```bash theme={null}
qairt-vm -y -f
mkdir -p "$HOME/qairt_sdks/qairt"
qairt-vm fetch -v 2.47.0 -d "$HOME/qairt_sdks/qairt"

export QAIRT_SDK_ROOT="$HOME/qairt_sdks/qairt/2.47.0.260601"
export PATH="$QAIRT_SDK_ROOT/bin/x86_64-linux-clang:$PATH"
export LD_LIBRARY_PATH="$QAIRT_SDK_ROOT/lib/x86_64-linux-clang:${LD_LIBRARY_PATH:-}"
qairt-vm -i
```

最终的构建后缀可能在后续的 2.47 包中有所不同。如果不是 `2.47.0.260601`，请使用 `qairt-vm fetch` 打印的目录。

在此环境中将 `qairt-dev` 包保持为 Python 导入的主导。不要 source 完整 SDK 的 `envsetup.sh`；它可能会前置第二个 `qairt` Python 树，从而遮蔽构建器包。上面两个路径导出提供了构建器所需的桌面二进制文件和共享库。

## 下载官方 GGUF

发布方在官方 Mistral 仓库中提供了 [`Ministral-3-3B-Instruct-2512-Q4_K_M.gguf`](https://huggingface.co/mistralai/Ministral-3-3B-Instruct-2512-GGUF/blob/main/Ministral-3-3B-Instruct-2512-Q4_K_M.gguf)。

```bash theme={null}
mkdir -p "$HOME/models/ministral3_3b"

hf download \
  mistralai/Ministral-3-3B-Instruct-2512-GGUF \
  Ministral-3-3B-Instruct-2512-Q4_K_M.gguf \
  --local-dir "$HOME/models/ministral3_3b"
```

Q4\_K\_M 文件已经过权重量化。QAIRT 仍需要摄入 GGUF、构建 IQ9075 图、编译 HTP 上下文，并为 Genie 打包结果。因此这不仅仅是将 GGUF 复制到设备。

## 为 IQ9075 编译并导出

在激活的 `qairt-dev-gguf` 环境中运行仓库辅助脚本：

```bash theme={null}
cd "$HOME/repos-native/qai-nemotron"
mkdir -p logs

/usr/bin/time -v \
  python scripts/export_ministral3_3b_iq9075_gguf.py \
    --gguf "$HOME/models/ministral3_3b/Ministral-3-3B-Instruct-2512-Q4_K_M.gguf" \
    --build-root "$HOME/qairt_build/ministral3_3b_q4" \
  2>&1 | tee logs/ministral3_3b_q4_export.log
```

辅助脚本选择 IQ9075 HTP 目标 `dsp_arch:v73;soc_model:77;cores:1` 并导出两个 QNN 上下文二进制文件。成功的输出以 `EXPORT_SUMMARY` JSON 行结尾。

可部署的包为：

```text theme={null}
~/qairt_build/ministral3_3b_q4/genie/
|-- genie_config.json
`-- artifacts/
    |-- tokenizer.json
    |-- split_model_1.bin
    |-- split_model_2.bin
    `-- ...
```

不要将由 QAIRT 2.47 编译的包与开发板默认的 QAIRT 2.45 运行时混用。这种组合会在创建上下文零时失败，报 QNN 错误 5000。

## 在 EVK 默认版旁并列安装 QAIRT 2.47

将 2.47 与 `/opt/qairt/current` 并列可避免修改开发板上现有的每个模型。设置当前 EVK 地址，然后创建目标目录：

```bash theme={null}
EVK=ubuntu@<EVK_IP>
QAIRT_DEVICE_ROOT=/home/ubuntu/qairt-2.47.0.260601

ssh "$EVK" "mkdir -p \
  $QAIRT_DEVICE_ROOT/bin/aarch64-oe-linux-gcc11.2 \
  $QAIRT_DEVICE_ROOT/lib/aarch64-oe-linux-gcc11.2 \
  $QAIRT_DEVICE_ROOT/lib/hexagon-v73/unsigned"
```

复制目标运行时二进制文件、目标库以及 Hexagon 库：

```bash theme={null}
rsync -ah --info=progress2 \
  "$QAIRT_SDK_ROOT/bin/aarch64-oe-linux-gcc11.2/" \
  "$EVK:$QAIRT_DEVICE_ROOT/bin/aarch64-oe-linux-gcc11.2/"

rsync -ah --info=progress2 \
  "$QAIRT_SDK_ROOT/lib/aarch64-oe-linux-gcc11.2/" \
  "$EVK:$QAIRT_DEVICE_ROOT/lib/aarch64-oe-linux-gcc11.2/"

rsync -ah --info=progress2 \
  "$QAIRT_SDK_ROOT/lib/hexagon-v73/unsigned/" \
  "$EVK:$QAIRT_DEVICE_ROOT/lib/hexagon-v73/unsigned/"
```

启动脚本仅为此模型进程将这些位置前置到 `PATH`、`LD_LIBRARY_PATH` 和 `ADSP_LIBRARY_PATH`。

## 复制模型和示例

在桌面机的仓库根目录下：

```bash theme={null}
rsync -ah --info=progress2 \
  "$HOME/qairt_build/ministral3_3b_q4/genie/" \
  "$EVK:~/ministral_q4_genie_export/"

ssh "$EVK" "mkdir -p ~/qai-nemotron"

rsync -ah \
  agent_arena shipping_agent \
  "$EVK:~/qai-nemotron/"
```

桥接程序还期望第一篇教程中基础 EVK 设置的 `~/qairt-env.sh`。它在启动器添加 2.47 路径之前提供正常的设备环境。

## 在 EVK 上准备代理环境

打开 SSH shell：

```bash theme={null}
ssh "$EVK"
```

安装 Python 虚拟环境支持并创建客户端环境：

```bash theme={null}
sudo apt-get update
sudo apt-get install -y python3-venv

python3 -m venv "$HOME/shipping-agent-venv"
source "$HOME/shipping-agent-venv/bin/activate"

cd "$HOME/qai-nemotron"
python -m pip install -r shipping_agent/requirements.txt
```

创建确定性的 Genie 配置：

```bash theme={null}
python3 -m shipping_agent.prepare_bundle "$HOME/ministral_q4_genie_export"
```

这将写入 `genie_config.agent.json`，seed 为 42，temperature 为 0，`top-k=1`，`top-p=1.0`。它还写入 C++ 服务的身份 `prompt.json`，并在 `artifacts/` 下创建到 tokenizer、后端扩展和 QNN 上下文二进制文件的根级相对链接。这些链接消耗的存储微不足道，并适配示例服务的按 basename 查找。原始导出的配置和产物不会被修改。

## 构建常驻 C++ 服务

实时演示使用 Qualcomm 的开源 [`GenieAPIService`](https://github.com/qualcomm/qai-appbuilder/tree/main/samples/genie/c%2B%2B/Service)。已验证的构建将 `qai-appbuilder` 固定在提交 `86ce07a`，以便源代码和命令保持可重现：

```bash theme={null}
sudo apt-get install -y build-essential cmake git

mkdir -p "$HOME/src"
git clone --recurse-submodules https://github.com/qualcomm/qai-appbuilder.git "$HOME/src/qai-appbuilder-full"

cd "$HOME/src/qai-appbuilder-full"
git checkout 86ce07addc4404a026a5fdb17787ca804a8221d4
git submodule update --init --recursive
git apply "$HOME/qai-nemotron/shipping_agent/qai_appbuilder_86ce07a_shipping.patch"
```

递归子模块是必需的；否则 CMake 找不到诸如 `CLI/CLI.hpp` 之类的头文件时会停止。这个小型仓库补丁移除了一个被 EVK 上 GCC 13 编译器拒绝的无效额外 `std::` 限定，并将服务的硬编码绑定地址从 `0.0.0.0` 改为 `127.0.0.1`。它不会添加模型模板或工具解析器。

在 EVK 上配置和构建：

```bash theme={null}
export QNN_SDK_ROOT=/opt/qairt/current

SERVICE_SRC="$HOME/src/qai-appbuilder-full/samples/genie/c++/Service"
cmake -S "$SERVICE_SRC" -B "$SERVICE_SRC/build_linux" -DCMAKE_BUILD_TYPE=Release

cmake --build "$SERVICE_SRC/build_linux" -j2
```

在两个命令期间都保持 `QNN_SDK_ROOT` 导出。嵌套的 AppBuilder 构建从环境读取它，同时针对 EVK 上已安装的 QNN 开发头文件进行编译。启动时，脚本会选择该模型包所需的并列 QAIRT 2.47 库。

只应用这一个小源补丁的干净检出在 EVK 上以 2 分 20.6 秒完成了所有四个严格直接工具场景。这证实了实时演示不依赖于模型比较工作中其他地方使用的早期实验性 C++ 模型模板或工具解析器补丁。

## 启动本地模型服务器

使用第一个 EVK shell 启动常驻实时演示后端：

```bash theme={null}
cd "$HOME/qai-nemotron"
./shipping_agent/run_ministral_cpp_server.sh
```

期望的启动输出为：

```text theme={null}
Shipping Genie adapter: http://127.0.0.1:8001/v1 -> http://127.0.0.1:8911/v1
```

如果 QAIRT 目录有不同的后缀，请显式设置：

```bash theme={null}
QAIRT_ROOT="$HOME/qairt-2.47.0.YOUR_BUILD" ./shipping_agent/run_ministral_cpp_server.sh
```

启动器在端口 8911 上启动 `GenieAPIService`，加载一次模型，等待就绪，然后在端口 8001 上启动模型特定的 Python 适配器。适配器使用原生 Mistral 工具模板并暴露每个解析出的调用。当模型发出额外调用时，它不会静默地只保留第一个调用。

对于可检查的模型集成和解析器回归测试，先停止 C++ 服务器，然后启动 CLI 桥接：

```bash theme={null}
./shipping_agent/run_ministral_server.sh
```

该桥接具有相同的公共端点和解析器，但为每个模型请求单独启动 `genie-t2t-run`。不要同时运行两个后端，因为它们共享端口 8001 和 HTP 资源。

## 在没有模型的情况下测试 Python 工具

打开第二个 EVK shell：

```bash theme={null}
ssh "$EVK"
source "$HOME/shipping-agent-venv/bin/activate"
cd "$HOME/qai-nemotron"
```

运行单元和适配器测试：

```bash theme={null}
python -m unittest -v shipping_agent.test_runtime shipping_agent.test_genie_cpp_adapter
```

然后直接演练每个模拟：

```bash theme={null}
python -m shipping_agent.app --scenario all --scripted
```

脚本模式证明模拟世界和预期结果是自洽的。它不衡量模型智能。

## 运行代理

默认使用直接 Pydantic 函数工具。从常规案例开始：

```bash theme={null}
python -m shipping_agent.app --scenario routine
```

列出或选择其他案例：

```bash theme={null}
python -m shipping_agent.app --list-scenarios
python -m shipping_agent.app --scenario cold_chain
python -m shipping_agent.app --scenario weather_hold
python -m shipping_agent.app --scenario no_compliant_carrier
```

依次运行全部四个，或重复运行以衡量可靠性：

```bash theme={null}
python -m shipping_agent.app --scenario all --transport direct
python -m shipping_agent.app --scenario all --transport direct --repeat 2
```

### 天气封路示例

以下记录来自常驻 C++ 运行器。仅重新排列了空白和终端标签以便于阅读 JSON。完整的系统指令展示在下一节。

用户输入：

```text theme={null}
Choose a safe disposition for the pending shipment and notify dispatch.
```

模型选择并执行了以下四个工具回合：

```json theme={null}
[
  {
    "tool_call": {
      "name": "get_pending_shipment",
      "arguments": {}
    },
    "tool_result": {
      "ok": true,
      "shipment": {
        "deadline_minutes": 180,
        "destination": "Mountain depot",
        "handling": "standard",
        "pallets": 5,
        "shipment_id": "SHP-3001",
        "status": "pending"
      }
    }
  },
  {
    "tool_call": {
      "name": "get_shipping_options",
      "arguments": {}
    },
    "tool_result": {
      "excluded_wcarriers": [
        {
          "assessment": "incompatible",
          "capacity_pallets": 2,
          "carrier_id": "VAN-3",
          "handling": ["standard"],
          "issues": ["insufficient_capacity"],
          "route_open": true,
          "travel_minutes": 35
        }
      ],
      "excluded_docks": [],
      "ok": true,
      "shipment_id": "SHP-3001",
      "temporarily_blocked_carriers": [
        {
          "assessment": "temporarily_blocked",
          "capacity_pallets": 10,
          "carrier_id": "TRUCK-9",
          "handling": ["standard"],
          "issues": ["route_temporarily_closed"],
          "route_open": false,
          "route_reopens_minutes": 40,
          "travel_minutes": 55
        }
      ],
      "usable_carriers": [],
      "usable_docks": [
        {
          "assessment": "usable",
          "available": true,
          "dock_id": "D1",
          "handling": ["standard"],
          "issues": []
        }
      ]
    }
  },
  {
    "tool_call": {
      "name": "hold_shipment",
      "arguments": {
        "reason": "Temporary route closure expected to resolve within 40 minutes"
      }
    },
    "tool_result": {
      "dispatch_notified": false,
      "ok": true,
      "reason": "Temporary route closure expected to resolve within 40 minutes",
      "shipment_id": "SHP-3001",
      "status": "held"
    }
  },
  {
    "tool_call": {
      "name": "notify_dispatch",
      "arguments": {}
    },
    "tool_result": {
      "dispatch_notified": true,
      "message": "Shipment SHP-3001 is held.",
      "notified": true,
      "ok": true,
      "shipment_id": "SHP-3001"
    }
  }
]
```

模型随后返回：

```text theme={null}
**Summary:**
- Shipment **SHP-3001** was **held** due to a temporary route closure expected
  to resolve within 40 minutes.
- Dispatch has been notified.
```

确定性评估器记录了最终状态：

```json theme={null}
{
  "actual": {
    "action": "held"
  },
  "call_names": [
    "get_pending_shipment",
    "get_shipping_options",
    "hold_shipment",
    "notify_dispatch"
  ],
  "dispatch_notified": true,
  "expected": {
    "action": "held"
  },
  "expected_calls": [
    "get_pending_shipment",
    "get_shipping_options",
    "hold_shipment",
    "notify_dispatch"
  ],
  "passed": true,
  "state_correct": true,
  "tool_calls": 4,
  "tool_errors": [],
  "trace_correct": true
}
```

单个案例的套件汇总为：

```json theme={null}
{
  "passed": 1,
  "results": [
    {
      "passed": true,
      "run": 1,
      "scenario": "weather_hold",
      "transport": "direct"
    }
  ],
  "total": 1
}
```

终端还会打印诊断位置：

```text theme={null}
Raw prompt/response records: /home/ubuntu/shipping_agent_results/cpp_adapter/
C++ server log: /tmp/iq9075_cpp_case_demo_server.log
```

没有客户端阶段选择这些工具。模型读取了运单和选项，从三个处置动作中选择了 hold\_shipment，等待其结果，然后在下一次模型回合中通知调度。

### 危险品示例

危险品案例演练了另一种非调度结果。它使用相同的用户输入：

```text theme={null}
Choose a safe disposition for the pending shipment and notify dispatch.
```

模型收到一个合规的危险品码头，但没有能够处理该运单的承运人：

```json theme={null}
[
  {
    "tool_call": {
      "name": "get_pending_shipment",
      "arguments": {}
    },
    "tool_result": {
      "ok": true,
      "shipment": {
        "deadline_minutes": 120,
        "destination": "Industrial customer",
        "handling": "hazmat",
        "pallets": 2,
        "shipment_id": "SHP-4001",
        "status": "pending"
      }
    }
  },
  {
    "tool_call": {
      "name": "get_shipping_options",
      "arguments": {}
    },
    "tool_result": {
      "excluded_carriers": [
        {
          "assessment": "incompatible",
          "capacity_pallets": 8,
          "carrier_id": "TRUCK-7",
          "handling": ["standard"],
          "issues": ["handling_not_supported"],
          "route_open": true,
          "travel_minutes": 45
        },
        {
          "assessment": "incompatible",
          "capacity_pallets": 6,
          "carrier_id": "REEFER-2",
          "handling": ["standard", "cold_chain"],
          "issues": ["handling_not_supported"],
          "route_open": true,
          "travel_minutes": 50
        }
      ],
      "excluded_docks": [],
      "ok": true,
      "shipment_id": "SHP-4001",
      "temporarily_blocked_carriers": [],
      "usable_carriers": [],
      "usable_docks": [
        {
          "assessment": "usable",
          "available": true,
          "dock_id": "HAZ-1",
          "handling": ["hazmat"],
          "issues": []
        }
      ]
    }
  },
  {
    "tool_call": {
      "name": "escalate_shipment",
      "arguments": {
        "reason": "No carriers support hazmat handling"
      }
    },
    "tool_result": {
      "dispatch_notified": false,
      "ok": true,
      "reason": "No carriers support hazmat handling",
      "shipment_id": "SHP-4001",
      "status": "escalated"
    }
  },
  {
    "tool_call": {
      "name": "notify_dispatch",
      "arguments": {}
    },
    "tool_result": {
      "dispatch_notified": true,
      "message": "Shipment SHP-4001 is escalated.",
      "notified": true,
      "ok": true,
      "shipment_id": "SHP-4001"
    }
  }
]
```

模型的最终回答是：

```text theme={null}
Completed:
- Escalated **SHP-4001** (hazmat handling unavailable) and notified dispatch.
```

严格评估器接受了准确的执行轨迹：

```json theme={null}
{
  "actual": {
    "action": "escalated"
  },
  "call_names": [
    "get_pending_shipment",
    "get_shipping_options",
    "escalate_shipment",
    "notify_dispatch"
  ],
  "dispatch_notified": true,
  "expected": {
    "action": "escalated"
  },
  "expected_calls": [
    "get_pending_shipment",
    "get_shipping_options",
    "escalate_shipment",
    "notify_dispatch"
  ],
  "passed": true,
  "state_correct": true,
  "tool_calls": 4,
  "tool_errors": [],
  "trace_correct": true
}
```

与天气案例不同的是，这里没有暂时受阻的兼容承运人可以等待。因此模型选择升级而不是暂缓运输。

## 自主代理循环

Pydantic AI 将普通的 Python 函数注册为模型工具。模型永远不会直接执行 Python 或 shell 代码。所有六个工具在每一轮都保持可用：

1. `get_pending_shipment`
2. `get_shipping_options`
3. `schedule_shipment`
4. `hold_shipment`
5. `escalate_shipment`
6. `notify_dispatch`

完整的系统指令有意保持简短：

```text theme={null}
You are an autonomous shipping coordinator.

Complete the user's task by choosing and calling the available tools. Treat tool results as the only source of operational truth. Work one step at a time: in each response, call exactly one listed tool, then stop and wait for its result before deciding the next action. Never invent tool names, identifiers, or facts, and do not repeat successful work. When the shipment has a safe recorded disposition and dispatch has been notified, finish with a concise summary of what you completed.
```

一个直接注册的动作看起来像普通的 Pydantic AI 代码：

```python theme={null}
@agent.tool(sequential=True)
def schedule_shipment(ctx, carrier_id: str, dock_id: str):
    """Schedule IDs from usable_carriers and usable_docks; never use other lists."""
    runtime = ctx.deps.runtime
    shipment_id = runtime.shipment["shipment_id"]
    return runtime.call(
        "schedule_shipment",
        {"carrier_id": carrier_id, "dock_id": dock_id},
        lambda: runtime.schedule_shipment(shipment_id, carrier_id, dock_id),
    )
```

`sequential=True` 可防止并发的状态更改调用。它不隐藏工具，也不规定下一个动作。模型在对话中接收每个工具结果，并选择下一步。

## 返回决策就绪的观察结果

第一个自主版本返回一个混合的承运人列表。Ministral 通常能完成任务，但严格的重复运行暴露了两个注意力失误：在天气案例中它尝试了明显被拒绝的选项，而有一次即使存在可用卡车，它也在关注一辆容量不足的厢式车后暂缓了一个常规运单。

添加更多针对特定案例的提示文本会让测试更容易通过，但也不那么通用。相反，模拟选项服务被改进得更像一个真实的物流服务。它执行确定性的兼容性检查，并将记录分组为：

```json theme={null}
{
  "usable_carriers": [],
  "temporarily_blocked_carriers": [
    {"carrier_id": "TRUCK-9", "issues": ["route_temporarily_closed"]}
  ],
  "excluded_carriers": [
    {"carrier_id": "VAN-3", "issues": ["insufficient_capacity"]}
  ],
  "usable_docks": [{"dock_id": "D1"}],
  "excluded_docks": []
}
```

该服务不选择 `schedule`、`hold` 或 `escalate`。它提供决策就绪的事实，将协调策略留给模型。这是一个重要的边缘代理模式：确定性系统应该计算它们可以精确计算的事实，而 LLM 负责处理变化的工作流和意图。

工具在一个当前运单上操作，因此下游调用不要求模型重复其运单 ID。移除冗余的标识符转写修复了一个真实的 MCP 验证失败，同时没有降低决策自主性。

## 通过 MCP 运行相同的工具

同一个控制台客户端可以通过 [Model Context Protocol](https://modelcontextprotocol.io/) 发现并执行全部六个工具：

```bash theme={null}
python -m shipping_agent.app --scenario routine --transport mcp
python -m shipping_agent.app --scenario all --transport mcp --repeat 2
```

`shipping_agent/mcp_server.py` 会作为独立的 stdio 进程为每个场景自动启动。它拥有模拟状态，并为严格评估器持久化每次执行的调用。没有 MCP 工具裁剪。

有一个微妙的差异很重要。Pydantic 的直接注册发出简洁的 JSON schema，而原始 MCP 发现添加了诸如 `get_shipping_optionsArguments` 和 `Shipment Id` 等 schema 标题。一次早期的 MCP 运行将 `SHP-1001` 复制为 `SHP-1000`，最终恢复过来，但因为多出一个被拒绝的调用而正确地未通过严格评分。

客户端使用 Pydantic AI 的 `PreparedToolset` 包装发现的工具，并只规范化其面向模型的 JSON schema：

```python theme={null}
def normalize_mcp_tool_schemas(_ctx, tool_defs):
    def normalize(value):
        if isinstance(value, dict):
            result = {
                key: normalize(item)
                for key, item in value.items()
                if key != "title"
            }
            if result.get("type") == "object":
                result.setdefault("additionalProperties", False)
            return result
        if isinstance(value, list):
            return [normalize(item) for item in value]
        return value

    return [
        replace(tool, parameters_json_schema=normalize(tool.parameters_json_schema))
        for tool in tool_defs
    ]

toolset = PreparedToolset(discovered_toolset, normalize_mcp_tool_schemas)
```

发现和执行仍使用真实的 MCP stdio 传输。包装器只是让呈现给这个小模型的 schema 与已验证的直接工具形状相匹配。结果与之前的基准测试发现一致：MCP 对于一个 3B 模型来说本质上并非过于不可靠，但额外的 schema 噪声可能会让它偏离方向。

## 更安全的渐进披露选项

某些部署应仅暴露在当前工作流阶段有效的动作。Pydantic AI 可以动态准备工具列表：

```python theme={null}
def available_when(predicate):
    def prepare(ctx, tool_definition):
        return tool_definition if predicate(ctx.deps.runtime) else None
    return prepare

@agent.tool(
    prepare=available_when(
        lambda runtime:
            runtime.options_read and runtime.shipment["status"] == "pending"
    ),
    sequential=True,
)
def schedule_shipment(ctx, carrier_id: str, dock_id: str):
    ...
```

更严格的版本可以仅暴露初始的读取工具，然后是选项工具，然后是三个处置动作，最后是通知。这种设计对小型模型更友好，减少了无效顺序的机会。当安全性或事务成本比展示模型自主性更重要时，这是合理的生产选择。

本教程*不*使用该路径。运行的应用程序从第一轮就暴露所有六个工具，因此观察到的顺序确实是从提示、工具描述和返回的观察结果中真实选择出来的。

## 在代码中保留硬性验证

自主性并不要求运营系统接受不可能的动作。调度工具仍然强制执行普通的软件规则：

```python theme={null}
if carrier["capacity_pallets"] < shipment["pallets"]:
    return {"ok": False, "error": "insufficient_capacity"}

if shipment["handling"] not in carrier["handling"]:
    return {"ok": False, "error": "carrier_not_compliant"}

if not carrier["route_open"]:
    return {"ok": False, "error": "route_closed"}
```

区别在于策略与规则条件。Python 不决定是暂缓还是升级。两个工具都接受一个待处理的运单和一个非空的原因。
运行后评估器将安全但糟糕的处置标记为错误。Python 会继续拒绝未知资源、不兼容的调度、重复的状态更改，以及在处置存在之前的通知。

调度工具不接受任何模型参数。它读取当前运单的权威 ID 和状态并撰写通知。让小模型重复已知的状态会增加转写失误，而不会增加有用的自主性。

每个成功的处置结果都明确报告 `"dispatch_notified": false`；只有 `notify_dispatch` 才返回 `true`。这种真实的状态可防止模型将升级或调度当作已通知处理。这是一个 API 设计改进，而不是隐藏的下一步或场景特定的提示。

严格评分使用记录的动作台账，而不是 LLM 评委。运行会因错误的处置、错误的承运人或码头、被拒绝的动作、多余的动作、错误的顺序或缺少调度通知而失败。禁用 Pydantic 工具重试，因此不能在该台账之外静默修复格式错误的调用。

## 已验证的结果

最终应用程序在物理 IQ9075 HTP 上进行了验证，temperature 为 0，top-k 为 1，top-p 为 1，并对两种传输运行相同的四个场景。下表中的每一行都包含两次完整的重复，暴露所有模型发出的调用，使用零次自动工具重试，并要求准确的动作台账。

| 模型后端 | 工具传输 | 严格通过 | 执行的调用 | 端到端时间 |
| - | - | -: | -: | -: |
| 常驻 C++ 服务 | 直接 Pydantic 注册 | 8/8 | 32，全部成功 | 4m 37.5s |
| 常驻 C++ 服务 | 通过 stdio 进行的 MCP 发现 | 8/8 | 32，全部成功 | 4m 46.4s |
| 每请求一次 `genie-t2t-run` | 直接 Pydantic 注册 | 8/8 | 32，全部成功 | 6m 00.9s |
| 每请求一次 `genie-t2t-run` | 通过 stdio 进行的 MCP 发现 | 8/8 | 32，全部成功 | 6m 09.2s |

常驻服务将直接工具的总墙钟时间减少约 23%，MCP 减少约 22%。这是工作流时间而非原始解码吞吐量：每个案例需要四个生成工具的请求，加上第五个用于其最终总结的请求。C++ 进程在这些请求之间保持模型和 QNN 上下文加载状态；CLI 路径每次都初始化一个新的对话进程。

每个案例都遵循以下确切的四调用路径之一：

| 场景 | 观察到的确切路径 | 结果 |
| - | - | - |
| 常规托盘 | read, options, schedule, notify | 在 `D1` 使用 `TRUCK-7` |
| 冷藏药品 | read, options, schedule, notify | 在 `COLD-1` 使用 `REEFER-2` |
| 天气封路 | read, options, hold, notify | 因可恢复的封路暂缓 |
| 危险品 | read, options, escalate, notify | 上报给人工处理 |

早期的 CLI 设置只保留第一个解析出的调用。这看起来有帮助，直到一次原始的天气响应显示一次有效的 `hold_shipment` 调用后跟一个虚构的 `notify_shipment` 调用。静默丢弃第二个调用让运行看起来比模型的实际行为更好。最终的 `all` 策略暴露两者，上面的每个结果都在这个更严格的规则下重新运行。

四个通用变更使完全暴露的运行具有可重复性：

* 模型面向的空助手内容渲染为空字符串，而不是字面上的 `null`。
* 提示要求恰好一次工具调用，然后暂停等待其结果。
* 暂缓和升级的描述区分临时恢复与无兼容选项。
* 处置结果明确报告尚未通知调度。

未使用特定于场景的工具列表：相同的六个工具在每个案例中从第一轮起都保持可见。与模型推理相比，MCP 发现增加的时间很少。

## 在一次模型回合中试验多个工具

主套件有意要求每个模型回合调用一个工具。这是可靠的，但它留下了一个有用的问题未解答：这个模型能否识别独立的工作、一次请求多个工具，并且在采取依赖于它们结果的动作之前仍然等待？

[单独的多工具案例](https://github.com/eivholt/qai-nemotron/blob/main/shipping_agent/multitool_demo.py) 将组合的选项工具替换为三个只读观察：

1. `get_pending_shipment`
2. `get_carrier_options`
3. `get_dock_options`

它还暴露了 `schedule_shipment`、`hold_shipment`、`escalate_shipment` 和 `notify_dispatch`。只有在模型将独立的运单、承运人和码头结果组合后，冷链运单才能被调度。它的额外指令是关于依赖处理的，而不是关于答案：

```text theme={null}
Request independent read-only observations together in one response when it is safe to do so. Never batch a state-changing call with observations or other work whose result it depends on.
```

读取工具使用普通的 Pydantic 注册，而状态更改工具使用 `sequential=True`。请求还设置了 `parallel_tool_calls=True`。针对常驻 C++ 适配器运行该案例：

```bash theme={null}
cd ~/qai-nemotron
source ~/shipping-agent-venv/bin/activate

python -m shipping_agent.multitool_demo \
  --base-url http://127.0.0.1:8001/v1 \
  --model ministral3-3b-q4 \
  --repeat 3
```

来自 C++ 服务的第一个原始响应在一条助手消息中包含三个原生 Mistral 工具块。为方便阅读，此处添加了换行符：

```text theme={null}
[TOOL_CALLS]get_pending_shipment[ARGS]{}
[TOOL_CALLS]get_carrier_options[ARGS]{}
[TOOL_CALLS]get_dock_options[ARGS]{}
```

适配器保留了所有三个调用，Pydantic 在将结果返回给模型之前执行了独立的 Python 工具。完整的顺序为：

```json theme={null}
[
  [
    "get_pending_shipment",
    "get_carrier_options",
    "get_dock_options"
  ],
  [
    "schedule_shipment"
  ],
  [
    "notify_dispatch"
  ]
]
```

外层列表表示模型的回合。第一个回合执行三个独立的读取。然后模型看到所有三个结果，并在第二个回合中在 `COLD-1` 调度 `REEFER-2`。只有在该动作成功之后，它才在第三个回合中通知调度。第四个回合返回最终的文本总结，未调用任何工具。

评估器报告两个独立的结果。`task_passed` 需要准确的五个工具、正确的承运人和码头、成功的最终状态和调度通知。`batching_passed` 另外要求在一次助手响应中至少有两次独立的读取调用，并拒绝任何混合了依赖性状态更改调用的响应。这意味着完全顺序的运行可以完成运输任务，但仍然可能未通过批处理实验。

| 后端 | 重复次数 | 任务结果 | 多工具结果 | 不安全的写入批次 | 大约时间 |
| - | -: | -: | -: | -: | -: |
| IQ9075 HTP 上的常驻 C++ 服务 | 4 | 4/4 | 4/4 | 0/4 | 每个案例 36s |

所有四次运行都发出了相同的模型级工具批次，进行了准确的五次调用，并达到了正确的状态。为三个 Python 读取函数打印的执行顺序可能会有所不同，因为它们并发运行；模型的原始调用顺序仍然可以在 `TOOL BATCHES` 和适配器的原始请求记录中找到。

这是一次成功的有限演示，而不是证明每一个并行计划都是安全的。模型并没有预先发出整个工作流，也不应该：调度选择依赖于观察结果，而通知依赖于成功的调度。有用的多工具行为只并行化真正独立的调用，并将依赖动作留在后续回合。

## 生成并执行协调代码

代码生成是与工具调用不同形式的自主性。工具调用代理选择一个或多个声明的动作，接收结果，并决定后续模型回合的操作；代码生成代理写出一个受限程序，可以在沙箱内执行循环、计算和多个 API 调用。该程序可以被测试、批准、缓存和复用，而无需再次请求模型，但它也引入了关于恶意代码、验证和缓存选择的独立关注点。这种方法在 **TODO：发布后更新链接** [在 Dragonwing IQ9075 上构建代码生成制造代理](https://github.com/eivholt/qai-nemotron/blob/main/tutorial_codegen_manufacturing_iq9075.md) 中介绍。

## 前沿代理平台增加了什么

成功的多工具案例并不意味着这个小型模型能匹敌当前的前沿代理。它展示了一项有用但有限的技能：模型能够一起请求独立的信息，检查结果，然后选择下一步动作。前沿平台可以处理比这种逐步循环更复杂的工作流。

> 在下面的几节中，我们概述了前沿模型和 API 中代理能力的现状。虽然它们与可部署到边缘的模型之间的差距很大，但后者似乎正朝着前沿模型的能力汇聚。了解这些能力有助于为较小模型即将面对的情况做好准备，也能为聪明的代理型 AI 工程提供灵感。

截至 2026 年 7 月，[GPT-5.6 系列](https://openai.com/index/gpt-5-6/) 将更强的代理训练模型与 Responses API 中的编排功能结合起来。这一区别很重要：一些能力存在于模型中，一些存在于托管运行时中，最先进的工作流则依赖于两者。

### 延迟工具发现

传统的函数调用请求将每一个工具名称、描述和参数 schema 都放在提示中。大型 MCP 部署可能会花费数万个 token 来描述从未使用的工具。

OpenAI 的[工具搜索](https://developers.openai.com/api/docs/guides/tools-tool-search) 代之以给模型提供轻量级的命名空间或 MCP 服务器描述。标记为 `defer_loading: true` 的函数仅在模型搜索它们时才加载。所选定义随后被附加到对话中。这有时被称为延迟工具发现。它减少了提示大小，保留了更多的提示缓存，并让一个代理能够浏览一个大得多的工具目录。

这与仅仅对小型模型隐藏大部分工具不同。模型必须理解一个两阶段协议：搜索适当的能力、检查发现的 schema，然后正确调用它。目前只有 GPT-5.4 及后续的 OpenAI 模型支持该协议。因此，IQ9075 演示直接呈现一小组稳定的工具集。自定义客户端可以构建工具搜索层，但这些边缘模型尚未针对其协议进行训练或验证。

### 程序化工具调用

[程序化工具调用](https://developers.openai.com/api/docs/guides/tools-programmatic-tool-calling) 允许 GPT-5.6 生成 JavaScript，在一个隔离的 V8 运行时（谷歌的高性能 JavaScript 引擎）内协调工具。程序可以并发运行独立调用、使用循环和条件、将一个结果传递给另一个调用，并在返回给模型之前减少大型中间输出。它没有通用的文件系统、子进程、包安装或直接网络访问；它只能通过应用程序允许的工具访问外界。

当控制流可预测时，这可以替代许多模型往返。而运输实验仍会将每个工具结果返回给模型以做出新决定。在本地重现程序化工具调用需要沙箱运行时以及能够可靠地根据类型化工具输入和输出生成编排代码的模型。早期的边缘实验发现，直接的、描述良好的函数调用比再增加一个生成代码协议要可靠得多。

### 模型引导的多代理工作

GPT-5.6 的 `ultra` 模式默认协调四个代理。Responses API 还提供了一个[多代理测试版](https://developers.openai.com/api/docs/guides/responses-multi-agent)，其中根模型可以创建子代理、赋予它们有界任务、交换消息、等待结果，并综合它们的发现。独立的上下文允许多个代理并发调查代码库的独立部分、比较假设或进行研究，而不会挤占一个对话。

这与前面的多工具案例不同。该案例有一个模型上下文和三个并发执行的 Python 函数。真正的多代理执行需要多个活动的推理流和上下文历史。让多个 QNN 模型上下文保持驻留，或反复交换它们，会给 EVK 内存和 HTP 调度带来更大压力。将子代理串行化是可行的，但会失去主要的延迟收益，并且仍然让一个 3B 模型负责委派和综合。

### 长视野状态和更广泛的动作面

[GPT-5.6 Sol 模型](https://developers.openai.com/api/docs/models/gpt-5.6-sol) 拥有 105 万 token 的上下文窗口、最多 128,000 个输出 token、通过 `max` 可调的推理强度、结构化输出，以及一个包含 Web 和文件搜索、代码解释、托管 shell、补丁应用、计算机使用、技能、MCP 和工具搜索的 Responses API 工具面。这些工具共同让代理能够检索公开或私有信息、操作图形界面、执行沙箱代码、修改文件，并加载可复用的过程，而无需将每一项能力都定义为自定义函数。该平台还支持[对话状态](https://developers.openai.com/api/docs/guides/conversation-state)、[后台执行](https://developers.openai.com/api/docs/guides/background) 和[上下文压缩](https://developers.openai.com/api/docs/guides/compaction) 以支持长时间运行的工作。

本教程中的 Ministral 包被编译为 4,096 token 的上下文。增大该编译时的限制会消耗更多内存，并使导出和运行时的资源压力更大。更重要的是，仅仅增大 token 限制并不能教会模型在长工作流中保持连贯、从部分失败中恢复、管理审批或使用附加的托管协议。

### 为何差距仍然存在

缺失的能力不是单一的模型大小限制：

* **训练：** 前沿模型经过训练和评估，能够处理长工具工作流、延迟 schema、代码编排、计算机使用以及从失败中恢复。
* **模型容量：** 委派、协议选择、中间状态跟踪和最终综合在 3B 或 8B 模型中竞争有限的注意力。
* **运行时支持：** Genie 和本教程的适配器暴露聊天补全和函数调用，而不是 Responses API 的工具搜索、程序、子代理、压缩和托管工具项类型。
* **内存和计算：** 百万 token 上下文和并发的模型代理远远超出了这个导出的 EVK 设置的实际内存和延迟预算。
* **量化：** Q4 或 W4A16 部署使这些模型在设备上变得可行，但可能会进一步降低精确、低频率协议 token 和复杂结构化输出上的可靠性。

其中的一部分差距可以通过应用程序代码来缩小。本地注册表可以过滤工具，Python 可以并发运行调用，状态机可以压缩历史或委派有界子任务。这些是有用的工程技术，但在通过现实的重复测试之前，不应将它们呈现为原生的模型能力。

目前，实用的边缘设计就是这里所展示的：一个简洁的直接工具集、决策就绪的观察、严格的执行记录、只有在独立时才并行调用，以及在结果到达后才采取依赖动作。前沿功能为未来的边缘运行时和模型提供了路线图，而较小的本地代理今天就提供了隐私、离线运行、可预测成本和有用的自主性。

## 结论

一个实用的边缘代理无需接收任意代码或硬件访问就可以有意义地实现自主。这个示例将全部六个工作流工具交给一个 3B NPU 托管模型，让它独立选择和排序动作，并将硬性运营规则和条件保留在确定性服务中。

最终的直接和 MCP 运行显示，当模型收到其原生提示格式、简洁的工具 schema 和决策就绪的观察时，这种级别的本地自主性在 IQ9075 上已经可行。当部署需要更狭窄的安全范围时，仍然可以使用渐进披露。

## 故障排除

### QNN 上下文错误 5000

如果原始响应包含：

```text theme={null}
Using libGenie.so version 1.17.0
Could not create context from binary for context index = 0 : err 5000
```

那么由 2.47 编译的包正在使用 EVK 的 2.45 / Genie 1.17 库运行。
检查服务器进程环境：

```bash theme={null}
pid=$(pgrep -f "GenieAPIService.*--config_file" | head -n 1)
tr '\0' '\n' <"/proc/$pid/environ" |
  grep -E "QAIRT_ROOT|PATH|LD_LIBRARY_PATH|ADSP_LIBRARY_PATH"
```

在调试 CLI 参考后端时，请改选其 `openai_genie_server` 进程。

2.47 路径必须出现在 `/opt/qairt/current` 之前。

### 设备创建失败 14001

QNN 设备创建错误可能是过期的运行时或资源冲突，而非糟糕的答案。停止残留的 Genie 进程，验证没有其他测试在使用 HTP，并重试。如果设备仍然不可用，请重启 EVK。

```bash theme={null}
ps -ef | grep -E "GenieAPIService|genie_cpp_adapter|genie-t2t-run|openai_genie_server"
```

不要将此基础设施错误算作模型失败。

### 缺少 libatomic

如果 Genie 报告 `libatomic.so.1: cannot open shared object file`，请将 QAIRT 编译器运行时库目录包含到 `LD_LIBRARY_PATH` 中。第一篇教程中的基础 `qairt-env.sh` 应在需要该兼容性库时同时包含 QAIRT 目标目录和 `lib/aarch64-oe-linux-gcc8.2`。

### C++ 服务找不到包文件

固定的服务在包根目录查找上下文二进制文件，并按 basename 解析部分配置路径。正常的 QAIRT GGUF 导出会将这些文件保存在 `artifacts/` 下。重新运行：

```bash theme={null}
python3 -m shipping_agent.prepare_bundle "$HOME/ministral_q4_genie_export"
```

辅助脚本会创建相对链接；它不会重复几个 GB 的上下文二进制文件。

### C++ `/status` 返回错误

在固定的服务版本中，即使模型健康，`/status` 也可能在序列化其响应时失败。启动器有意探测 `http://127.0.0.1:8911/` 以检测 C++ 就绪。适配器启动后，请使用 `http://127.0.0.1:8001/health`。不要将损坏的 status 响应视为推理失败。

### 有效的 JSON 但没有 Pydantic 工具调用

Genie 返回文本，而不是 OpenAI 工具对象。桥接必须使用原生 Mistral 渲染器和 `mistral_tool` 解析器。通用的 JSON 解析器或另一个模型的聊天模板会将看起来有效的响应变成不可用的调用。

### 输出过长或不终止

确认使用了原生 Mistral `[INST]...[/INST]` 包裹和 EOS token。之前没有原生包裹的原始提示运行了超过 11 分钟；而正确包裹的冒烟请求在几秒内完成。

## 停止演示

在服务器 shell 中使用 `Ctrl+C`。模拟状态仅存在于一次控制台运行中，因此每个场景在下次调用时都会重新开始。
