<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>AgentForge Blog</title>
        <link>https://agentforge-tech.github.io/AgentForge/blog</link>
        <description>AgentForge Blog</description>
        <lastBuildDate>Sun, 11 Oct 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-Hans</language>
        <item>
            <title><![CDATA[开源项目之 AgentForge 发布到 Maven Central 官方仓库实战：命名空间选择与自动化发布]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/番外/工程化/开源项目之AgentForge发布到MavenCentral官方仓库实战</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/番外/工程化/开源项目之AgentForge发布到MavenCentral官方仓库实战</guid>
            <pubDate>Sun, 11 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[把 AgentForge 的库模块以正式版 1.0.0 发布到 Maven Central，让使用者可以直接通过 Maven 依赖引入。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>把 AgentForge 的库模块以正式版 <code>1.0.0</code> 发布到 Maven Central，让使用者可以直接通过 Maven 依赖引入。</p>
</blockquote>
<!-- -->
<br>
<p>在做开源项目时，我们常常会遇到这样一个具体问题：项目在 GitHub 上跑通了，别人想用却不知道该怎么引入。要么 <code>git clone</code> 后自己 <code>mvn install</code>，要么把 jar 传来传去，体验非常差。<strong>最优雅的方式，是把它发布到 Maven Central 官方仓库</strong>，使用者只需要在 <code>pom.xml</code> 里写一段依赖坐标即可。</p>
<p>AgentForge 的模型层与 Agent Runtime 已经具备发布条件，于是我们决定把它正式推送到 Maven Central。整个过程中，第一个拦路虎就是 <strong>groupId 命名空间该怎么定</strong>。本文就把这段完整实践记录下来。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题">一、背景与问题<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98" class="hash-link" aria-label="一、背景与问题的直接链接" title="一、背景与问题的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11-为什么要发布到-maven-central">1.1 为什么要发布到 Maven Central<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#11-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%8F%91%E5%B8%83%E5%88%B0-maven-central" class="hash-link" aria-label="1.1 为什么要发布到 Maven Central的直接链接" title="1.1 为什么要发布到 Maven Central的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>使用零门槛</strong>：使用者只需声明依赖坐标，无需 clone / 编译；</li>
<li class=""><strong>生态认可</strong>：能被 IDE、依赖平台（如 mvnrepository）索引，属于开源项目的"正式身份"；</li>
<li class=""><strong>多项目复用</strong>：配合 BOM 统一版本管理，便于后续模块化演进。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12-我们遇到的问题">1.2 我们遇到的问题<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#12-%E6%88%91%E4%BB%AC%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98" class="hash-link" aria-label="1.2 我们遇到的问题的直接链接" title="1.2 我们遇到的问题的直接链接" translate="no">​</a></h2>
<p>给 AgentForge 发布时，坐标命名（groupId）是绕不过去的第一关：</p>
<ul>
<li class="">早期项目里用的是 <code>com.changlu.agentforge</code>；</li>
<li class="">但 <strong>Maven Central 要求 groupId 必须是你能够证明"拥有 / 控制"的命名空间</strong>，<code>com.changlu</code> 反推域名是 <code>changlu.com</code>，我们并不拥有它，校验会被驳回。</li>
</ul>
<p>那么问题来了：<strong>个人开源项目的 groupId，到底该怎么取？</strong> 这就是本文要先讲清楚的核心概念。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念">二、核心概念<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="二、核心概念的直接链接" title="二、核心概念的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21-发布-maven-central-的硬性要求">2.1 发布 Maven Central 的硬性要求<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#21-%E5%8F%91%E5%B8%83-maven-central-%E7%9A%84%E7%A1%AC%E6%80%A7%E8%A6%81%E6%B1%82" class="hash-link" aria-label="2.1 发布 Maven Central 的硬性要求的直接链接" title="2.1 发布 Maven Central 的硬性要求的直接链接" translate="no">​</a></h2>
<p>在动手之前，必须先知道 Central 对上传构件做了哪些校验。它不是"上传就完事"，而是要求同时满足：</p>
<ol>
<li class=""><strong>sources jar + javadoc jar</strong>（非 <code>pom</code> 打包模块必须提供）；</li>
<li class=""><strong>GPG/PGP 签名</strong>：每个文件要有一个 <code>.asc</code> 签名文件；</li>
<li class=""><strong>校验和</strong>：<code>.md5</code> / <code>.sha1</code> 必需（<code>.sha256</code> / <code>.sha512</code> 可选），由发布插件自动生成；</li>
<li class=""><strong>POM 必需元数据</strong>：<code>name</code>、<code>description</code>、<code>url</code>、<code>licenses</code>、<code>developers</code>、<code>scm</code>；</li>
<li class=""><strong>版本号不能以 <code>-SNAPSHOT</code> 结尾</strong>。</li>
</ol>
<blockquote>
<p><strong>重点</strong>：以上任何一条不满足，发布都会在 Portal 校验阶段失败。本文后续所有配置，都是围绕这 5 条来做的。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22-groupid-命名空间的两种来源方案对比">2.2 groupId 命名空间的两种来源（方案对比）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#22-groupid-%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%9D%A5%E6%BA%90%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="2.2 groupId 命名空间的两种来源（方案对比）的直接链接" title="2.2 groupId 命名空间的两种来源（方案对比）的直接链接" translate="no">​</a></h2>
<p>对于个人 / 小团队项目，groupId 命名空间常见有两条路：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案一基于-github-账号--iogithubusername">方案一：基于 GitHub 账号 —— <code>io.github.&lt;username&gt;</code><a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E6%96%B9%E6%A1%88%E4%B8%80%E5%9F%BA%E4%BA%8E-github-%E8%B4%A6%E5%8F%B7--iogithubusername" class="hash-link" aria-label="方案一基于-github-账号--iogithubusername的直接链接" title="方案一基于-github-账号--iogithubusername的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>规则</strong>：<code>io.github.myusername</code>，例如 <code>io.github.changluya</code>；</li>
<li class=""><strong>校验方式</strong>：无需域名，Sonatype 要求你在<strong>对应的 GitHub 账号</strong>下，创建一个 Portal 指定名字的<strong>公开仓库</strong>来证明账号归属，验证通过后删掉即可。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案二基于自有域名--反向域名">方案二：基于自有域名 —— 反向域名<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E6%96%B9%E6%A1%88%E4%BA%8C%E5%9F%BA%E4%BA%8E%E8%87%AA%E6%9C%89%E5%9F%9F%E5%90%8D--%E5%8F%8D%E5%90%91%E5%9F%9F%E5%90%8D" class="hash-link" aria-label="方案二：基于自有域名 —— 反向域名的直接链接" title="方案二：基于自有域名 —— 反向域名的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>规则</strong>：拥有 <code>example.com</code> 就用 <code>com.example</code>；拥有 <code>changlu.cloud</code> 就用 <code>cloud.changlu</code>，还能继续加项目子空间 <code>cloud.changlu.agentforge</code>；</li>
<li class=""><strong>校验方式</strong>：在<strong>该域名 DNS</strong> 添加一条 Portal 给出的 <strong>TXT 记录</strong>。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="两种方案对比">两种方案对比<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="两种方案对比的直接链接" title="两种方案对比的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th>方案一：GitHub <code>io.github.&lt;username&gt;</code></th><th>方案二：自有域名反向</th></tr></thead><tbody><tr><td>校验方式</td><td>建 GitHub 公开仓库</td><td>加 DNS TXT 记录</td></tr><tr><td>成本</td><td>零成本</td><td>需持有域名</td></tr><tr><td>命名自由度</td><td>被 GitHub 账号名绑死</td><td>完全自主，可用品牌名</td></tr><tr><td>长期稳定性</td><td>账号改名 / 弃用会受影响</td><td>域名在即命名在</td></tr><tr><td>多项目复用</td><td>共用一个账号命名空间</td><td>共用一个域名命名空间</td></tr><tr><td>适用场景</td><td>没有域名的快速起步</td><td>已有域名的品牌化项目</td></tr></tbody></table>
<blockquote>
<p><strong>弊端说明</strong>：方案一虽然零成本，但命名会带上 GitHub 账号名（如 <code>changluya</code>），既不好看也无法改；一旦账号改名，历史坐标就"名不副实"。方案二需要你真正持有并会操作域名 DNS，但长期收益更大。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="23-本方案的选择">2.3 本方案的选择<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#23-%E6%9C%AC%E6%96%B9%E6%A1%88%E7%9A%84%E9%80%89%E6%8B%A9" class="hash-link" aria-label="2.3 本方案的选择的直接链接" title="2.3 本方案的选择的直接链接" translate="no">​</a></h2>
<p><strong>本方案采用方案二：命名空间 <code>cloud.changlu</code>，项目子空间 <code>cloud.changlu.agentforge</code>。</strong></p>
<p>因此下文的<strong>全部前置准备（命名空间校验用 DNS TXT、Token、GPG、settings）都按「自有域名」路径编写</strong>。</p>
<blockquote>
<p><strong>注意</strong>：如果你想改用方案一，只需把命名空间换成 <code>io.github.changluya</code>，并把"DNS TXT 校验"替换成"建 GitHub 公开仓库"，其余步骤（GPG / Token / settings.xml / 工程改造 / 发布流程）<strong>完全一致</strong>。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三整体方案设计">三、整体方案设计<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%B8%89%E6%95%B4%E4%BD%93%E6%96%B9%E6%A1%88%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="三、整体方案设计的直接链接" title="三、整体方案设计的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31-发布哪些模块">3.1 发布哪些模块<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#31-%E5%8F%91%E5%B8%83%E5%93%AA%E4%BA%9B%E6%A8%A1%E5%9D%97" class="hash-link" aria-label="3.1 发布哪些模块的直接链接" title="3.1 发布哪些模块的直接链接" translate="no">​</a></h2>
<p>AgentForge 是一个多模块工程，并不是所有模块都需要对外发布。我们把<strong>库模块</strong>发布，<strong>示例模块</strong>排除：</p>
<table><thead><tr><th>模块 artifactId</th><th>packaging</th><th>是否发布</th><th>说明</th></tr></thead><tbody><tr><td><code>agentforge-ai-parent</code></td><td>pom</td><td>是</td><td>所有模块的 parent，必须发布</td></tr><tr><td><code>agentforge-ai-bom</code></td><td>pom</td><td>是</td><td>BOM，建议发布</td></tr><tr><td><code>agentforge-model</code></td><td>pom</td><td>是</td><td>model 子模块的聚合 / 父 POM</td></tr><tr><td><code>agentforge-model-api</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-model-core</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-model-openai</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-model-anthropic</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-model-registry</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-framework</code></td><td>pom</td><td>是</td><td>framework 子模块的聚合 / 父 POM</td></tr><tr><td><code>agentforge-agent-core</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-harness-agent</code></td><td>jar</td><td>是</td><td></td></tr><tr><td><code>agentforge-aggregator</code>（根）</td><td>pom</td><td>否</td><td>只是聚合器，用户不需要</td></tr><tr><td><code>agentforge-examples</code> / <code>agentforge-service</code> / <code>-ui</code> / <code>-web</code></td><td>pom/jar</td><td>否</td><td>示例 / 应用，不发布</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32-发布链路">3.2 发布链路<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#32-%E5%8F%91%E5%B8%83%E9%93%BE%E8%B7%AF" class="hash-link" aria-label="3.2 发布链路的直接链接" title="3.2 发布链路的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">本地构建</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  → 各模块生成 sources / javadoc jar</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  → 各模块 GPG 签名（.asc）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  → central-publishing-maven-plugin 打包成 bundle</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  → 上传到 Sonatype Central Portal 校验</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  → Portal 发布 → 同步到 Maven Central</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：Sonatype 的 <code>central-publishing-maven-plugin</code> 会把整个 reactor 的构件打成一个 bundle 上传，因此我们只需在 parent 里配置一次，而示例模块用 <code>excludeArtifacts</code> 排除即可。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33-是否需要脚本">3.3 是否需要脚本？<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#33-%E6%98%AF%E5%90%A6%E9%9C%80%E8%A6%81%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="3.3 是否需要脚本？的直接链接" title="3.3 是否需要脚本？的直接链接" translate="no">​</a></h2>
<p><strong>建议配置脚本。</strong> 把"版本号校验 → 本地签名打包预演 → 正式 deploy"封装成一个 <code>release.sh</code>，可以避免每次手敲长命令、漏参数。脚本内容见第七节。</p>
<p>日常开发仍然用 <code>mvn install / test</code>，只有发布时才走脚本。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四前置准备账号--域名--密钥">四、前置准备（账号 / 域名 / 密钥）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E5%9B%9B%E5%89%8D%E7%BD%AE%E5%87%86%E5%A4%87%E8%B4%A6%E5%8F%B7--%E5%9F%9F%E5%90%8D--%E5%AF%86%E9%92%A5" class="hash-link" aria-label="四、前置准备（账号 / 域名 / 密钥）的直接链接" title="四、前置准备（账号 / 域名 / 密钥）的直接链接" translate="no">​</a></h2>
<blockquote>
<p>本节按「自有域名」路径说明。若用 GitHub 方式，见 2.2 的说明。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41-注册-sonatype-central-portal">4.1 注册 Sonatype Central Portal<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#41-%E6%B3%A8%E5%86%8C-sonatype-central-portal" class="hash-link" aria-label="4.1 注册 Sonatype Central Portal的直接链接" title="4.1 注册 Sonatype Central Portal的直接链接" translate="no">​</a></h2>
<p>打开 <a href="https://central.sonatype.com/" target="_blank" rel="noopener noreferrer" class="">https://central.sonatype.com/</a>，用邮箱 / GitHub 注册登录，记住这个账号——后面命名空间、Token、发布都在这里管理。</p>
<p>默认进入的时候选择的namespace由于是github登陆的则是如下<strong>默认的namespace空间</strong>：</p>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610042229470.png" alt="img" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42-添加并校验命名空间-cloudchanglu自有域名路径">4.2 添加并校验命名空间 <code>cloud.changlu</code>（自有域名路径）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#42-%E6%B7%BB%E5%8A%A0%E5%B9%B6%E6%A0%A1%E9%AA%8C%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4-cloudchanglu%E8%87%AA%E6%9C%89%E5%9F%9F%E5%90%8D%E8%B7%AF%E5%BE%84" class="hash-link" aria-label="42-添加并校验命名空间-cloudchanglu自有域名路径的直接链接" title="42-添加并校验命名空间-cloudchanglu自有域名路径的直接链接" translate="no">​</a></h2>
<p><strong>如何完成命名空间校验呢？</strong> 步骤如下：</p>
<ol>
<li class="">登录 Portal → 右上角 <strong>Account → Namespaces</strong>（或首页 <strong>Add Namespace</strong>）；</li>
<li class="">输入 <strong><code>cloud.changlu</code></strong>，提交；<!-- -->
<ul>
<li class="">注意：校验针对的是<strong>域名本身 <code>changlu.cloud</code></strong>，<code>cloud.changlu</code> 只是它的反向命名空间；</li>
</ul>
</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610042229835.png" alt="image-20261004222909676" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610042229418.png" alt="img" class="img_ev3q"></p>
<ol start="3">
<li class="">
<p>Portal 给出一个 <strong>Verification Key</strong>，到 <code>changlu.cloud</code> 的 DNS 服务商添加一条 <strong>TXT 记录</strong>：</p>
<ul>
<li class="">主机记录：<code>@</code>（根域名 <code>changlu.cloud</code>）</li>
<li class="">记录类型：<code>TXT</code></li>
<li class="">记录值：Portal 给出的验证值（完整粘贴）</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610042230619.png" alt="img" class="img_ev3q"></p>
</li>
<li class="">
<p><strong>如何验证 TXT 是否生效呢？</strong> 本地执行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">dig</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token plain"> txt changlu.cloud</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 或</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">host</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token plain"> txt changlu.cloud</span><br></div></code></pre></div></div>
<p>能看到你添加的 TXT 值即可。</p>
</li>
<li class="">
<p>回到 Portal 点击 <strong>Verify</strong>，状态变为 <strong>Verified</strong>。</p>
</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610042230292.png" alt="img" class="img_ev3q"></p>
<blockquote>
<p><strong>注意</strong>：DNS 有缓存，刚添加可能需要等几分钟到几十分钟；国内 DNS 商可能更久。</p>
</blockquote>
<blockquote>
<p><strong>重点</strong>：命名空间一旦通过，其子命名空间（<code>cloud.changlu.agentforge</code>）无需再次校验，可直接发布。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="43-安装-gpg生成密钥并上传公钥含常见失败排查">4.3 安装 GPG、生成密钥并上传公钥（含常见失败排查）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#43-%E5%AE%89%E8%A3%85-gpg%E7%94%9F%E6%88%90%E5%AF%86%E9%92%A5%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%85%AC%E9%92%A5%E5%90%AB%E5%B8%B8%E8%A7%81%E5%A4%B1%E8%B4%A5%E6%8E%92%E6%9F%A5" class="hash-link" aria-label="4.3 安装 GPG、生成密钥并上传公钥（含常见失败排查）的直接链接" title="4.3 安装 GPG、生成密钥并上传公钥（含常见失败排查）的直接链接" translate="no">​</a></h2>
<blockquote>
<p>本节按“安装 → 配置 → 生成 → 签名自检 → 上传反查 → 备份”编排。除密钥向导中的姓名、公开邮箱和 Passphrase 需要人工输入外，命令块都可按顺序直接复制执行。示例不包含任何真实密钥、口令或 Token。</p>
</blockquote>
<p><strong>第 0 步：先确认本机是否已有 GPG：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><br></div></code></pre></div></div>
<ul>
<li class="">有输出且 &gt;= 2.2 → 直接跳到 4.3.2；</li>
<li class="">提示 <code>command not found</code> → 按下文安装。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="431-按操作系统选择安装方式">4.3.1 按操作系统选择安装方式<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#431-%E6%8C%89%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E9%80%89%E6%8B%A9%E5%AE%89%E8%A3%85%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="4.3.1 按操作系统选择安装方式的直接链接" title="4.3.1 按操作系统选择安装方式的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>环境</th><th>推荐路径</th><th>适用说明</th></tr></thead><tbody><tr><td>macOS（Homebrew 当前支持版本）</td><td>Homebrew bottle</td><td>最短路径，有预编译包时无需本地编译</td></tr><tr><td>macOS 旧版本 / Homebrew Tier 3</td><td>MacPorts</td><td>优先使用对应 macOS 的二进制包</td></tr><tr><td>macOS（偏好图形化）</td><td>GPG Suite</td><td>自带 MacGPG、GPG Keychain 和 pinentry</td></tr><tr><td>Debian / Ubuntu / WSL</td><td><code>apt</code></td><td>使用发行版软件包</td></tr><tr><td>Fedora / RHEL</td><td><code>dnf</code></td><td>使用发行版软件包</td></tr><tr><td>Windows 10/11</td><td>Gpg4win</td><td>官方推荐的 Windows 安装包，自带 Kleopatra/pinentry</td></tr></tbody></table>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="macos">macOS<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#macos" class="hash-link" aria-label="macOS的直接链接" title="macOS的直接链接" translate="no">​</a></h4>
<p><strong>先看 macOS 版本，再选安装路径：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">sw_vers </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-productVersion</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">uname</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-m</span><br></div></code></pre></div></div>
<p>Homebrew 只会为当前支持的 macOS 版本持续生成预编译 bottle。较旧的 macOS 可能被划为 Tier 3，此时 <code>brew install gnupg</code> 会转为从源码编译 OpenSSL 等大型依赖，耗时且容易中断。以 <strong>2026-10</strong> 的官方支持矩阵为例，Apple Silicon 上 macOS 15–27 可用 bottle，macOS 11–14 属 Tier 3。请以 <a href="https://docs.brew.sh/Support-Tiers" target="_blank" rel="noopener noreferrer" class="">Homebrew Support Tiers</a> 的实时说明为准。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="路径-ahomebrew当前受支持的-macos">路径 A：Homebrew（当前受支持的 macOS）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E8%B7%AF%E5%BE%84-ahomebrew%E5%BD%93%E5%89%8D%E5%8F%97%E6%94%AF%E6%8C%81%E7%9A%84-macos" class="hash-link" aria-label="路径 A：Homebrew（当前受支持的 macOS）的直接链接" title="路径 A：Homebrew（当前受支持的 macOS）的直接链接" translate="no">​</a></h4>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">brew </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">brew </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> gnupg pinentry-mac</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">command</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> gpg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><br></div></code></pre></div></div>
<p>Homebrew 在有匹配 bottle 时会自动下载预编译包，无需手动加 <code>--force-bottle</code>。如果提示 <code>has no bottle</code> 或长时间出现 <code>build.rb</code> / 编译 <code>openssl@3</code>，说明当前系统没有匹配的二进制包；不要反复启动 <code>brew install</code>，直接改用路径 B 或 C。</p>
<blockquote>
<p>Apple Silicon 上 Homebrew 默认在 <code>/opt/homebrew</code>；若 <code>command -v gpg</code> 无输出，执行 <code>eval "$(/opt/homebrew/bin/brew shellenv)"</code>，并将该行写入 <code>~/.zprofile</code>。Intel Mac 默认前缀为 <code>/usr/local</code>。</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="路径-bmacports旧版-macos-推荐可用二进制包">路径 B：MacPorts（旧版 macOS 推荐，可用二进制包）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E8%B7%AF%E5%BE%84-bmacports%E6%97%A7%E7%89%88-macos-%E6%8E%A8%E8%8D%90%E5%8F%AF%E7%94%A8%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%8C%85" class="hash-link" aria-label="路径 B：MacPorts（旧版 macOS 推荐，可用二进制包）的直接链接" title="路径 B：MacPorts（旧版 macOS 推荐，可用二进制包）的直接链接" translate="no">​</a></h4>
<ol>
<li class="">从 <a href="https://www.macports.org/install.php" target="_blank" rel="noopener noreferrer" class="">MacPorts 官方安装页</a> 下载与 macOS 大版本一致的 <code>.pkg</code>（例如 Sonoma 14）并安装；</li>
<li class="">打开新终端，执行：</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> /tmp</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> /opt/local/bin/port selfupdate</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 使用默认 +pinentry（终端口令输入器），不需要完整 Xcode</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> /opt/local/bin/port </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> gnupg2</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">PATH</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/opt/local/bin:/opt/local/sbin:</span><span class="token string environment constant" style="color:rgb(189, 147, 249)">$PATH</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">command</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> gpg </span><span class="token operator">&amp;&amp;</span><span class="token plain"> gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><br></div></code></pre></div></div>
<p>MacPorts 默认安装到 <code>/opt/local</code>。若 PATH 未自动生效：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'export PATH="/opt/local/bin:/opt/local/sbin:$PATH"'</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.zprofile</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">source</span><span class="token plain"> ~/.zprofile</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>不要在只安装了 Command Line Tools 的机器上加 <code>+pinentry_mac</code></strong>。MacPorts 的 <code>pinentry-mac</code> 端口要求完整 Xcode，否则会报 <code>requires a full Xcode installation</code>。Maven Central 签名使用默认终端版 <code>pinentry</code> 即可。如果已用错误 variant 启动过构建，需先清理该 port 的临时构建目录，再安装默认 variant：</p>
</blockquote>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> /tmp</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> /opt/local/bin/port clean gnupg2</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> /opt/local/bin/port </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> gnupg2</span><br></div></code></pre></div></div>
<blockquote>
<p><code>port clean gnupg2</code> 只会清理该 port 的临时构建产物，不会删除项目文件，也不会卸载已安装的其他依赖。</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="路径-cgpg-suite图形化快速方案">路径 C：GPG Suite（图形化快速方案）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E8%B7%AF%E5%BE%84-cgpg-suite%E5%9B%BE%E5%BD%A2%E5%8C%96%E5%BF%AB%E9%80%9F%E6%96%B9%E6%A1%88" class="hash-link" aria-label="路径 C：GPG Suite（图形化快速方案）的直接链接" title="路径 C：GPG Suite（图形化快速方案）的直接链接" translate="no">​</a></h4>
<p>从 <a href="https://gpgtools.org/" target="_blank" rel="noopener noreferrer" class="">GPG Suite 官网</a> 下载与当前 macOS 兼容的安装包。它自带 MacGPG、GPG Keychain 和 pinentry，不需要用 Homebrew 编译 OpenSSL。GPG Mail 是可选邮件插件，发布 Maven Central 并不需要它。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">/usr/local/MacGPG2/bin/gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'export PATH="/usr/local/MacGPG2/bin:$PATH"'</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.zprofile</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">source</span><span class="token plain"> ~/.zprofile</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="linux--wsl">Linux / WSL<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#linux--wsl" class="hash-link" aria-label="Linux / WSL的直接链接" title="Linux / WSL的直接链接" translate="no">​</a></h4>
<p>GnuPG 通常已在 Linux 发行版中提供。WSL 按其 Linux 发行版执行相同命令：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Debian / Ubuntu / WSL Ubuntu</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> update</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-y</span><span class="token plain"> gnupg2 pinentry-curses</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Fedora / RHEL</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> dnf </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-y</span><span class="token plain"> gnupg2 pinentry</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><br></div></code></pre></div></div>
<p>SSH、CI 或纯终端环境建议使用 <code>pinentry-curses</code>，并在当前 shell 设置：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_TTY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">tty</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="windows-1011">Windows 10/11<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#windows-1011" class="hash-link" aria-label="Windows 10/11的直接链接" title="Windows 10/11的直接链接" translate="no">​</a></h4>
<p>从 <a href="https://www.gpg4win.org/" target="_blank" rel="noopener noreferrer" class="">Gpg4win 官网</a> 下载安装包。发布 Maven Central 只需 GnuPG 和 pinentry，Kleopatra 可用于图形化管理密钥。安装后重开 PowerShell 验证：</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg --version</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">where.exe gpg</span><br></div></code></pre></div></div>
<p><code>gpg</code> 若未进入 PATH，将 Gpg4win 的 <code>bin</code> 目录加入用户 PATH，再重开 PowerShell。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么时候才需要-command-line-tools">什么时候才需要 Command Line Tools<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E6%89%8D%E9%9C%80%E8%A6%81-command-line-tools" class="hash-link" aria-label="什么时候才需要 Command Line Tools的直接链接" title="什么时候才需要 Command Line Tools的直接链接" translate="no">​</a></h4>
<p>Homebrew 安装 gnupg 时若报下面这个错：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Error: No developer tools installed.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Install the Command Line Tools:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  xcode-select --install</span><br></div></code></pre></div></div>
<p>说明系统缺少 <strong>Command Line Tools</strong>——即使 <code>/usr/bin/clang</code> 存在，只要没有"active developer directory"就会报。先执行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">xcode-select </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--install</span><br></div></code></pre></div></div>
<p>会弹出安装窗口，点 <strong>安装 / Install</strong>，等待下载安装完成（几分钟）。完成后确认：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">xcode-select </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 期望输出：/Library/Developer/CommandLineTools</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：安装 bottle 时 Apple Silicon 通常不需要 CLT；从源码编译时才必须安装。若已装完整 Xcode，也可用 <code>sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer</code>。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="432-验证-path-并配置-pinentry">4.3.2 验证 PATH 并配置 pinentry<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#432-%E9%AA%8C%E8%AF%81-path-%E5%B9%B6%E9%85%8D%E7%BD%AE-pinentry" class="hash-link" aria-label="4.3.2 验证 PATH 并配置 pinentry的直接链接" title="4.3.2 验证 PATH 并配置 pinentry的直接链接" translate="no">​</a></h3>
<p>先确认 Maven 后续能通过 PATH 找到同一个 <code>gpg</code>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">command</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> gpg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">command</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> pinentry </span><span class="token operator">||</span><span class="token plain"> </span><span class="token boolean">true</span><br></div></code></pre></div></div>
<p><code>command -v gpg</code> 必须输出一个真实路径，然后再执行下面的对应环境命令。</p>
<p>macOS/Linux 建议把 <code>GPG_TTY</code> 写入 shell 配置，避免口令提示绑定到错误终端：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># zsh（macOS 默认）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-qxF</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'export GPG_TTY="$(tty)"'</span><span class="token plain"> ~/.zshrc </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'export GPG_TTY="$(tty)"'</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.zshrc</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_TTY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">tty</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<p>bash 用户执行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-qxF</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'export GPG_TTY="$(tty)"'</span><span class="token plain"> ~/.bashrc </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'export GPG_TTY="$(tty)"'</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.bashrc</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_TTY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">tty</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<p>macOS 上 <code>gpg --full-generate-key</code> 最常见的报错就是 <code>No pinentry</code> 或 <code>Inappropriate ioctl for device</code>，原因是找不到口令输入程序：</p>
<p>Homebrew 用户直接复制：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> ~/.gnupg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">700</span><span class="token plain"> ~/.gnupg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">PINENTRY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">brew </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--prefix</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">/bin/pinentry-mac"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">touch</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i.bak</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/^pinentry-program /d'</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"pinentry-program </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$PINENTRY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpgconf </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--kill</span><span class="token plain"> gpg-agent</span><br></div></code></pre></div></div>
<p>MacPorts 用户直接复制：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">PATH</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/opt/local/bin:/opt/local/sbin:</span><span class="token string environment constant" style="color:rgb(189, 147, 249)">$PATH</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> ~/.gnupg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">700</span><span class="token plain"> ~/.gnupg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">touch</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i.bak</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/^pinentry-program /d'</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'pinentry-program /opt/local/bin/pinentry'</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpgconf </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--kill</span><span class="token plain"> gpg-agent</span><br></div></code></pre></div></div>
<p>Linux 纯终端用户若需要显式配置 <code>pinentry-curses</code>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> ~/.gnupg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">700</span><span class="token plain"> ~/.gnupg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">touch</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sed</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i.bak</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/^pinentry-program /d'</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"pinentry-program </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">command</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> pinentry-curses</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> ~/.gnupg/gpg-agent.conf</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpgconf </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--kill</span><span class="token plain"> gpg-agent</span><br></div></code></pre></div></div>
<p>GPG Suite 和 Gpg4win 已自带并配置 pinentry，通常无需手动写 <code>gpg-agent.conf</code>。MacPorts 默认的 <code>/opt/local/bin/pinentry</code> 是终端版，可直接用于 Maven 签名。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="433-生成密钥对">4.3.3 生成密钥对<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#433-%E7%94%9F%E6%88%90%E5%AF%86%E9%92%A5%E5%AF%B9" class="hash-link" aria-label="4.3.3 生成密钥对的直接链接" title="4.3.3 生成密钥对的直接链接" translate="no">​</a></h3>
<p><strong>个人本机推荐交互式</strong>，让 pinentry 安全读取 Passphrase，避免把口令写入 shell 历史或配置文件：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_TTY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">tty</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator file-descriptor important">2</span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg --full-generate-key</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># RSA and RSA → 4096 → 有效期（例如 2y）→ 姓名/邮箱 → 设置强 Passphrase</span><br></div></code></pre></div></div>
<p>建议值：</p>
<ul>
<li class="">类型：<code>RSA and RSA</code>；</li>
<li class="">长度：<code>4096</code>；</li>
<li class="">有效期：建议 <code>2y</code>，到期前可续期，比永久密钥更易管理风险；</li>
<li class="">姓名/邮箱：使用长期维护 Maven Central 构件的发布者身份；</li>
<li class="">Passphrase：使用强口令，不要写入仓库、脚本或 shell 历史。</li>
</ul>
<blockquote>
<p>CI 场景可用 <code>--batch</code> 生成专用密钥，但不建议把 <code>%no-protection</code> 作为个人发布密钥的默认选项。如果必须使用无口令 CI 密钥，应限定用途、设置有效期，并将私钥仅保存在 CI Secret 中。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="434-查看并记录-key_id">4.3.4 查看并记录 KEY_ID<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#434-%E6%9F%A5%E7%9C%8B%E5%B9%B6%E8%AE%B0%E5%BD%95-key_id" class="hash-link" aria-label="4.3.4 查看并记录 KEY_ID的直接链接" title="4.3.4 查看并记录 KEY_ID的直接链接" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg --list-secret-keys --keyid-format</span><span class="token operator">=</span><span class="token plain">long</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># sec   rsa4096/ABCDEF1234567890 2026-10-04 ...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">#              ^^^^^^^^^^^^^^^^ 即 KEY_ID</span><br></div></code></pre></div></div>
<p>同时记录完整的 40 位指纹，对外核对密钥时应优先使用指纹，不要只比较短 KEY_ID：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 自动取当前密钥环中的第一把私钥</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">KEY_ID</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">gpg </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--batch</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> --with-colons --list-secret-keys </span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">awk</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> -F: </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">'$1 == "sec" { print $5; exit }'</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">FULL_FINGERPRINT</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">gpg </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--batch</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> --with-colons </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--fingerprint</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">awk</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> -F: </span><span class="token string variable string" style="color:rgb(255, 121, 198);font-style:italic">'$1 == "fpr" { print $10; exit }'</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$FULL_FINGERPRINT</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'ERROR: 未找到私钥或指纹'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"KEY_ID=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"FULL_FINGERPRINT=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$FULL_FINGERPRINT</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--fingerprint</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<blockquote>
<p>如果密钥环中已有多把私钥，先通过 <code>gpg --list-secret-keys --keyid-format=long</code> 找到本次创建的密钥，再手动执行 <code>KEY_ID="16位长KEY_ID"</code>。后续命令默认在同一个终端中执行并复用这两个变量。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="435-本地签名与验签发布前必做">4.3.5 本地签名与验签（发布前必做）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#435-%E6%9C%AC%E5%9C%B0%E7%AD%BE%E5%90%8D%E4%B8%8E%E9%AA%8C%E7%AD%BE%E5%8F%91%E5%B8%83%E5%89%8D%E5%BF%85%E5%81%9A" class="hash-link" aria-label="4.3.5 本地签名与验签（发布前必做）的直接链接" title="4.3.5 本地签名与验签（发布前必做）的直接链接" translate="no">​</a></h3>
<p>这一步会同时验证私钥、pinentry、Passphrase 和 GPG agent，比直接跑 Maven 更容易定位问题：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 即使换了新终端，也能自动恢复变量</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">KEY_ID</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${KEY_ID</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(gpg --batch --with-colons --list-secret-keys | awk -F</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> '$1 == "sec" { print $5; exit }</span><span class="token string" style="color:rgb(255, 121, 198)">')}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'AgentForge Maven Central GPG signing test'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> /tmp/agentforge-gpg-test.txt</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg --local-user </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--armor</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --detach-sign /tmp/agentforge-gpg-test.txt</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--verify</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  /tmp/agentforge-gpg-test.txt.asc </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  /tmp/agentforge-gpg-test.txt</span><br></div></code></pre></div></div>
<p>看到 <code>Good signature</code> / <code>完好的签名</code> 即表示本地签名链路正常。测试文件位于 <code>/tmp</code>，不要将其提交到项目。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="436-上传公钥并从服务器反查">4.3.6 上传公钥并从服务器反查<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#436-%E4%B8%8A%E4%BC%A0%E5%85%AC%E9%92%A5%E5%B9%B6%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8F%8D%E6%9F%A5" class="hash-link" aria-label="4.3.6 上传公钥并从服务器反查的直接链接" title="4.3.6 上传公钥并从服务器反查的直接链接" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 自动恢复 KEY_ID 与完整指纹</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">KEY_ID</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${KEY_ID</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(gpg --batch --with-colons --list-secret-keys | awk -F</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> '$1 == "sec" { print $5; exit }</span><span class="token string" style="color:rgb(255, 121, 198)">')}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">FULL_FINGERPRINT</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${FULL_FINGERPRINT</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(gpg --batch --with-colons --fingerprint "$KEY_ID" | awk -F</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> '$1 == "fpr" { print $10; exit }</span><span class="token string" style="color:rgb(255, 121, 198)">')}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 主用：Ubuntu 服务器（较稳定）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--keyserver</span><span class="token plain"> hkps://keyserver.ubuntu.com --send-keys </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 备用：主用超时时改用它</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--keyserver</span><span class="token plain"> hkps://keys.openpgp.org --send-keys </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 上传后请使用完整指纹反查，不要只用短 KEY_ID</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-fsSL</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"https://keyserver.ubuntu.com/pks/lookup?op=get&amp;search=0x</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${FULL_FINGERPRINT}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'BEGIN PGP PUBLIC KEY BLOCK'</span><br></div></code></pre></div></div>
<p>输出 <code>BEGIN PGP PUBLIC KEY BLOCK</code> 说明 keyserver 已经可以返回该公钥。刚上传后索引可能有短暂延迟，等待几十秒后再查即可。</p>
<blockquote>
<p><strong>注意</strong>：<code>keys.openpgp.org</code> 会<strong>剥离用户身份</strong>，且需要收邮件点确认链接后才算上传成功；Maven Central 更常从 <code>keyserver.ubuntu.com</code> 拉取，因此<strong>优先用 ubuntu</strong>。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="437-备份私钥和-ownertrust">4.3.7 备份私钥和 ownertrust<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#437-%E5%A4%87%E4%BB%BD%E7%A7%81%E9%92%A5%E5%92%8C-ownertrust" class="hash-link" aria-label="4.3.7 备份私钥和 ownertrust的直接链接" title="4.3.7 备份私钥和 ownertrust的直接链接" translate="no">​</a></h3>
<p>公钥可以公开，私钥必须加密保存。备份时先限制当前 shell 的文件权限：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">umask</span><span class="token plain"> 077</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">KEY_ID</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${KEY_ID</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(gpg --batch --with-colons --list-secret-keys | awk -F</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> '$1 == "sec" { print $5; exit }</span><span class="token string" style="color:rgb(255, 121, 198)">')}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_BACKUP_DIR</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string environment constant" style="color:rgb(189, 147, 249)">$HOME</span><span class="token string" style="color:rgb(255, 121, 198)">/agentforge-gpg-backup"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_BACKUP_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">700</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_BACKUP_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--armor</span><span class="token plain"> --export-secret-keys </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_BACKUP_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">/agentforge-private-key.asc"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg --export-ownertrust </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_BACKUP_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">/agentforge-ownertrust.txt"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_BACKUP_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain">/*</span><br></div></code></pre></div></div>
<p><code>$HOME/agentforge-gpg-backup</code> 只是临时导出位置。加密磁盘、离线介质或支持安全附件的密码管理器才是合适的长期备份位置。转移完成后删除该临时目录；<strong>不要提交 Git，不要上传网盘明文，不要把私钥或 Passphrase 发给他人</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="438-完整验收清单">4.3.8 完整验收清单<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#438-%E5%AE%8C%E6%95%B4%E9%AA%8C%E6%94%B6%E6%B8%85%E5%8D%95" class="hash-link" aria-label="4.3.8 完整验收清单的直接链接" title="4.3.8 完整验收清单的直接链接" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">command</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> gpg</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg --list-secret-keys --keyid-format</span><span class="token operator">=</span><span class="token plain">long</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">KEY_ID</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${KEY_ID</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(gpg --batch --with-colons --list-secret-keys | awk -F</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> '$1 == "sec" { print $5; exit }</span><span class="token string" style="color:rgb(255, 121, 198)">')}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--fingerprint</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$KEY_ID</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">gpg </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--verify</span><span class="token plain"> /tmp/agentforge-gpg-test.txt.asc /tmp/agentforge-gpg-test.txt</span><br></div></code></pre></div></div>
<p>全部满足以下条件后，4.3 才算完成：</p>
<ul>
<li class=""><code>gpg</code> 可通过 PATH 找到；</li>
<li class="">能看到私钥、长 KEY_ID 和完整指纹；</li>
<li class="">detached signature 本地验签成功；</li>
<li class="">Ubuntu keyserver 能按完整指纹返回公钥；</li>
<li class="">私钥和 ownertrust 已在仓库外安全备份；</li>
<li class="">KEY_ID 已记录，供 Maven GPG 插件使用。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="439-常见失败与排查">4.3.9 常见失败与排查<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#439-%E5%B8%B8%E8%A7%81%E5%A4%B1%E8%B4%A5%E4%B8%8E%E6%8E%92%E6%9F%A5" class="hash-link" aria-label="4.3.9 常见失败与排查的直接链接" title="4.3.9 常见失败与排查的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>现象</th><th>原因</th><th>处理</th></tr></thead><tbody><tr><td><code>gpg: command not found</code></td><td>未安装 / 安装目录不在 PATH</td><td>按 4.3.1 选择本机路径，并配置 <code>/opt/homebrew/bin</code>、<code>/opt/local/bin</code> 或 Gpg4win <code>bin</code></td></tr><tr><td><code>No pinentry</code> / <code>Inappropriate ioctl for device</code></td><td>缺 pinentry / <code>GPG_TTY</code> 未设置 / 配置了错误路径</td><td>按 4.3.2 检查 pinentry，设置 <code>export GPG_TTY="$(tty)"</code>，然后 <code>gpgconf --kill gpg-agent</code></td></tr><tr><td><code>brew: command not found</code></td><td>未装 Homebrew</td><td>按 4.3.1 方式一安装</td></tr><tr><td><code>Error: No developer tools installed</code></td><td>缺 Xcode Command Line Tools</td><td>执行 <code>xcode-select --install</code>，或改用 4.3.1 方式二 GPG Suite</td></tr><tr><td>Homebrew 长时间编译 <code>openssl@3</code></td><td>当前 macOS 无匹配 bottle / Tier 3</td><td>停止重复安装，改用 MacPorts 的对应 macOS <code>.pkg</code> 和 <code>gnupg2</code></td></tr><tr><td><code>--force-bottle</code> 仍报 <code>has no bottle</code></td><td>当前公式未发布该系统的 bottle</td><td>参数无法凭空生成 bottle，改用 MacPorts / GPG Suite</td></tr><tr><td>MacPorts 报 <code>pinentry-mac requires a full Xcode installation</code></td><td>显式选了 <code>+pinentry_mac</code></td><td>无需安装完整 Xcode；运行 <code>sudo port clean gnupg2 &amp;&amp; sudo port install gnupg2</code> 改用默认 <code>+pinentry</code></td></tr><tr><td>MacPorts 报 <code>Requested variants ... do not match</code></td><td>上一次失败构建留下了 variant 缓存</td><td>运行 <code>sudo port clean gnupg2</code>，然后重新安装</td></tr><tr><td>生成密钥时卡住无反应</td><td>pinentry 弹窗被拦截 / TTY 不正确</td><td>确保终端在前台，执行 <code>export GPG_TTY="$(tty)"</code> 和 <code>gpgconf --kill gpg-agent</code> 后重试</td></tr><tr><td><code>signing failed: No pinentry</code></td><td>Maven 进程启动时找不到 pinentry</td><td>用 4.3.5 的独立签名测试先验证，并检查 <code>gpg-agent.conf</code> 中的绝对路径</td></tr><tr><td><code>keyserver send failed: Connection timed out</code></td><td>公钥服务器网络问题</td><td>换 <code>hkps://keys.openpgp.org</code> 重试</td></tr><tr><td>发送命令长时间停在 <code>sending key</code></td><td>keyserver 回应慢，不一定表示失败</td><td>先用 4.3.6 的 HTTPS 指纹查询反查，查得到公钥即已上传</td></tr><tr><td>openpgp.org 上传后查不到</td><td>需邮箱确认</td><td>邮箱点确认后重新 <code>--send-keys</code></td></tr><tr><td>签名时报 <code>No secret key</code></td><td>KEY_ID 不对 / 密钥在别的 keyring</td><td><code>gpg --list-secret-keys</code> 核对，或加 <code>--local-user "$KEY_ID"</code></td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：私钥与 Passphrase 只在本机 / CI 使用，<strong>不要提交到仓库</strong>。公开的只有公钥、KEY_ID 和指纹。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="44-生成-portal-user-token">4.4 生成 Portal User Token<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#44-%E7%94%9F%E6%88%90-portal-user-token" class="hash-link" aria-label="4.4 生成 Portal User Token的直接链接" title="4.4 生成 Portal User Token的直接链接" translate="no">​</a></h2>
<ol>
<li class="">
<p>登录 <a href="https://central.sonatype.com/" target="_blank" rel="noopener noreferrer" class="">https://central.sonatype.com/</a>；</p>
</li>
<li class="">
<p>右上角打开用户菜单，点击 <strong>View User Tokens</strong>（或访问 <a href="https://central.sonatype.com/account" target="_blank" rel="noopener noreferrer" class="">https://central.sonatype.com/account</a>）；</p>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610050004619.png" alt="image-20261005000413403" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610050004722.png" alt="image-20261005000438264" class="img_ev3q"></p>
</li>
<li class="">
<p>在 User Tokens 页面生成 Token，得到一对 <strong>username / password</strong>（这是 Token，不是登录密码），只显示一次，立即保存。</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="45-配置-maven-settingsxml">4.5 配置 Maven <code>settings.xml</code><a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#45-%E9%85%8D%E7%BD%AE-maven-settingsxml" class="hash-link" aria-label="45-配置-maven-settingsxml的直接链接" title="45-配置-maven-settingsxml的直接链接" translate="no">​</a></h2>
<p>可以使用 Maven 默认位置 <code>~/.m2/settings.xml</code>，也可放在仓库外的独立目录，发布时通过 <code>mvn -s /absolute/path/settings.xml</code> 指定。下面配置不把 GPG Passphrase 明文写入 XML：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token prolog" style="color:rgb(189, 147, 249)">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">settings</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">xmlns</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">http://maven.apache.org/SETTINGS/1.0.0</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">          </span><span class="token tag attr-name namespace" style="color:rgb(241, 250, 140)">xmlns:</span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">xsi</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">http://www.w3.org/2001/XMLSchema-instance</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">          </span><span class="token tag attr-name namespace" style="color:rgb(241, 250, 140)">xsi:</span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">schemaLocation</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">http://maven.apache.org/SETTINGS/1.0.0</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token tag attr-value" style="color:rgb(255, 121, 198)">                              https://maven.apache.org/xsd/settings-1.0.0.xsd</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">servers</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- Central Portal 的 User Token --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">server</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">central</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">username</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">YOUR_CENTRAL_TOKEN_USERNAME</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">username</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">password</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">YOUR_CENTRAL_TOKEN_PASSWORD</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">password</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">server</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">servers</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">settings</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
<p>限制文件权限：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> /absolute/path/settings.xml</span><br></div></code></pre></div></div>
<p>发布前在当前终端安全读取 GPG Passphrase，输入时不回显，也不进入 shell 历史：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">printf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'GPG Passphrase: '</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">IFS</span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> MAVEN_GPG_PASSPHRASE</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">printf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'\n'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> MAVEN_GPG_PASSPHRASE</span><br></div></code></pre></div></div>
<p>使用自定义配置文件时，Maven 命令必须带 <code>-s</code>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">mvn </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> /absolute/path/settings.xml help:effective-settings</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>&lt;server id="central"&gt;</code> 对应发布插件的 <code>publishingServerId</code>，不要写成 <code>${server}</code>。Maven GPG Plugin 3.2.x 默认从 <code>MAVEN_GPG_PASSPHRASE</code> 读取口令，不再需要在 <code>settings.xml</code> 增加 <code>gpg</code> server。</p>
</blockquote>
<blockquote>
<p><code>settings.xml</code> 包含 Central Token，不得提交到 Git。文章中只使用占位符，不展示真实 Token 或 Passphrase。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五工程构建改造pom">五、工程构建改造（POM）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%BA%94%E5%B7%A5%E7%A8%8B%E6%9E%84%E5%BB%BA%E6%94%B9%E9%80%A0pom" class="hash-link" aria-label="五、工程构建改造（POM）的直接链接" title="五、工程构建改造（POM）的直接链接" translate="no">​</a></h2>
<p><strong>为什么核心发布配置只改两个文件？</strong> 因为本仓库模块的 parent 是 <code>agentforge-ai-parent</code>，根 <code>agentforge-aggregator</code> 并不是模块的 parent，所以：</p>
<ul>
<li class="">所有模块要用的插件（source/javadoc/gpg/central）→ 放 <code>agentforge-ai-parent</code>；</li>
<li class="">根聚合器自己不发布 → 根 POM 加 <code>maven.deploy.skip=true</code>。</li>
</ul>
<p>Central 还要求每个发布 POM 具有完整描述；如果某个子聚合模块原来缺少 <code>&lt;description&gt;</code>，还需在该模块的 POM 中补上。本项目对应为 <code>agentforge-model/pom.xml</code> 和 <code>agentforge-framework/pom.xml</code>。</p>
<p>改造全部放进 <strong><code>release</code> profile</strong>，日常 <code>mvn install / test</code> 不触发，行为不变。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51-根-pomxml">5.1 根 <code>pom.xml</code><a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#51-%E6%A0%B9-pomxml" class="hash-link" aria-label="51-根-pomxml的直接链接" title="51-根-pomxml的直接链接" translate="no">​</a></h2>
<p>在 <code>&lt;properties&gt;</code> 增加（只影响根聚合器自身）：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">properties</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">revision</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">1.0.0-SNAPSHOT</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">revision</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 根聚合器不发布到 Central --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.deploy.skip</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">true</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.deploy.skip</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ...</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">properties</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52-agentforge-ai-parentpomxml-补齐元数据">5.2 <code>agentforge-ai-parent/pom.xml</code> 补齐元数据<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#52-agentforge-ai-parentpomxml-%E8%A1%A5%E9%BD%90%E5%85%83%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="52-agentforge-ai-parentpomxml-补齐元数据的直接链接" title="52-agentforge-ai-parentpomxml-补齐元数据的直接链接" translate="no">​</a></h2>
<p>在 <code>&lt;licenses&gt;...&lt;/licenses&gt;</code> 之后增加：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">developers</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">developer</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">changlu</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">name</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">changlu</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">name</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">email</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">YOUR_PUBLIC_EMAIL_OR_NOREPLY_EMAIL</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">email</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">url</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">https://changlu.cloud</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">url</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">organization</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">AgentForge</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">organization</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">organizationUrl</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">https://github.com/changluya/AgentForge</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">organizationUrl</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">developer</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">developers</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">scm</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">connection</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">scm:git:https://github.com/changluya/AgentForge.git</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">connection</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">developerConnection</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">scm:git:ssh://git@github.com/changluya/AgentForge.git</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">developerConnection</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">tag</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">HEAD</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">tag</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">url</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">https://github.com/changluya/AgentForge</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">url</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">scm</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
<blockquote>
<p><code>licenses</code> / <code>developers</code> / <code>scm</code> 会被子模块继承，Central 校验时可读取；<code>name</code> / <code>description</code> 现有模块已具备，如校验提示缺 <code>url</code>，在 parent 补 <code>&lt;url&gt;</code> 或逐模块补齐。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="53-agentforge-ai-parentpomxml-增加-release-profile">5.3 <code>agentforge-ai-parent/pom.xml</code> 增加 <code>release</code> profile<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#53-agentforge-ai-parentpomxml-%E5%A2%9E%E5%8A%A0-release-profile" class="hash-link" aria-label="53-agentforge-ai-parentpomxml-增加-release-profile的直接链接" title="53-agentforge-ai-parentpomxml-增加-release-profile的直接链接" translate="no">​</a></h2>
<p>先在现有 <code>&lt;properties&gt;</code> 中统一声明插件版本：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">gpg.executable</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">gpg</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">gpg.executable</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 公开指纹：固定发布密钥，避免多私钥环境误签 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">gpg.keyname</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">YOUR_FULL_GPG_FINGERPRINT</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">gpg.keyname</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.gpg.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">3.2.8</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.gpg.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.javadoc.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">3.11.2</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.javadoc.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.source.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">3.3.1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">maven.source.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">central.publishing.maven.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">0.11.0</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">central.publishing.maven.plugin.version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
<p>再在 <code>&lt;/project&gt;</code> 之前加入：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">profiles</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">profile</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">release</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">build</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">plugins</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 1) 为每个待发布 JAR 附加 Central 必需的 sources JAR --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">org.apache.maven.plugins</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">maven-source-plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">${maven.source.plugin.version}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">executions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">execution</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">attach-sources</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">goals</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">goal</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">jar-no-fork</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">goal</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">goals</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">execution</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">executions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 2) 附加 Central 必需的 Javadoc JAR；放宽 doclint，避免文档告警阻断发布 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">org.apache.maven.plugins</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">maven-javadoc-plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">${maven.javadoc.plugin.version}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">configuration</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">doclint</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">none</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">doclint</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">quiet</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">true</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">quiet</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">configuration</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">executions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">execution</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">attach-javadocs</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">goals</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">goal</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">jar</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">goal</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">goals</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">execution</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">executions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 3) verify 阶段签名 POM 及全部附件；口令来自 gpg-agent 或 MAVEN_GPG_PASSPHRASE --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">org.apache.maven.plugins</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">maven-gpg-plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">${maven.gpg.plugin.version}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">configuration</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 脚本会用 -Dgpg.executable 覆盖为 MacPorts / Homebrew 绝对路径 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">executable</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">${gpg.executable}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">executable</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">keyname</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">${gpg.keyname}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">keyname</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">bestPractices</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">true</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">bestPractices</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">configuration</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">executions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">execution</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">sign-artifacts</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">id</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">goals</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">goal</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">sign</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">goal</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">goals</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">phase</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">verify</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">phase</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">execution</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">executions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 4) 将 reactor 构件汇总为一个 Central Portal deployment；首发保留手动 Publish --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">org.sonatype.central</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">central-publishing-maven-plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">${central.publishing.maven.plugin.version}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">extensions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">true</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">extensions</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">configuration</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">publishingServerId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">central</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">publishingServerId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 首次建议 false：上传后到 Portal 手工检查再发布 --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">autoPublish</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">false</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">autoPublish</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">&lt;!-- 排除示例模块，避免被打进上传 bundle --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifacts</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">agentforge-examples</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">agentforge-service</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">agentforge-service-ui</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">agentforge-service-web</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifact</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">excludeArtifacts</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">configuration</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">plugin</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">plugins</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">build</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">profile</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">profiles</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：</p>
<ul>
<li class=""><code>extensions=true</code> 让插件接管 deploy，并把整个 reactor 打成<strong>一个 bundle</strong> 上传；</li>
<li class=""><code>excludeArtifacts</code> 按 artifactId 排除，确保 service 应用不进入 bundle；</li>
<li class=""><code>bestPractices=true</code> 禁止在 POM / XML 中传入明文 GPG 口令，发布前使用 <code>MAVEN_GPG_PASSPHRASE</code>；</li>
<li class=""><code>autoPublish=false</code>（首次）上传后需到 Portal 手动点 Publish，稳定后可改 <code>true</code> 并加 <code>&lt;waitUntil&gt;published&lt;/waitUntil&gt;</code>。</li>
</ul>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="54-版本号策略">5.4 版本号策略<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#54-%E7%89%88%E6%9C%AC%E5%8F%B7%E7%AD%96%E7%95%A5" class="hash-link" aria-label="5.4 版本号策略的直接链接" title="5.4 版本号策略的直接链接" translate="no">​</a></h2>
<ul>
<li class="">
<p>工程用 <code>${revision}</code> 管理版本，<code>flatten-maven-plugin</code> 已配置 <code>resolveCiFriendliesOnly</code>，部署前会把 <code>${revision}</code> 解析为实际版本；</p>
</li>
<li class="">
<p><strong>发布时不改文件</strong>，命令行覆盖即可：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Drevision</span><span class="token operator">=</span><span class="token number">1.0</span><span class="token plain">.0</span><br></div></code></pre></div></div>
</li>
</ul>
<blockquote>
<p><strong>弊端说明</strong>：正式版本<strong>不能</strong>是 <code>1.0.0-SNAPSHOT</code>；且 Central 版本<strong>一经发布不可覆盖</strong>，每次发布请递增版本号。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="55-本地验证第五步不签名不上传">5.5 本地验证第五步（不签名、不上传）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#55-%E6%9C%AC%E5%9C%B0%E9%AA%8C%E8%AF%81%E7%AC%AC%E4%BA%94%E6%AD%A5%E4%B8%8D%E7%AD%BE%E5%90%8D%E4%B8%8D%E4%B8%8A%E4%BC%A0" class="hash-link" aria-label="5.5 本地验证第五步（不签名、不上传）的直接链接" title="5.5 本地验证第五步（不签名、不上传）的直接链接" translate="no">​</a></h2>
<p>在仓库根目录执行。这里只选中实际要发布的 11 个模块，避免 service 应用工程下载大量无关依赖：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SETTINGS_FILE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/absolute/path/settings.xml"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">RELEASE_MODULES</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">'agentforge-ai-parent,agentforge-ai-bom,agentforge-model,agentforge-model/agentforge-model-api,agentforge-model/agentforge-model-core,agentforge-model/agentforge-model-openai,agentforge-model/agentforge-model-anthropic,agentforge-model/agentforge-model-registry,agentforge-framework,agentforge-framework/agentforge-agent-core,agentforge-framework/agentforge-harness-agent'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">mvn </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-P</span><span class="token plain"> release </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Drevision</span><span class="token operator">=</span><span class="token number">1.0</span><span class="token plain">.0 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-DskipTests</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Dgpg.skip</span><span class="token operator">=</span><span class="token plain">true </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-pl</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$RELEASE_MODULES</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  verify</span><br></div></code></pre></div></div>
<p>预期结果：日志中出现 <code>Inspecting build with total of 10 modules</code>、<code>Installing Central Publishing features</code> 和最终的 <code>BUILD SUCCESS</code>。每个 JAR 模块的 <code>target/</code> 中应同时存在：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">&lt;artifactId&gt;-1.0.0.jar</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">&lt;artifactId&gt;-1.0.0-sources.jar</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">&lt;artifactId&gt;-1.0.0-javadoc.jar</span><br></div></code></pre></div></div>
<blockquote>
<p><code>-Dgpg.skip=true</code> 只用于第五步的 POM / 构件结构验证；第六步预演和正式发布必须移除它，确保 Central 收到 <code>.asc</code> 签名。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六实战发布与一键部署">六、实战发布与一键部署<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E5%85%AD%E5%AE%9E%E6%88%98%E5%8F%91%E5%B8%83%E4%B8%8E%E4%B8%80%E9%94%AE%E9%83%A8%E7%BD%B2" class="hash-link" aria-label="六、实战发布与一键部署的直接链接" title="六、实战发布与一键部署的直接链接" translate="no">​</a></h2>
<p>本节是 AgentForge <code>1.0.0</code> 真实跑通后整理的线性 Runbook。后续发布人员不需要手工拼 Maven 参数，<strong>从 6.1 开始按顺序执行，任何一步失败都不要继续下一步</strong>。</p>
<p>一键脚本位于 <code>bin/local/release.sh</code>，说明位于 <code>bin/local/README.md</code>。脚本会自动限定 11 个公开库模块、排除 examples / service、探测 GPG 绝对路径并在 Maven 前做独立签名预检。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="61-步骤-0创建一键发布脚本">6.1 步骤 0：创建一键发布脚本<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#61-%E6%AD%A5%E9%AA%A4-0%E5%88%9B%E5%BB%BA%E4%B8%80%E9%94%AE%E5%8F%91%E5%B8%83%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="6.1 步骤 0：创建一键发布脚本的直接链接" title="6.1 步骤 0：创建一键发布脚本的直接链接" translate="no">​</a></h2>
<p>在仓库根目录创建 <code>bin/local/release.sh</code>，完整内容如下：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-euo</span><span class="token plain"> pipefail</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">readonly</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">RELEASE_MODULES</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">'agentforge-ai-parent,agentforge-ai-bom,agentforge-model,agentforge-model/agentforge-model-api,agentforge-model/agentforge-model-core,agentforge-model/agentforge-model-openai,agentforge-model/agentforge-model-anthropic,agentforge-model/agentforge-model-registry,agentforge-framework,agentforge-framework/agentforge-agent-core,agentforge-framework/agentforge-harness-agent'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">usage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token string" style="color:rgb(255, 121, 198)">'EOF'</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">Usage: bin/local/release.sh &lt;version&gt; [options]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">Options:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  --settings &lt;file&gt;  Maven settings.xml (default: $MAVEN_SETTINGS or ~/.m2/settings.xml)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  --dry-run          Build and sign the release artifacts without uploading</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  --auto-publish     Upload and publish automatically instead of waiting in Portal</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  --skip-tests       Skip tests (not recommended for a formal release)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  -h, --help         Show this help</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$#</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-gt</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> usage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'-h'</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'--help'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    usage</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">VERSION</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">shift</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SETTINGS_FILE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${MAVEN_SETTINGS</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${</span><span class="token string variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">HOME</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">/.m2/settings.xml}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">DRY_RUN</span><span class="token operator">=</span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">AUTO_PUBLISH</span><span class="token operator">=</span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SKIP_TESTS</span><span class="token operator">=</span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$#</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-gt</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        --settings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$#</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-ge</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Error: --settings requires a file path.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SETTINGS_FILE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$2</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">shift</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        --dry-run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">DRY_RUN</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">shift</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        --auto-publish</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">AUTO_PUBLISH</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">shift</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        --skip-tests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SKIP_TESTS</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">shift</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        -h</span><span class="token operator">|</span><span class="token plain">--help</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> usage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        *</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Error: unknown option: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$1</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> usage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">esac</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">=~</span><span class="token plain"> ^</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token plain">-9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">+</span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token plain">-9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">+</span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token plain">-9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">+</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">.-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">A-Za-z0-9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">+</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">*$ </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Error: invalid release version: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">!=</span><span class="token plain"> *-SNAPSHOT </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Error: a Central release cannot use -SNAPSHOT.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Error: settings.xml not found: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">command</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> mvn </span><span class="token operator">&gt;</span><span class="token plain">/dev/null </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Error: mvn was not found in PATH.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># GUI Terminal 的 PATH 可能没有 MacPorts / Homebrew，因此显式探测 GPG。</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_EXECUTABLE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${GPG_EXECUTABLE</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_EXECUTABLE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:rgb(189, 147, 249);font-style:italic">candidate</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">command</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> gpg </span><span class="token string variable operator file-descriptor important" style="color:rgb(189, 147, 249);font-style:italic">2</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">&gt;</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">/dev/null </span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">||</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable boolean" style="color:rgb(189, 147, 249);font-style:italic">true</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> /opt/local/bin/gpg /opt/homebrew/bin/gpg /usr/local/bin/gpg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$candidate</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-x</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$candidate</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_EXECUTABLE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$candidate</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">break</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_EXECUTABLE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Error: gpg was not found.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_TTY</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${GPG_TTY</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(tty)}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 口令隐藏输入，不放入命令行、POM 或 settings.xml。</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${MAVEN_GPG_PASSPHRASE</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">:-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">printf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'GPG Passphrase (input hidden): '</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">IFS</span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">read</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-r</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> MAVEN_GPG_PASSPHRASE</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">printf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'\n'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$MAVEN_GPG_PASSPHRASE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Error: GPG Passphrase cannot be empty.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> MAVEN_GPG_PASSPHRASE</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Maven 前先对临时文件签名和验签，尽早发现错误口令或 GPG 配置。</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SIGN_CHECK_FILE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">mktemp /tmp/agentforge-gpg-check.XXXXXX</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function-name function" style="color:rgb(80, 250, 123)">cleanup_sign_check</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SIGN_CHECK_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${SIGN_CHECK_FILE}</span><span class="token string" style="color:rgb(255, 121, 198)">.asc"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">trap</span><span class="token plain"> cleanup_sign_check EXIT</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">printf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'AgentForge Maven Central signing check\n'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SIGN_CHECK_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">printf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'%s\n'</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$MAVEN_GPG_PASSPHRASE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_EXECUTABLE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--batch</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--yes</span><span class="token plain"> --no-tty --pinentry-mode loopback --passphrase-fd </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--armor</span><span class="token plain"> --detach-sign </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SIGN_CHECK_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Error: GPG preflight signing failed. Enter the private-key Passphrase created with this GPG key.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'       Do not enter the Central Token, KEY_ID, fingerprint, or macOS login password.'</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token file-descriptor important">&amp;2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_EXECUTABLE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--batch</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--verify</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${SIGN_CHECK_FILE}</span><span class="token string" style="color:rgb(255, 121, 198)">.asc"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SIGN_CHECK_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">cleanup_sign_check</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">trap</span><span class="token plain"> - EXIT</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">MVN_ARGS</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-P</span><span class="token plain"> release</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"-Drevision=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${VERSION}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"-Dgpg.executable=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${GPG_EXECUTABLE}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-pl</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$RELEASE_MODULES</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SKIP_TESTS</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-eq</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">MVN_ARGS</span><span class="token operator">+=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">-DskipTests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"&gt;&gt; version=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)"> dryRun=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$DRY_RUN</span><span class="token string" style="color:rgb(255, 121, 198)"> autoPublish=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$AUTO_PUBLISH</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"&gt;&gt; settings=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"&gt;&gt; gpg=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_EXECUTABLE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$DRY_RUN</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-eq</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    mvn </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${MVN_ARGS</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">@</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> clean verify</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&gt;&gt; Dry run succeeded: artifacts were built and signed; nothing was uploaded.'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$AUTO_PUBLISH</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-eq</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">MVN_ARGS</span><span class="token operator">+=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">-Dcentral.publishing.autoPublish</span><span class="token operator">=</span><span class="token plain">true </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Dcentral.publishing.waitUntil</span><span class="token operator">=</span><span class="token plain">published</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">mvn </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${MVN_ARGS</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">[</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">@</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">]</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> clean deploy</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$AUTO_PUBLISH</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-eq</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&gt;&gt; Central deployment was uploaded and automatic publishing was requested.'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&gt;&gt; Central deployment was uploaded. Review and publish it in the Portal:'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'   https://central.sonatype.com/publishing/deployments'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><br></div></code></pre></div></div>
<p>脚本中不要填写任何 Token、Passphrase 或私钥内容。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="62-步骤-1进入仓库并设置本次发布参数">6.2 步骤 1：进入仓库并设置本次发布参数<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#62-%E6%AD%A5%E9%AA%A4-1%E8%BF%9B%E5%85%A5%E4%BB%93%E5%BA%93%E5%B9%B6%E8%AE%BE%E7%BD%AE%E6%9C%AC%E6%AC%A1%E5%8F%91%E5%B8%83%E5%8F%82%E6%95%B0" class="hash-link" aria-label="6.2 步骤 1：进入仓库并设置本次发布参数的直接链接" title="6.2 步骤 1：进入仓库并设置本次发布参数的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/absolute/path/AgentForge"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">VERSION</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">SETTINGS_FILE</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/absolute/path/settings.xml"</span><br></div></code></pre></div></div>
<p><code>VERSION</code> 必须是从未在 Central 发布过的非 SNAPSHOT 版本。<code>SETTINGS_FILE</code> 必须位于 Git 仓库外。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="63-步骤-2发布前自检">6.3 步骤 2：发布前自检<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#63-%E6%AD%A5%E9%AA%A4-2%E5%8F%91%E5%B8%83%E5%89%8D%E8%87%AA%E6%A3%80" class="hash-link" aria-label="6.3 步骤 2：发布前自检的直接链接" title="6.3 步骤 2：发布前自检的直接链接" translate="no">​</a></h2>
<p>先依次执行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> +x bin/local/release.sh</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 脚本语法与用法</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> bin/local/release.sh</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bin/local/release.sh </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--help</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># settings.xml 必须存在；macOS 可额外用 xmllint 检查 XML</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">xmllint </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--noout</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 必须能看到至少一把 sec 私钥</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_BIN</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">command</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> gpg </span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">||</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable boolean" style="color:rgb(189, 147, 249);font-style:italic">true</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-x</span><span class="token plain"> /opt/local/bin/gpg </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_BIN</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/opt/local/bin/gpg"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-x</span><span class="token plain"> /opt/homebrew/bin/gpg </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">GPG_BIN</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/opt/homebrew/bin/gpg"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$GPG_BIN</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> --list-secret-keys --keyid-format LONG</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 检查工作树；正式发布建议使用可追溯的 commit/tag</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> status </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--short</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 404 表示该版本尚未出现在 Central；200 时必须换新版本</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-sS</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> /dev/null </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-w</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'%{http_code}\n'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"https://repo1.maven.org/maven2/cloud/changlu/agentforge/agentforge-ai-parent/</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${VERSION}</span><span class="token string" style="color:rgb(255, 121, 198)">/agentforge-ai-parent-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${VERSION}</span><span class="token string" style="color:rgb(255, 121, 198)">.pom"</span><br></div></code></pre></div></div>
<p>同时在 Central Portal 确认命名空间为 <strong>Verified</strong>、<code>settings.xml</code> 中存在 <code>&lt;server id="central"&gt;</code>、GPG 公钥已上传 keyserver。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="64-步骤-3一键预演不上传">6.4 步骤 3：一键预演（不上传）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#64-%E6%AD%A5%E9%AA%A4-3%E4%B8%80%E9%94%AE%E9%A2%84%E6%BC%94%E4%B8%8D%E4%B8%8A%E4%BC%A0" class="hash-link" aria-label="6.4 步骤 3：一键预演（不上传）的直接链接" title="6.4 步骤 3：一键预演（不上传）的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">bin/local/release.sh </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--settings</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --dry-run</span><br></div></code></pre></div></div>
<p>执行时脚本会提示：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GPG Passphrase (input hidden):</span><br></div></code></pre></div></div>
<p>输入创建当前 GPG 私钥时设置的 Passphrase。输入不回显，也不会进入 shell 历史。成功时应出现：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">[INFO] BUILD SUCCESS</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">&gt;&gt; Dry run succeeded: artifacts were built and signed; nothing was uploaded.</span><br></div></code></pre></div></div>
<p>本次实战结果：10 个模块全部 <code>SUCCESS</code>，真实生成的 28 个 <code>.asc</code> 全部本地验签成功。</p>
<blockquote>
<p>经验：不要用 <code>-Dgpg.skip=true</code> 做最终预演，那只能验证 POM 和构件结构，无法证明真实签名链路可用。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="65-步骤-4一键上传首次推荐">6.5 步骤 4：一键上传（首次推荐）<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#65-%E6%AD%A5%E9%AA%A4-4%E4%B8%80%E9%94%AE%E4%B8%8A%E4%BC%A0%E9%A6%96%E6%AC%A1%E6%8E%A8%E8%8D%90" class="hash-link" aria-label="6.5 步骤 4：一键上传（首次推荐）的直接链接" title="6.5 步骤 4：一键上传（首次推荐）的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">bin/local/release.sh </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--settings</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></div></code></pre></div></div>
<p>该模式执行 <code>clean deploy</code>，但 POM 中 <code>autoPublish=false</code>，因此只上传至 Central Portal，不会立即公开。成功标志：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">[INFO] BUILD SUCCESS</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">&gt;&gt; Central deployment was uploaded. Review and publish it in the Portal:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   https://central.sonatype.com/publishing/deployments</span><br></div></code></pre></div></div>
<p>本次真实上传前的 Bundle 检查结果：</p>
<ul>
<li class="">Bundle 大小约 <code>1.8 MB</code>；</li>
<li class="">共 <code>168</code> 个条目；</li>
<li class="">正好包含 10 个公开 artifact；</li>
<li class="">不包含 examples / studio；</li>
<li class="">POM、主 JAR、sources JAR、Javadoc JAR 及对应 <code>.asc</code> 齐全。</li>
</ul>
<p><strong>示范案例：</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 打开AgentForge</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/Users/edy/changlu_workspace/mymd/我的开源项目/AgentForge"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 执行一键推送</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bin/local/release.sh </span><span class="token number">1.0</span><span class="token plain">.0 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--settings</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/Users/edy/changlu_workspace/mymd/我的开源项目/maven/settings.xml"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    --dry-run</span><br></div></code></pre></div></div>
<p>输入的<strong>Passphrase</strong>为<strong>安装 GPG自己设置的密钥</strong>～</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="66-步骤-5portal-核对与-publish">6.6 步骤 5：Portal 核对与 Publish<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#66-%E6%AD%A5%E9%AA%A4-5portal-%E6%A0%B8%E5%AF%B9%E4%B8%8E-publish" class="hash-link" aria-label="6.6 步骤 5：Portal 核对与 Publish的直接链接" title="6.6 步骤 5：Portal 核对与 Publish的直接链接" translate="no">​</a></h2>
<p>打开 <a href="https://central.sonatype.com/publishing/deployments" target="_blank" rel="noopener noreferrer" class="">https://central.sonatype.com/publishing/deployments</a>：</p>
<ol>
<li class="">
<p>确认 deployment 的版本和构件数量正确；</p>
</li>
<li class="">
<p>等待状态变为 <strong>VALIDATED</strong>；</p>
</li>
<li class="">
<p>首次发布建议人工检查后点击 <strong>Publish</strong>；</p>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610050105391.png" alt="image-20261005010505202" class="img_ev3q"></p>
<p>此时会进入到publishing过程中：</p>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610050107677.png" alt="image-20261005010707580" class="img_ev3q"></p>
</li>
<li class="">
<p>等待状态变为 <strong>PUBLISHED</strong>。</p>
</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://pictured-bed.oss-cn-beijing.aliyuncs.com/img/2024/202610050116487.png" alt="image-20261005011610344" class="img_ev3q"></p>
<blockquote>
<p>Publish 是不可逆操作。Central 上已发布的 GAV 版本不能覆盖，所以不建议首次直接使用自动 Publish。</p>
</blockquote>
<p><strong>Publish 后的等待时间：</strong></p>
<ul>
<li class="">点击 <strong>Publish</strong> 后，<code>VALIDATED</code> 通常会立即进入 <code>PUBLISHING</code>；</li>
<li class=""><code>PUBLISHING → PUBLISHED</code> 一般需要几分钟，常见约 <code>1～10</code> 分钟；</li>
<li class=""><code>PUBLISHED</code> 后同步到 <code>repo1.maven.org</code> 通常还需要几分钟；</li>
<li class="">Central Search 索引通常在发布后几分钟内更新，但可能比公共仓库更晚。</li>
</ul>
<p>建议每隔 <code>1～2</code> 分钟刷新一次 Deployments 页面，不要重复上传。状态变为 <code>PUBLISHED</code> 后，可直接检查公共仓库：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-sS</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-L</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> /dev/null </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-w</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'%{http_code}\n'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">'https://repo1.maven.org/maven2/cloud/changlu/agentforge/agentforge-agent-core/1.0.0/agentforge-agent-core-1.0.0.pom'</span><br></div></code></pre></div></div>
<p>返回 <code>200</code> 表示已公开可用；返回 <code>404</code> 则继续等待同步。如果超过 <code>30</code> 分钟仍停留在 <code>PUBLISHING</code>，先查看 <a href="https://status.maven.org/" target="_blank" rel="noopener noreferrer" class="">https://status.maven.org/</a>；超过 <code>1</code> 小时仍未完成，记录 deployment ID 并联系 <code>central-support@sonatype.com</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="67-步骤-6后续版本可选一键自动-publish">6.7 步骤 6（后续版本可选）：一键自动 Publish<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#67-%E6%AD%A5%E9%AA%A4-6%E5%90%8E%E7%BB%AD%E7%89%88%E6%9C%AC%E5%8F%AF%E9%80%89%E4%B8%80%E9%94%AE%E8%87%AA%E5%8A%A8-publish" class="hash-link" aria-label="6.7 步骤 6（后续版本可选）：一键自动 Publish的直接链接" title="6.7 步骤 6（后续版本可选）：一键自动 Publish的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">VERSION</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bin/local/release.sh </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$VERSION</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--settings</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$SETTINGS_FILE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  --auto-publish</span><br></div></code></pre></div></div>
<p><code>--auto-publish</code> 会动态覆盖 POM 的 <code>autoPublish=false</code>，并使用 <code>waitUntil=published</code> 等待 Central 完成发布。只应在首次人工发布验证成功后使用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="68-步骤-7发布后验证">6.8 步骤 7：发布后验证<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#68-%E6%AD%A5%E9%AA%A4-7%E5%8F%91%E5%B8%83%E5%90%8E%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="6.8 步骤 7：发布后验证的直接链接" title="6.8 步骤 7：发布后验证的直接链接" translate="no">​</a></h2>
<ul>
<li class="">
<p>Portal Deployments 状态为 <strong>PUBLISHED</strong>；</p>
</li>
<li class="">
<p>在 <a href="https://central.sonatype.com/" target="_blank" rel="noopener noreferrer" class="">https://central.sonatype.com/</a> 搜索完整 groupId <code>cloud.changlu.agentforge</code> 或 artifactId <code>agentforge-agent-core</code>；</p>
</li>
<li class="">
<p>直接检查 <code>repo1.maven.org/maven2/&lt;groupId 路径&gt;/&lt;artifactId&gt;/&lt;version&gt;/</code>；</p>
</li>
<li class="">
<p>在空白 Maven 项目中真实引用：</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">dependency</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">cloud.changlu.agentforge</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">agentforge-agent-core</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">1.0.0</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">dependency</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
</li>
</ul>
<p>可先用 HTTP 状态做无缓存的核心验证：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-sS</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-L</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> /dev/null </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-w</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'%{http_code}\n'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">'https://repo1.maven.org/maven2/cloud/changlu/agentforge/agentforge-agent-core/1.0.0/agentforge-agent-core-1.0.0.pom'</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>200</code>：构件已进入 Maven Central 公共仓库；</li>
<li class=""><code>404</code> 且 Portal 是 <code>VALIDATED</code>：只完成上传，还需在 Deployments 页点击 <strong>Publish</strong>；</li>
<li class="">Portal 是 <code>PUBLISHING</code>：继续等待，不要重复上传；</li>
<li class="">Portal 是 <code>PUBLISHED</code> 但仍为 <code>404</code>：公共仓库正在同步，稍后重试；</li>
<li class=""><code>repo1.maven.org</code> 已返回 <code>200</code> 但 Central Search 搜不到：只是搜索索引延迟。</li>
</ul>
<blockquote>
<p>Portal 显示 PUBLISHED 后，同步到 Central Search 和镜像仓库仍可能需要几十分钟，以 Portal 状态和 <code>repo1.maven.org</code> 为准。脚本不包含任何凭据：Central Token 仅从 <code>settings.xml</code> 读取，GPG Passphrase 仅存在于当前发布进程。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七常见问题与注意事项">七、常见问题与注意事项<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E4%B8%83%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" class="hash-link" aria-label="七、常见问题与注意事项的直接链接" title="七、常见问题与注意事项的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>问题</th><th>说明 / 处理</th></tr></thead><tbody><tr><td>命名空间校验失败</td><td>确认 TXT 记录加在<strong>根域名 <code>changlu.cloud</code></strong> 上，值完全一致，并用 <code>dig -t txt changlu.cloud</code> 验证</td></tr><tr><td><code>Invalid signature</code> / GPG 报错</td><td>确认 KEY_ID 正确、公钥已上传、<code>MAVEN_GPG_PASSPHRASE</code> 口令正确；gpg 版本 ≥ 2.2</td></tr><tr><td><code>损坏的密码</code> / <code>Bad passphrase</code></td><td>输入的必须是创建当前 GPG 私钥时设置的 Passphrase，不是 Central Token、KEY_ID、指纹或 macOS 密码；忘记后无法反查，需换新密钥</td></tr><tr><td>bundle 缺 sources/javadoc</td><td>检查 <code>-P release</code> 是否生效，javadoc 是否因源码报错中断（已加 <code>doclint=none</code>）</td></tr><tr><td>POM 缺 metadata</td><td>确认 parent 有 <code>licenses</code>/<code>developers</code>/<code>scm</code>，各模块有 <code>name</code>/<code>description</code>/<code>url</code></td></tr><tr><td>版本已存在</td><td>Central <strong>不可覆盖 / 删除</strong>已发布版本，必须换新版本号</td></tr><tr><td><code>-SNAPSHOT</code> 被拒</td><td>正式版必须非 SNAPSHOT，快照走独立通道，不混用</td></tr><tr><td>上传后搜不到</td><td>同步有延迟，先看 Portal Deployments 的 Published 状态</td></tr><tr><td>示例模块被打包</td><td>确认 <code>excludeArtifacts</code> 生效，或先 <code>--dry-run</code> 检查 bundle 内容</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八总结">八、总结<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E5%85%AB%E6%80%BB%E7%BB%93" class="hash-link" aria-label="八、总结的直接链接" title="八、总结的直接链接" translate="no">​</a></h2>
<p>发布到 Maven Central 的完整路径可以归纳为：</p>
<ol>
<li class=""><strong>选命名空间</strong>：无域名用 <code>io.github.&lt;username&gt;</code>，有域名用反向域名（本方案选 <code>cloud.changlu</code>）；</li>
<li class=""><strong>过校验</strong>：域名方式加 DNS TXT，GitHub 方式建公开仓库；</li>
<li class=""><strong>备密钥与口令</strong>：GPG 密钥对 + Portal User Token + <code>settings.xml</code>；</li>
<li class=""><strong>改构建</strong>：parent 里补元数据与 <code>release</code> profile（sources / javadoc / gpg / central）；</li>
<li class=""><strong>跑发布</strong>：<code>--dry-run</code> 预演 → 正式 <code>deploy</code> → Portal Publish → 验证依赖；</li>
<li class=""><strong>可持续</strong>：用 <code>release.sh</code> 固化流程，版本号递增，永不覆盖。</li>
</ol>
<p>至此，AgentForge 的库模块即可被全世界的 Java 开发者通过一段依赖坐标直接引入。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/%E7%95%AA%E5%A4%96/%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E4%B9%8BAgentForge%E5%8F%91%E5%B8%83%E5%88%B0MavenCentral%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E5%AE%9E%E6%88%98#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://central.sonatype.org/" target="_blank" rel="noopener noreferrer" class="">Sonatype Central Portal 官方文档</a></p>
<p>[2]. <a href="https://central.sonatype.org/publish/requirements/coordinates/" target="_blank" rel="noopener noreferrer" class="">Choosing your Coordinates（groupId 命名空间）</a></p>
<p>[3]. <a href="https://central.sonatype.org/publish/requirements/" target="_blank" rel="noopener noreferrer" class="">Requirements（sources/javadoc、GPG、POM 元数据）</a></p>
<p>[4]. <a href="https://central.sonatype.org/publish/publish-portal-maven/" target="_blank" rel="noopener noreferrer" class="">Publishing By Using the Maven Plugin</a></p>
<p>[5]. <a href="https://central.sonatype.org/publish/generate-portal-token/" target="_blank" rel="noopener noreferrer" class="">Generating a Portal Token for Publishing</a></p>
<p>[6]. <a href="https://central.sonatype.org/faq/how-to-set-txt-record/" target="_blank" rel="noopener noreferrer" class="">How do I set the TXT record needed to prove ownership of my Web Domain?</a></p>
<p>[7]. <a href="https://central.sonatype.org/publish/requirements/gpg/" target="_blank" rel="noopener noreferrer" class="">GPG Requirements</a></p>
<p>[8]. <a href="https://central.sonatype.com/artifact/org.sonatype.central/central-publishing-maven-plugin" target="_blank" rel="noopener noreferrer" class="">central-publishing-maven-plugin（Maven Central Artifact）</a></p>
<br>
<hr>
<p>整理者：长路　创建时间：2026.10.4　更新时间：2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>番外</category>
            <category>工程化</category>
        </item>
        <item>
            <title><![CDATA[MCP协议转换FunctionCall协议02、MCP框架调研与AgentForge接入实现]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/mcp/MCP协议转换FunctionCall协议02、MCP框架调研与AgentForge接入实现</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/mcp/MCP协议转换FunctionCall协议02、MCP框架调研与AgentForge接入实现</guid>
            <pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[本文按「背景/问题引入 → 核心概念 → 主流框架调研 → 实现思路 → 实战代码 → 验证测试 → 总结」递进展开，共七个部分。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>本文按「<strong>背景/问题引入 → 核心概念 → 主流框架调研 → 实现思路 → 实战代码 → 验证测试 → 总结</strong>」递进展开，共七个部分。</p>
</blockquote>
<!-- -->
<br>
<p>一句话结论：三方中<strong>只有 LangChain4j 自研协议栈，Spring AI 与 AgentScope Java 都基于官方 MCP Java SDK</strong>；而 <strong>AgentForge 选择"核心自研协议栈 + 官方 SDK 可选桥接"</strong>。下面把这一结论的来龙去脉讲清楚。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动一个真实的接入难题">1.1、场景驱动：一个真实的接入难题<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E4%B8%80%E4%B8%AA%E7%9C%9F%E5%AE%9E%E7%9A%84%E6%8E%A5%E5%85%A5%E9%9A%BE%E9%A2%98" class="hash-link" aria-label="1.1、场景驱动：一个真实的接入难题的直接链接" title="1.1、场景驱动：一个真实的接入难题的直接链接" translate="no">​</a></h2>
<p>在为 AgentForge 规划 MCP 生态接入时，我们就遇到了这样一个具体问题：</p>
<blockquote>
<p>团队希望 AgentForge 的 <code>ReActAgent</code> 能直接吃下 MCP 生态里现成的工具（filesystem、git、fetch、数据库……）。但 AgentForge 有一条硬约束——<strong>Java 8 兼容、核心零第三方依赖</strong>。而放眼 Java 生态，Spring AI 与 AgentScope 都选了官方 MCP Java SDK，那个 SDK 却要求 <strong>JDK 17+</strong>，还会带进 Jackson、Reactor、SLF4J。<br>
<!-- -->于是问题来了：<strong>我们到底该不该跟官方 SDK？</strong></p>
</blockquote>
<p>这不是 AgentForge 一家的困惑。任何"低 JDK / 零依赖"取向的框架，在接入 MCP 时都会撞上同一堵墙。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导我们要回答什么">1.2、问题引导：我们要回答什么？<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BC%E6%88%91%E4%BB%AC%E8%A6%81%E5%9B%9E%E7%AD%94%E4%BB%80%E4%B9%88" class="hash-link" aria-label="1.2、问题引导：我们要回答什么？的直接链接" title="1.2、问题引导：我们要回答什么？的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>三个主流 Java 框架，stdio 与 HTTP 支不支持？支持到什么程度？</strong><br></li>
<li class=""><strong>它们的协议是"自己手写"还是"基于官方 MCP Java SDK"？</strong><br></li>
<li class=""><strong>AgentForge 应该怎么选？自研还是官方？支持到什么程度？</strong><br></li>
<li class=""><strong>落地后，如何与 AgentForge 既有的 <code>http</code> / <code>local</code> 工具模式统一？</strong></li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13调研范围与对照基线">1.3、调研范围与对照基线<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#13%E8%B0%83%E7%A0%94%E8%8C%83%E5%9B%B4%E4%B8%8E%E5%AF%B9%E7%85%A7%E5%9F%BA%E7%BA%BF" class="hash-link" aria-label="1.3、调研范围与对照基线的直接链接" title="1.3、调研范围与对照基线的直接链接" translate="no">​</a></h2>
<p>调研对象：<strong>LangChain4j</strong>、<strong>Spring AI</strong>、<strong>AgentScope Java</strong>。对照基线是官方 <strong>MCP Java SDK</strong>。</p>
<table><thead><tr><th>项</th><th>官方 MCP Java SDK</th></tr></thead><tbody><tr><td>坐标</td><td><code>io.modelcontextprotocol.sdk:mcp</code>（BOM：<code>mcp-bom</code>）</td></tr><tr><td>当前版本</td><td><strong>2.0.1</strong></td></tr><tr><td>JDK</td><td><strong>17+</strong></td></tr><tr><td>编程模型</td><td>同步 + 异步（Reactor）</td></tr><tr><td>JSON / 日志</td><td>Jackson（可插拔）/ SLF4J</td></tr><tr><td>Client 传输</td><td><strong>STDIO、Streamable HTTP、SSE（legacy）</strong></td></tr><tr><td>Server 传输</td><td><strong>STDIO、Streamable-HTTP、Stateless Streamable-HTTP、SSE</strong></td></tr><tr><td>架构分层</td><td>Client/Server → Session → Transport 三层</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念">二、核心概念<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="二、核心概念的直接链接" title="二、核心概念的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21两条技术路线官方-sdk-vs-自研">2.1、两条技术路线：官方 SDK vs 自研<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#21%E4%B8%A4%E6%9D%A1%E6%8A%80%E6%9C%AF%E8%B7%AF%E7%BA%BF%E5%AE%98%E6%96%B9-sdk-vs-%E8%87%AA%E7%A0%94" class="hash-link" aria-label="2.1、两条技术路线：官方 SDK vs 自研的直接链接" title="2.1、两条技术路线：官方 SDK vs 自研的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="211路线一基于官方-java-sdk">2.1.1、路线一：基于官方 Java SDK<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#211%E8%B7%AF%E7%BA%BF%E4%B8%80%E5%9F%BA%E4%BA%8E%E5%AE%98%E6%96%B9-java-sdk" class="hash-link" aria-label="2.1.1、路线一：基于官方 Java SDK的直接链接" title="2.1.1、路线一：基于官方 Java SDK的直接链接" translate="no">​</a></h3>
<p>协议细节（JSON-RPC、版本协商、传输分帧）由官方维护，框架只做<strong>薄封装 + 生态整合</strong>。</p>
<ul>
<li class=""><strong>优点</strong>：省心、易过一致性测试、能力齐全、规范跟进快。</li>
<li class=""><strong>弊端说明</strong>：JDK 17+、引入 Jackson / Reactor / SLF4J，难以嵌入低 JDK / 零依赖项目。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="212路线二自研协议栈">2.1.2、路线二：自研协议栈<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#212%E8%B7%AF%E7%BA%BF%E4%BA%8C%E8%87%AA%E7%A0%94%E5%8D%8F%E8%AE%AE%E6%A0%88" class="hash-link" aria-label="2.1.2、路线二：自研协议栈的直接链接" title="2.1.2、路线二：自研协议栈的直接链接" translate="no">​</a></h3>
<p>框架自己实现 <code>McpTransport</code> / <code>McpClient</code> / JSON-RPC 路由。</p>
<ul>
<li class=""><strong>优点</strong>：完全掌控依赖与 API，可兼容更低 JDK、零额外依赖。</li>
<li class=""><strong>弊端说明</strong>：需自行跟进规范修订、做双时代兼容与一致性验证。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：这两条路线没有绝对优劣，只取决于框架自身的<strong>约束取向</strong>——这正是三方分野、也是 AgentForge 决策的根因。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22官方-sdk-的三层架构对照">2.2、官方 SDK 的三层架构（对照）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#22%E5%AE%98%E6%96%B9-sdk-%E7%9A%84%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%E5%AF%B9%E7%85%A7" class="hash-link" aria-label="2.2、官方 SDK 的三层架构（对照）的直接链接" title="2.2、官方 SDK 的三层架构（对照）的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Client/Server 层   协议操作（listTools / callTool / initialize）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Session 层         通信模式与连接状态</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Transport 层        JSON-RPC 收发与序列化（STDIO / HTTP / SSE）</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="23三方速览">2.3、三方速览<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#23%E4%B8%89%E6%96%B9%E9%80%9F%E8%A7%88" class="hash-link" aria-label="2.3、三方速览的直接链接" title="2.3、三方速览的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>框架</th><th>stdio</th><th>HTTP</th><th>实现方式</th><th>官方 SDK</th></tr></thead><tbody><tr><td><strong>LangChain4j</strong></td><td>✅</td><td>✅ Streamable HTTP（+ WebSocket / Docker）</td><td><strong>自研协议栈</strong></td><td>❌ 不使用</td></tr><tr><td><strong>Spring AI</strong></td><td>✅</td><td>✅ Streamable / Stateless / SSE（WebMVC / WebFlux）</td><td><strong>基于官方 SDK</strong></td><td>✅ 依赖 1.0+</td></tr><tr><td><strong>AgentScope Java</strong></td><td>✅</td><td>✅ Streamable HTTP / SSE</td><td><strong>基于官方 SDK</strong></td><td>✅ 依赖</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三主流框架调研">三、主流框架调研<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E4%B8%89%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94" class="hash-link" aria-label="三、主流框架调研的直接链接" title="三、主流框架调研的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31langchain4j自研协议栈">3.1、LangChain4j：自研协议栈<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#31langchain4j%E8%87%AA%E7%A0%94%E5%8D%8F%E8%AE%AE%E6%A0%88" class="hash-link" aria-label="3.1、LangChain4j：自研协议栈的直接链接" title="3.1、LangChain4j：自研协议栈的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="311它到底用没用官方-sdk">3.1.1、它到底用没用官方 SDK？<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#311%E5%AE%83%E5%88%B0%E5%BA%95%E7%94%A8%E6%B2%A1%E7%94%A8%E5%AE%98%E6%96%B9-sdk" class="hash-link" aria-label="3.1.1、它到底用没用官方 SDK？的直接链接" title="3.1.1、它到底用没用官方 SDK？的直接链接" translate="no">​</a></h3>
<p>MCP 能力在 <code>dev.langchain4j:langchain4j-mcp</code> 模块。查其 <code>pom.xml</code> 依赖：<code>langchain4j</code>、<code>langchain4j-core</code>、<code>jackson-databind</code>、<code>langchain4j-http-client(-jdk)</code>、<code>langchain4j-open-ai</code>。<strong>没有 <code>io.modelcontextprotocol.sdk</code></strong>——即协议是自己实现的。</p>
<blockquote>
<p><strong>重点</strong>：它是三方中<strong>唯一自研</strong>的（自有 <code>McpTransport</code> / <code>DefaultMcpClient</code> / <code>McpOperationHandler</code>）。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="312stdio-支持">3.1.2、stdio 支持<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#312stdio-%E6%94%AF%E6%8C%81" class="hash-link" aria-label="3.1.2、stdio 支持的直接链接" title="3.1.2、stdio 支持的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>StdioMcpTransport</code>：<code>ProcessBuilder</code> 起子进程，按行读写 JSON-RPC。</li>
<li class="">独立模块 <code>langchain4j-mcp-docker</code> 提供 <code>DockerMcpTransport</code>（容器形态的 stdio server）。</li>
<li class="">Server 侧（构建 stdio server）放在 <strong>LangChain4j Community</strong>。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="313http-支持">3.1.3、HTTP 支持<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#313http-%E6%94%AF%E6%8C%81" class="hash-link" aria-label="3.1.3、HTTP 支持的直接链接" title="3.1.3、HTTP 支持的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>StreamableHttpMcpTransport</code>：基于 JDK <code>HttpClient</code>，POST 单一 endpoint；可选 <code>subsidiaryChannel(true)</code> 打开 GET SSE 支流（legacy）。</li>
<li class=""><code>HttpMcpTransport</code>：旧 HTTP+SSE，已 <code>@Deprecated(forRemoval)</code>。</li>
<li class=""><code>WebSocketMcpTransport</code>：非标准额外传输，兼容 Quarkus MCP Server 扩展。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="314协议版本与工具治理">3.1.4、协议版本与工具治理<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#314%E5%8D%8F%E8%AE%AE%E7%89%88%E6%9C%AC%E4%B8%8E%E5%B7%A5%E5%85%B7%E6%B2%BB%E7%90%86" class="hash-link" aria-label="3.1.4、协议版本与工具治理的直接链接" title="3.1.4、协议版本与工具治理的直接链接" translate="no">​</a></h3>
<ul>
<li class="">同时支持 <strong>legacy（2025-11-25）与 modern（2026-07-28）</strong>，默认自动探测（<code>server/discover</code> → 失败回退 <code>initialize</code>）。</li>
<li class="">工具能力：<code>McpToolProvider</code> 支持按名过滤、<code>toolNameMapper</code>、<code>toolSpecificationMapper</code>、多 client 聚合；<code>DefaultMcpClient</code> 自带工具列表缓存；支持 <code>x-mcp-header</code>、<code>_meta</code> 注入、资源订阅。</li>
<li class="">另有 <strong>MCP Registry</strong> 只读客户端。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32spring-ai基于官方-java-sdk">3.2、Spring AI：基于官方 Java SDK<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#32spring-ai%E5%9F%BA%E4%BA%8E%E5%AE%98%E6%96%B9-java-sdk" class="hash-link" aria-label="3.2、Spring AI：基于官方 Java SDK的直接链接" title="3.2、Spring AI：基于官方 Java SDK的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="321它到底用没用官方-sdk">3.2.1、它到底用没用官方 SDK？<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#321%E5%AE%83%E5%88%B0%E5%BA%95%E7%94%A8%E6%B2%A1%E7%94%A8%E5%AE%98%E6%96%B9-sdk" class="hash-link" aria-label="3.2.1、它到底用没用官方 SDK？的直接链接" title="3.2.1、它到底用没用官方 SDK？的直接链接" translate="no">​</a></h3>
<p>Spring AI MCP <strong>明确采用官方 MCP Java SDK</strong>，其文档直接给出 SDK 的三层架构。自 <strong>Spring AI 2.0</strong> 起要求官方 SDK <strong>1.0.0+</strong>。</p>
<blockquote>
<p><strong>注意（迁移点）</strong>：<code>mcp-spring-webflux</code> / <code>mcp-spring-webmvc</code> 从 <code>io.modelcontextprotocol.sdk</code> <strong>迁到了</strong> <code>org.springframework.ai</code>；相关传输类包名也从 <code>io.modelcontextprotocol.*</code> 变为 <code>org.springframework.ai.mcp.*</code>。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="322stdio-支持">3.2.2、stdio 支持<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#322stdio-%E6%94%AF%E6%8C%81" class="hash-link" aria-label="3.2.2、stdio 支持的直接链接" title="3.2.2、stdio 支持的直接链接" translate="no">​</a></h3>
<ul>
<li class="">Client：<code>spring-ai-starter-mcp-client</code> 提供 STDIO。</li>
<li class="">Server：<code>spring-ai-starter-mcp-server</code>（<code>spring.ai.mcp.server.stdio=true</code>）。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="323http-支持">3.2.3、HTTP 支持<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#323http-%E6%94%AF%E6%8C%81" class="hash-link" aria-label="3.2.3、HTTP 支持的直接链接" title="3.2.3、HTTP 支持的直接链接" translate="no">​</a></h3>
<ul>
<li class="">Client：<code>spring-ai-starter-mcp-client</code>（Servlet 版 Streamable-HTTP / Stateless / SSE）、<code>spring-ai-starter-mcp-client-webflux</code>（WebFlux 版）。</li>
<li class="">Server：<code>spring-ai-starter-mcp-server-webmvc</code> / <code>-webflux</code>，用 <code>spring.ai.mcp.server.protocol = SSE | STREAMABLE | STATELESS</code> 切换。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="324注解与治理">3.2.4、注解与治理<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#324%E6%B3%A8%E8%A7%A3%E4%B8%8E%E6%B2%BB%E7%90%86" class="hash-link" aria-label="3.2.4、注解与治理的直接链接" title="3.2.4、注解与治理的直接链接" translate="no">​</a></h3>
<ul>
<li class="">Server 注解：<code>@McpTool</code>、<code>@McpResource</code>、<code>@McpPrompt</code>、<code>@McpComplete</code>。</li>
<li class="">Client 注解：<code>@McpLogging</code>、<code>@McpSampling</code>、<code>@McpElicitation</code>、<code>@McpProgress</code>。</li>
<li class="">工具适配：<code>SyncMcpToolCallback</code> / <code>AsyncMcpToolCallback</code>、<code>SyncMcpToolCallbackProvider</code>、<code>McpToolUtils</code>。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：Spring AI 自己不实现协议，而是<strong>薄封装官方 SDK + 补齐 Spring Boot 体验</strong>（Starter / 注解 / WebMVC·WebFlux 传输）。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33agentscope-java基于官方-java-sdk">3.3、AgentScope Java：基于官方 Java SDK<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#33agentscope-java%E5%9F%BA%E4%BA%8E%E5%AE%98%E6%96%B9-java-sdk" class="hash-link" aria-label="3.3、AgentScope Java：基于官方 Java SDK的直接链接" title="3.3、AgentScope Java：基于官方 Java SDK的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="331它到底用没用官方-sdk">3.3.1、它到底用没用官方 SDK？<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#331%E5%AE%83%E5%88%B0%E5%BA%95%E7%94%A8%E6%B2%A1%E7%94%A8%E5%AE%98%E6%96%B9-sdk" class="hash-link" aria-label="3.3.1、它到底用没用官方 SDK？的直接链接" title="3.3.1、它到底用没用官方 SDK？的直接链接" translate="no">​</a></h3>
<p>AgentScope Java 的 <code>McpClientBuilder</code> 内部<strong>复用官方 <code>io.modelcontextprotocol</code> SDK 的传输与客户端类</strong>，最终包装成 <code>McpAsyncClient</code> / <code>McpSyncClient</code>，并注入 AgentScope 的版本与 User-Agent。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="332stdio-支持">3.3.2、stdio 支持<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#332stdio-%E6%94%AF%E6%8C%81" class="hash-link" aria-label="3.3.2、stdio 支持的直接链接" title="3.3.2、stdio 支持的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">McpClientWrapper</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpClientBuilder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">create</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"filesystem-mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stdioTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"npx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"-y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@modelcontextprotocol/server-filesystem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/tmp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">block</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="333http-支持">3.3.3、HTTP 支持<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#333http-%E6%94%AF%E6%8C%81" class="hash-link" aria-label="3.3.3、HTTP 支持的直接链接" title="3.3.3、HTTP 支持的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>sseTransport(url)</code>：SSE（有状态流式）。</li>
<li class=""><code>streamableHttpTransport(url)</code>：Streamable HTTP（无状态流式）。</li>
<li class="">支持 <code>.header(...)</code> / <code>.queryParam(...)</code>、<code>.timeout(...)</code>、<code>.initializationTimeout(...)</code>、<code>.protocolVersions(...)</code>、<code>elicitation</code> 回调。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="334工具治理">3.3.4、工具治理<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#334%E5%B7%A5%E5%85%B7%E6%B2%BB%E7%90%86" class="hash-link" aria-label="3.3.4、工具治理的直接链接" title="3.3.4、工具治理的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>Toolkit.registerMcpClient(...)</code> 统一注册；<code>enableTools</code> / <code>disableTools</code> 白黑名单；<code>group(...)</code> 工具分组；<code>removeMcpClient(...)</code> 动态摘除。</li>
<li class="">命名空间惯例：<code>mcp__{server}__{tool}</code>。</li>
<li class="">另有 Higress AI 网关扩展（语义化工具检索）。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：默认只声明 <code>2024-11-05</code>，连较新 Server 需显式 <code>protocolVersions(...)</code>，否则报 "Unsupported protocol version"。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="34横向对比">3.4、横向对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#34%E6%A8%AA%E5%90%91%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="3.4、横向对比的直接链接" title="3.4、横向对比的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="341stdio--http-支持矩阵">3.4.1、stdio / HTTP 支持矩阵<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#341stdio--http-%E6%94%AF%E6%8C%81%E7%9F%A9%E9%98%B5" class="hash-link" aria-label="3.4.1、stdio / HTTP 支持矩阵的直接链接" title="3.4.1、stdio / HTTP 支持矩阵的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>能力</th><th>LangChain4j</th><th>Spring AI</th><th>AgentScope Java</th></tr></thead><tbody><tr><td>Client stdio</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Client Streamable HTTP</td><td>✅</td><td>✅（Servlet / WebFlux）</td><td>✅</td></tr><tr><td>Client SSE（legacy）</td><td>✅（弃用中）</td><td>✅</td><td>✅</td></tr><tr><td>Server stdio</td><td>⚠️（Community）</td><td>✅</td><td>—（偏客户端）</td></tr><tr><td>Server Streamable HTTP</td><td>⚠️（Community）</td><td>✅（WebMVC / WebFlux / Stateless）</td><td>—</td></tr><tr><td>额外传输</td><td>WebSocket / Docker</td><td>—</td><td>—</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="342实现方式与约束">3.4.2、实现方式与约束<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#342%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E4%B8%8E%E7%BA%A6%E6%9D%9F" class="hash-link" aria-label="3.4.2、实现方式与约束的直接链接" title="3.4.2、实现方式与约束的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th>LangChain4j</th><th>Spring AI</th><th>AgentScope Java</th></tr></thead><tbody><tr><td>协议实现</td><td><strong>自研</strong></td><td>官方 SDK</td><td>官方 SDK</td></tr><tr><td>官方 SDK 版本</td><td>不依赖</td><td>1.0.0+</td><td>依赖 <code>io.modelcontextprotocol</code></td></tr><tr><td>JDK/依赖约束</td><td>跟随 LangChain4j</td><td>Spring + SDK（JDK17+）</td><td>SDK（JDK17+）</td></tr><tr><td>协议版本</td><td>legacy + modern 自动探测</td><td>跟随 SDK</td><td>需显式声明版本</td></tr><tr><td>工具治理</td><td>过滤 / 改名 / 资源当工具</td><td>Starter + 注解</td><td>过滤 / 分组 / 网关检索</td></tr><tr><td>Registry</td><td>✅ 只读客户端</td><td>部分</td><td>—</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="35对比分析与选型建议">3.5、对比分析与选型建议<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#35%E5%AF%B9%E6%AF%94%E5%88%86%E6%9E%90%E4%B8%8E%E9%80%89%E5%9E%8B%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="3.5、对比分析与选型建议的直接链接" title="3.5、对比分析与选型建议的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="351三种选型场景">3.5.1、三种选型场景<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#351%E4%B8%89%E7%A7%8D%E9%80%89%E5%9E%8B%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="3.5.1、三种选型场景的直接链接" title="3.5.1、三种选型场景的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>方案一：已是 Spring Boot 应用</strong> → Spring AI。官方 SDK + Starter 最省事，但被 Spring 生态绑定。</li>
<li class=""><strong>方案二：多 Agent 编排 / 要工具治理与网关</strong> → AgentScope Java。工具分组、命名空间、Higress 网关检索最完整。</li>
<li class=""><strong>方案三：已用 LangChain4j 或要脱离官方 SDK 约束</strong> → LangChain4j。自研协议栈、依赖可控。</li>
</ul>
<blockquote>
<p><strong>弊端说明</strong>：三者都直接或间接<strong>要求 JDK 17+ 并引入第三方库</strong>。凡是"低 JDK / 零依赖"的项目，三条都不满足——只能自研。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="352小结">3.5.2、小结<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#352%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="3.5.2、小结的直接链接" title="3.5.2、小结的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>传输层面</strong>：三者 stdio 与 HTTP（Streamable HTTP）都支持。</li>
<li class=""><strong>实现层面</strong>：<strong>LangChain4j 自研，Spring AI 与 AgentScope 基于官方 SDK</strong>。</li>
<li class=""><strong>推论</strong>：官方 SDK 是事实标准，但它把 JDK 门槛抬到 17——这正是 AgentForge 需要单独决策的地方。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四agentforge-实现思路">四、AgentForge 实现思路<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E5%9B%9Bagentforge-%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="四、AgentForge 实现思路的直接链接" title="四、AgentForge 实现思路的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41agentforge-现状与约束">4.1、AgentForge 现状与约束<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#41agentforge-%E7%8E%B0%E7%8A%B6%E4%B8%8E%E7%BA%A6%E6%9D%9F" class="hash-link" aria-label="4.1、AgentForge 现状与约束的直接链接" title="4.1、AgentForge 现状与约束的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="411硬约束">4.1.1、硬约束<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#411%E7%A1%AC%E7%BA%A6%E6%9D%9F" class="hash-link" aria-label="4.1.1、硬约束的直接链接" title="4.1.1、硬约束的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>约束</th><th>说明</th></tr></thead><tbody><tr><td>JDK</td><td><code>maven.compiler.source/target = 8</code>（API 面向 Java 8）</td></tr><tr><td>依赖</td><td>核心模块<strong>零第三方依赖</strong>：无 Jackson / OkHttp / SLF4J / Lombok / Reactor</td></tr><tr><td>JSON</td><td>内置 <code>cloud.changlu.agentforge.model.internal.json.Json</code></td></tr><tr><td>HTTP</td><td>内置 <code>HttpTransport</code> / <code>HttpRequest</code> / <code>HttpResponse</code> / <code>JdkHttpTransport</code></td></tr><tr><td>日志</td><td><code>java.util.logging</code></td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：官方 MCP Java SDK 要求 <strong>JDK 17+</strong> 并引入 <strong>Jackson + Reactor + SLF4J</strong>，与 AgentForge 核心约束<strong>直接冲突</strong>。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="412可复用的工具抽象">4.1.2、可复用的工具抽象<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#412%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9A%84%E5%B7%A5%E5%85%B7%E6%8A%BD%E8%B1%A1" class="hash-link" aria-label="4.1.2、可复用的工具抽象的直接链接" title="4.1.2、可复用的工具抽象的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>已有类型</th><th>作用</th><th>与 MCP 的对应</th></tr></thead><tbody><tr><td><code>ToolExecutor</code>（<code>execute</code> / <code>executeWithResult</code>）</td><td>工具执行接口</td><td><code>tools/call</code></td></tr><tr><td><code>ToolExecutionRequest</code></td><td><code>name</code> + <code>arguments</code>(JSON)</td><td><code>tools/call</code> 入参</td></tr><tr><td><code>ToolExecutionResult</code></td><td><code>text</code> / <code>result</code> / <code>isError</code></td><td><code>content</code> / <code>structuredContent</code> / <code>isError</code></td></tr><tr><td><code>ToolSpecification</code></td><td>工具声明</td><td><code>tools/list</code> 的 tool</td></tr><tr><td><code>ToolParameters</code></td><td>原始 JSON Schema（Map）</td><td><code>inputSchema</code></td></tr><tr><td><code>ToolService</code></td><td>工具注册与循环</td><td>工具表</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：MCP 与 AgentForge 工具抽象<strong>天然同构</strong>，接入落点就是 <code>ToolExecutor</code> 的实现。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="413既有模式规范">4.1.3、既有模式规范<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#413%E6%97%A2%E6%9C%89%E6%A8%A1%E5%BC%8F%E8%A7%84%E8%8C%83" class="hash-link" aria-label="4.1.3、既有模式规范的直接链接" title="4.1.3、既有模式规范的直接链接" translate="no">​</a></h3>
<p><code>agent-core</code> 已有两种工具模式，遵循同一套「工厂 + 执行器 + 子包」：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">tool/local/   LocalToolFactory + LocalToolExecutor + support/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">tool/http/    HttpToolFactory  + HttpToolExecutor  + domain/ enums/ support/ parser/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">tool/mcp/     （本文新增，对齐上述规范）</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42实现思路官方-sdk-vs-自研">4.2、实现思路：官方 SDK vs 自研<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#42%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF%E5%AE%98%E6%96%B9-sdk-vs-%E8%87%AA%E7%A0%94" class="hash-link" aria-label="4.2、实现思路：官方 SDK vs 自研的直接链接" title="4.2、实现思路：官方 SDK vs 自研的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="421方案对比">4.2.1、方案对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#421%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="4.2.1、方案对比的直接链接" title="4.2.1、方案对比的直接链接" translate="no">​</a></h3>
<p><strong>方案一：基于官方 MCP Java SDK</strong></p>
<ul>
<li class="">优点：协议官方维护、易过一致性测试、能力齐全。</li>
<li class="">弊端说明：<strong>JDK 17+</strong>、引入 Jackson / Reactor / SLF4J，破坏核心零依赖；需额外桥接层适配 <code>ToolExecutor</code>。</li>
</ul>
<p><strong>方案二：自研协议栈</strong></p>
<ul>
<li class="">优点：<strong>JDK 8 兼容、零额外依赖</strong>、与 <code>ToolExecutor</code> 无缝、复用内置 <code>Json</code> 与 <code>HttpTransport</code>。</li>
<li class="">弊端说明：需自行跟进规范修订、做双时代兼容与一致性验证。</li>
</ul>
<p><strong>方案三：混合（核心自研 + 官方桥接）</strong></p>
<ul>
<li class="">核心（<code>agentforge-agent-core</code>）自研协议栈，保持零依赖（<strong>已落地</strong>）；</li>
<li class="">另设<strong>可选模块</strong> <code>agentforge-mcp-sdk-bridge</code>（JDK 17），把官方 SDK 的 <code>McpClient</code> 包装成 AgentForge 的 <code>ToolExecutor</code>（<strong>P3 规划，尚未实现</strong>）。</li>
</ul>
<blockquote>
<p><strong>结论</strong>：目标形态采用 <strong>方案三</strong>——主路径自研先行落地，官方 SDK 作为可选桥接后续补齐，既守住核心约束，又不把用户锁死在自研协议栈上。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="422决策依据">4.2.2、决策依据<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#422%E5%86%B3%E7%AD%96%E4%BE%9D%E6%8D%AE" class="hash-link" aria-label="4.2.2、决策依据的直接链接" title="4.2.2、决策依据的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th>自研</th><th>官方 SDK</th></tr></thead><tbody><tr><td>JDK</td><td>8</td><td>17+</td></tr><tr><td>依赖</td><td>无</td><td>Jackson/Reactor/SLF4J</td></tr><tr><td>与 <code>ToolExecutor</code></td><td>直接实现</td><td>需桥接</td></tr><tr><td>规范跟进</td><td>自己跟</td><td>官方跟</td></tr><tr><td>一致性测试</td><td>自己做</td><td>官方做</td></tr><tr><td><strong>AgentForge 适配度</strong></td><td><strong>高</strong></td><td>低（需隔离模块）</td></tr></tbody></table>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="423桥接如何解决-jdk-17-冲突">4.2.3、桥接如何解决 JDK 17 冲突<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#423%E6%A1%A5%E6%8E%A5%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3-jdk-17-%E5%86%B2%E7%AA%81" class="hash-link" aria-label="4.2.3、桥接如何解决 JDK 17 冲突的直接链接" title="4.2.3、桥接如何解决 JDK 17 冲突的直接链接" translate="no">​</a></h3>
<p><strong>疑问</strong>：官方 SDK 要求 JDK 17，那设一个"官方桥接"模块，岂不是把 JDK 17 又带回来了？JDK 8 到底还能不能用 MCP？</p>
<p><strong>先厘清</strong>：MCP 是 JSON-RPC 协议，<strong>与 JDK 无关</strong>；JDK 17 是"官方 Java SDK 这个实现"的门槛，不是 MCP 的门槛。JDK 8 完全能实现 MCP——<code>ProcessBuilder</code>（stdio）+ <code>HttpURLConnection</code>（HTTP）+ 内置 <code>Json</code> 即可，无需 Jackson / Reactor。</p>
<p><strong>关键原则：依赖单向、字节码向上兼容。</strong> JDK 17 编译的模块可以依赖 Java 8 编译的模块，反之不行。所以让 <strong>bridge 依赖 core</strong>，而不是 core 依赖 bridge：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-agent-core（release 8 / 零依赖）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ▲  定义 ToolExecutor / McpClient / ToolSpecification</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │  ← 只被依赖，不含任何 MCP 实现</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-mcp-sdk-bridge（release 17）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   依赖 core + 官方 SDK + Jackson / Reactor / SLF4J</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   把官方 McpClient 包装成 core 的 ToolExecutor</span><br></div></code></pre></div></div>
<p>于是 JDK 17 类型、Jackson、Reactor、SLF4J 全被关在 bridge 内部，<strong>永远不会出现在 core 的编译或运行路径上</strong>。</p>
<br>
<p><strong>JDK 版本 × 实现路径</strong></p>
<table><thead><tr><th>运行环境</th><th>自研协议栈（core 内置）</th><th>官方 SDK 桥接（独立模块）</th></tr></thead><tbody><tr><td>JDK 8</td><td>✅ 唯一选择</td><td>❌ 用不了（SDK 需 17）</td></tr><tr><td>JDK 17+</td><td>✅ 仍可用（Java 8 字节码向上兼容，零依赖优势保留）</td><td>✅ 可选，显式引入才生效</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：自研栈<strong>不是 JDK 8 专用</strong>，它在 JDK 17 / 21 上同样运行；官方桥接是"opt-in"，<strong>不是按 JDK 版本自动切换</strong>。两条路是<strong>部署期二选一</strong>，由用户决定。</p>
</blockquote>
<br>
<p><strong>隔离手段</strong></p>
<ul>
<li class=""><strong>构建隔离</strong>：多模块工程，core 用 <code>maven.compiler.release=8</code>，bridge 用 <code>17</code>；<strong>core 的 POM 绝不声明 bridge</strong>，故 Jackson / Reactor / SLF4J 进不了 core 的依赖树。</li>
<li class=""><strong>运行隔离</strong>：bridge 是独立 artifact，JDK 8 用户不引入即永不加载，规避 <code>UnsupportedClassVersionError</code>。</li>
<li class=""><strong>接口隔离</strong>：core 只暴露 Java 8 的 <code>ToolExecutor</code> / <code>McpClient</code>，bridge 内部兼容官方类型，向上只实现 core 接口。</li>
<li class=""><strong>可选懒加载</strong>：用 Java 8 自带的 <code>java.util.ServiceLoader</code> 定义 <code>McpClientFactory</code> SPI——有 bridge 则发现，无则回落到自研 <code>DefaultMcpClient</code>，core 始终零依赖。</li>
</ul>
<blockquote>
<p><strong>结论</strong>：桥接并非"在 Java 8 上跑 JDK 17 SDK"，而是给"愿意升 JDK 17 且想要官方能力"的用户开的一扇<strong>可选、受隔离</strong>的门；不升 JDK 17 的用户完全走自研栈，两条路各自干净。</p>
</blockquote>
<blockquote>
<p><strong>当前进度</strong>：自研协议栈已实现；<code>agentforge-mcp-sdk-bridge</code> 与 <code>McpClientFactory</code> SPI 均为 <strong>P3 规划，尚未落地</strong>。</p>
</blockquote>
<br>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="43总体架构与分包">4.3、总体架构与分包<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#43%E6%80%BB%E4%BD%93%E6%9E%B6%E6%9E%84%E4%B8%8E%E5%88%86%E5%8C%85" class="hash-link" aria-label="4.3、总体架构与分包的直接链接" title="4.3、总体架构与分包的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="431目录结构">4.3.1、目录结构<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#431%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84" class="hash-link" aria-label="4.3.1、目录结构的直接链接" title="4.3.1、目录结构的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-agent-core/src/main/java/cloud/changlu/agentforge/agent/tool/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── mcp/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── McpToolFactory.java         # 构建入口：listTools → Map&lt;ToolSpecification, ToolExecutor&gt;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── McpToolExecutor.java        # implements ToolExecutor：tools/call</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── McpClient.java              # 客户端接口：serverAlias / listTools / callTool / close</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── DefaultMcpClient.java       # JSON-RPC 生命周期（initialize 握手、惰性初始化）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── McpProtocolException.java   # JSON-RPC error → 异常</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── McpTransportException.java  # 传输失败异常</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── domain/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── McpTool.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── McpCallToolResult.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── McpContent.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── McpServerInfo.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── McpCapabilities.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── transport/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── McpTransport.java       # send / request / close 的 SPI</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── StdioMcpTransport.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── StreamableHttpMcpTransport.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── support/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── JsonRpcCodec.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── McpJson.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── McpToolSpecificationMapper.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       └── McpResultConverter.java</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="432分层职责">4.3.2、分层职责<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#432%E5%88%86%E5%B1%82%E8%81%8C%E8%B4%A3" class="hash-link" aria-label="4.3.2、分层职责的直接链接" title="4.3.2、分层职责的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│ McpToolFactory / McpToolExecutor              │  对接 Agent 工具表</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├──────────────────────────────────────────────┤</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│ DefaultMcpClient（id 递增、握手、惰性初始化）  │  协议层</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├──────────────────────────────────────────────┤</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│ McpTransport（stdio / Streamable HTTP）        │  传输层</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="44核心设计与映射">4.4、核心设计与映射<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#44%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E4%B8%8E%E6%98%A0%E5%B0%84" class="hash-link" aria-label="4.4、核心设计与映射的直接链接" title="4.4、核心设计与映射的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="441mcptoolexecutor-implements-toolexecutor">4.4.1、<code>McpToolExecutor implements ToolExecutor</code><a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#441mcptoolexecutor-implements-toolexecutor" class="hash-link" aria-label="441mcptoolexecutor-implements-toolexecutor的直接链接" title="441mcptoolexecutor-implements-toolexecutor的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">McpToolExecutor</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">implements</span><span class="token plain"> </span><span class="token class-name">ToolExecutor</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">McpClient</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 构造期绑定：这个工具属于哪个 MCP Server</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> remoteToolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 构造期绑定：Server 上的原始工具名（不带 alias 前缀）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">McpToolExecutor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpClient</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> remoteToolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">client </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">remoteToolName </span><span class="token operator">=</span><span class="token plain"> remoteToolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">/** 简化接口：只回文本。 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤①：模型给的 arguments 是 JSON 字符串 → 还原为 Map</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> args </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpJson</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">argumentsAsMap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤②：用绑定的原始远程名发 tools/call，并把 content 拍成文本</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">McpResultConverter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toText</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">callTool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">remoteToolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">/** 结构化接口：额外携带 structuredContent 与 isError。 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">executeWithResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤①：JSON 字符串 → Map（嵌套对象/数组保持原样）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> args </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpJson</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">argumentsAsMap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤②：定位"发往哪个 Server、调哪个工具"——由构造期绑定的两个字段决定</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">McpCallToolResult</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">callTool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">remoteToolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤③：无损映射回 AgentForge 结果</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                    </span><span class="token comment" style="color:rgb(98, 114, 164)">// isError 原样透传</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">structuredContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">           </span><span class="token comment" style="color:rgb(98, 114, 164)">// structuredContent → result</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpResultConverter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toText</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">// content 文本 → text（回灌模型）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpProtocolException</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">// JSON-RPC error（如 -32601）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">failure</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"MCP protocol error: "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpTransportException</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">                       </span><span class="token comment" style="color:rgb(98, 114, 164)">// 传输失败 / 超时</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">failure</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"MCP transport error: "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="442mcptoolfactorylist--spec--executor">4.4.2、<code>McpToolFactory</code>：List → Spec + Executor<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#442mcptoolfactorylist--spec--executor" class="hash-link" aria-label="442mcptoolfactorylist--spec--executor的直接链接" title="442mcptoolfactorylist--spec--executor的直接链接" translate="no">​</a></h3>
<p><strong>核心步骤</strong>：① 解析 alias（显式优先，否则 <code>client.serverAlias()</code>）→ ② 拉取原生工具（<code>tools/list</code>）→ ③ 白/黑名单过滤 → ④ 拼 alias 生成模型可见名 → ⑤ 绑定 <code>McpToolExecutor(client, 原始名)</code> → ⑥ 交 <code>ToolService</code> 按 <code>spec.name()</code> 建表。</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">McpToolFactory</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">/** 用 client 自身的 serverAlias 作为前缀。 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpClient</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">/** 显式 alias 优先，空白时回落到 client.serverAlias()。 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">McpClient</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">/** enabledTools 非空时只暴露其中的远程工具名；disabledTools 命中的被隐藏。 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">McpClient</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">Set</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> enabledTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Set</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> disabledTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤①：确定前缀——显式 alias 优先，否则读 client.serverAlias()</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">String</span><span class="token plain"> alias </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">serverAlias </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">trim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isEmpty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token operator">?</span><span class="token plain"> serverAlias </span><span class="token operator">:</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">LinkedHashMap</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤②：拉取 Server 原生工具（首次会触发 initialize → initialized → tools/list）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpTool</span><span class="token plain"> tool </span><span class="token operator">:</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">listTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤③：白/黑名单过滤（按"原始工具名"判定）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">enabledTools </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">enabledTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isEmpty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">enabledTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">contains</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">disabledTools </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> disabledTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">contains</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤④：拼 alias 生成模型可见名；inputSchema 原样透传；写入归属元数据</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">ToolSpecification</span><span class="token plain"> spec </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">ToolSpecification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">qualify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">alias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)">// fs__read_file</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpToolSpecificationMapper</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toToolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">inputSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addMetadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"mcp.server"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> alias </span><span class="token operator">==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">?</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token plain"> </span><span class="token operator">:</span><span class="token plain"> alias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addMetadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"mcp.tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤⑤：绑定 (client + 原始远程名)——运行期据此路由回对应 Server</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">put</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">McpToolExecutor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 步骤⑥：返回 spec→executor 映射，交给 ToolService.tools(...) 按 spec.name() 建表</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">qualify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> alias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> toolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">alias </span><span class="token operator">==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> alias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">trim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isEmpty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">?</span><span class="token plain"> toolName </span><span class="token operator">:</span><span class="token plain"> alias </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"__"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> toolName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="443schema-映射零损耗">4.4.3、Schema 映射（零损耗）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#443schema-%E6%98%A0%E5%B0%84%E9%9B%B6%E6%8D%9F%E8%80%97" class="hash-link" aria-label="4.4.3、Schema 映射（零损耗）的直接链接" title="4.4.3、Schema 映射（零损耗）的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">ToolParameters</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toToolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> inputSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">inputSchema </span><span class="token operator">==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> inputSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isEmpty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">ToolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">empty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">// {"type":"object","properties":{}}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">ToolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">inputSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 原样透传 JSON Schema</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="444错误映射">4.4.4、错误映射<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#444%E9%94%99%E8%AF%AF%E6%98%A0%E5%B0%84" class="hash-link" aria-label="4.4.4、错误映射的直接链接" title="4.4.4、错误映射的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>MCP</th><th>AgentForge</th></tr></thead><tbody><tr><td><code>result.isError = true</code></td><td><code>ToolExecutionResult.isError(true)</code> + 文本回给模型自纠</td></tr><tr><td>JSON-RPC <code>error</code>（<code>-32601/-32602</code>…）</td><td><code>McpProtocolException</code> → 失败结果 / 错误处理器</td></tr><tr><td><code>-32022</code> 版本不支持</td><td>抛 <code>McpProtocolException</code>（自动重选版本为 P1）</td></tr><tr><td>传输异常 / 超时</td><td><code>McpTransportException</code> → 失败结果 + 告警</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五agentforge-实战代码">五、AgentForge 实战代码<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E4%BA%94agentforge-%E5%AE%9E%E6%88%98%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="五、AgentForge 实战代码的直接链接" title="五、AgentForge 实战代码的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51最小接入两种传输">5.1、最小接入：两种传输<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#51%E6%9C%80%E5%B0%8F%E6%8E%A5%E5%85%A5%E4%B8%A4%E7%A7%8D%E4%BC%A0%E8%BE%93" class="hash-link" aria-label="5.1、最小接入：两种传输的直接链接" title="5.1、最小接入：两种传输的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="511接入本地-stdio-server">5.1.1、接入本地 stdio Server<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#511%E6%8E%A5%E5%85%A5%E6%9C%AC%E5%9C%B0-stdio-server" class="hash-link" aria-label="5.1.1、接入本地 stdio Server的直接链接" title="5.1.1、接入本地 stdio Server的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 1) 启动 stdio MCP Server（本地子进程，换行分帧的 JSON-RPC）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">McpClient</span><span class="token plain"> fs </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">DefaultMcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">transport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">StdioMcpTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">command</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"npx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"-y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@modelcontextprotocol/server-filesystem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/tmp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 2) 拉取工具并注册进 ToolService（读取 client 的 serverAlias，自动加前缀：fs__read_file）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">fs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 3) 与本地 @Tool 共存（同一张工具表）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 4) 装配 ReActAgent</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ReActAgent</span><span class="token plain"> agent </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">ReActAgent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>DefaultMcpClient</code> 是<strong>惰性</strong>的——首次 <code>listTools()</code> / <code>callTool()</code> 时才发 <code>initialize</code> 握手与 <code>notifications/initialized</code>，随后才发 <code>tools/list</code>。<code>StdioMcpTransport.of("npx", "-y", ...)</code> 是等价的简写。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="512接入远程-streamable-http-server">5.1.2、接入远程 Streamable HTTP Server<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#512%E6%8E%A5%E5%85%A5%E8%BF%9C%E7%A8%8B-streamable-http-server" class="hash-link" aria-label="5.1.2、接入远程 Streamable HTTP Server的直接链接" title="5.1.2、接入远程 Streamable HTTP Server的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">McpClient</span><span class="token plain"> remote </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">DefaultMcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"github"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">transport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">StreamableHttpMcpTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">endpoint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://mcp.example.com/mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">header</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Bearer "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 自定义头随请求透传</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">remote</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 读取 client 的 serverAlias，工具名形如 github__search_repos</span><br></div></code></pre></div></div>
<p><strong>与 stdio 的差异只在传输层</strong>：<code>serverAlias="github"</code> → 模型可见名 <code>github__search_repos</code>；<code>buildTools</code> / <code>ToolService</code> / <code>ToolExecutor</code> 的定位与参数转换<strong>完全一致</strong>。HTTP 侧每次 <code>tools/call</code> 就是一次 POST：</p>
<ul>
<li class="">请求体 = 上述 JSON-RPC（<code>{"jsonrpc","id","method":"tools/call","params":{...}}</code>）；</li>
<li class="">握手后自动带 <code>MCP-Protocol-Version</code>，自定义头（<code>Authorization</code>）原样透传；</li>
<li class="">响应按 <code>id</code> 取出（<code>application/json</code> 或 SSE <code>data:</code>），再走同一套 <code>content</code> / <code>isError</code> 映射。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="513按需过滤与关闭">5.1.3、按需过滤与关闭<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#513%E6%8C%89%E9%9C%80%E8%BF%87%E6%BB%A4%E4%B8%8E%E5%85%B3%E9%97%AD" class="hash-link" aria-label="5.1.3、按需过滤与关闭的直接链接" title="5.1.3、按需过滤与关闭的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 只暴露 read_file，屏蔽其余工具（白名单为空/null 表示全部）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"fs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singleton</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 白名单</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">                              </span><span class="token comment" style="color:rgb(98, 114, 164)">// 黑名单</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">close</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// stdio：关闭并回收子进程；HTTP：释放传输</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="514底层核心原理一句提问如何被闭环真实-function-call-串">5.1.4、底层核心原理：一句提问如何被闭环（真实 function call 串）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#514%E5%BA%95%E5%B1%82%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86%E4%B8%80%E5%8F%A5%E6%8F%90%E9%97%AE%E5%A6%82%E4%BD%95%E8%A2%AB%E9%97%AD%E7%8E%AF%E7%9C%9F%E5%AE%9E-function-call-%E4%B8%B2" class="hash-link" aria-label="5.1.4、底层核心原理：一句提问如何被闭环（真实 function call 串）的直接链接" title="5.1.4、底层核心原理：一句提问如何被闭环（真实 function call 串）的直接链接" translate="no">​</a></h3>
<p>场景：MCP <code>fs</code>（filesystem）与 MCP <code>weather</code> 同时注册，用户问「把 /tmp/a.txt 内容读出来，并查一下杭州天气」。下面<strong>按真实发生顺序</strong>展开。</p>
<p><strong>① MCP Server 先返回原生 <code>tools/list</code>（无前缀）</strong></p>
<p>AgentForge 先对每个 Server 调 <code>tools/list</code>，拿到的是<strong>工具本名</strong>（此时还没有 <code>fs__</code> 前缀）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← fs server 的 tools/list 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"result"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"description"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"Read a file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"inputSchema"</span><span class="token operator">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"path"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"required"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"path"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// ← weather server 的 tools/list 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"result"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"description"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"Query weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"inputSchema"</span><span class="token operator">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"location"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"required"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"location"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><strong>② 中间层拼装 alias（<code>McpToolFactory</code>）</strong></p>
<p><code>buildTools(client)</code> 对上述<strong>原始工具</strong>做转换：<code>alias__原始名</code>，同时绑定 executor 并写入 <code>ToolService</code>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">fs      + read_file    → spec.name = "fs__read_file"        + McpToolExecutor(fsClient,      "read_file")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">weather + get_weather  → spec.name = "weather__get_weather" + McpToolExecutor(weatherClient, "get_weather")</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<strong>原始名始终保留在 executor 里</strong>（<code>read_file</code> / <code>get_weather</code>），前缀只加在模型可见名上——这是后续"剥前缀、发原始名"的依据。</p>
</blockquote>
<p><strong>③ 大模型看到的 <code>tools[]</code>（alias 形态）</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"fs__read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"parameters"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"path"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"path"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather__get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"parameters"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"location"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p><strong>④ 模型返回的组合 function call（OpenAI 形态，一次两条）</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_a1b2"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"fs__read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"path\":\"/tmp/a.txt\"}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_c3d4"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather__get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"location\":\"杭州\"}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>关键</strong>：<code>arguments</code> 是<strong>带转义的 JSON 字符串</strong>，不是对象（Anthropic 则是 <code>tool_use.input</code> 对象）。AgentForge 把两者统一归一化为 <code>ToolExecutionRequest(id, name, arguments)</code>——<code>arguments</code> <strong>始终是字符串</strong>。</p>
</blockquote>
<p><strong>⑤ 归一化 → ToolService 按 alias 定位 executor</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionRequest{id="call_a1b2", name="fs__read_file",       arguments="{\"path\":\"/tmp/a.txt\"}"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionRequest{id="call_c3d4", name="weather__get_weather", arguments="{\"location\":\"杭州\"}"}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>ToolService</code> 用 <code>name</code> 命中 <code>toolExecutors</code>：<code>fs__read_file</code> → 绑定了 <code>fsClient</code> + <code>read_file</code> 的 <code>McpToolExecutor</code>；<code>weather__get_weather</code> → <code>weatherClient</code> + <code>get_weather</code>。</li>
</ul>
<p><strong>⑥ 剥前缀 + 参数还原 → 发原始 <code>tools/call</code></strong></p>
<ul>
<li class=""><code>McpJson.argumentsAsMap(arguments)</code> 把<strong>字符串还原为对象</strong>；</li>
<li class="">用 executor 里的<strong>原始远程名</strong>（剥掉 <code>fs__</code> / <code>weather__</code> 前缀）发出：</li>
</ul>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → fs server</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"method"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"tools/call"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"path"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"/tmp/a.txt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// → weather server</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"method"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"tools/call"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"location"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><strong>⑦ 响应 → 回灌模型 → 最终答案</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"result"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"content"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"text"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"isError"</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"result"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"content"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"text"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州 26℃，晴"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"isError"</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_a1b2"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_c3d4"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州 26℃，晴"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p>最终模型给出：「/tmp/a.txt 内容是 hello；杭州当前 26℃、晴。」</p>
<blockquote>
<p><strong>完整链路</strong>：MCP 原生工具名（<code>read_file</code>）→ 中间层拼 alias（<code>fs__read_file</code>）→ 模型看到并回传 alias → <code>ToolService</code> 按 alias 定位 executor → 剥前缀还原原始名 → 发到对应 Server 的 <code>tools/call</code>。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52传输实现与协议版本">5.2、传输实现与协议版本<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#52%E4%BC%A0%E8%BE%93%E5%AE%9E%E7%8E%B0%E4%B8%8E%E5%8D%8F%E8%AE%AE%E7%89%88%E6%9C%AC" class="hash-link" aria-label="5.2、传输实现与协议版本的直接链接" title="5.2、传输实现与协议版本的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="521stdio换行分帧">5.2.1、stdio（换行分帧）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#521stdio%E6%8D%A2%E8%A1%8C%E5%88%86%E5%B8%A7" class="hash-link" aria-label="5.2.1、stdio（换行分帧）的直接链接" title="5.2.1、stdio（换行分帧）的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>ProcessBuilder</code> 起子进程，<code>stdin</code> / <code>stdout</code> 按 UTF-8 <strong>换行分帧</strong>：每条 JSON-RPC 一行，发送前剔除内嵌换行；</li>
<li class=""><code>stderr</code> 由后台守护线程持续抽取到日志，避免管道写满而阻塞；</li>
<li class="">关闭：关 <code>stdin</code> → <code>destroy()</code> → 等 2s → <code>destroyForcibly()</code>。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="522streamable-http复用内置-httptransport">5.2.2、Streamable HTTP（复用内置 <code>HttpTransport</code>）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#522streamable-http%E5%A4%8D%E7%94%A8%E5%86%85%E7%BD%AE-httptransport" class="hash-link" aria-label="522streamable-http复用内置-httptransport的直接链接" title="522streamable-http复用内置-httptransport的直接链接" translate="no">​</a></h3>
<ul>
<li class="">单一 endpoint，仅 POST；复用 <code>JdkHttpTransport</code> 与内置 <code>Json</code>；</li>
<li class="">请求头：<code>Content-Type: application/json</code>、<code>Accept: application/json, text/event-stream</code>；握手后追加 <code>MCP-Protocol-Version</code>，其余自定义头（如 <code>Authorization</code>）原样透传；</li>
<li class="">响应：<code>application/json</code>（单条 JSON-RPC）或请求级 <code>text/event-stream</code>（解析 <code>data:</code> 事件，按 <code>id</code> 选出匹配响应）；</li>
<li class=""><strong>P0 边界</strong>：<code>HttpResponse</code> 未暴露响应头，暂不回传 <code>Mcp-Session-Id</code>（无状态 Server 可用；有状态会话为 P1）。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：<code>JdkHttpTransport</code> 基于 <code>HttpURLConnection</code>，不支持 <code>PATCH</code>；MCP 本身不用 PATCH，无影响。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="523协议版本支持现状">5.2.3、协议版本支持现状<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#523%E5%8D%8F%E8%AE%AE%E7%89%88%E6%9C%AC%E6%94%AF%E6%8C%81%E7%8E%B0%E7%8A%B6" class="hash-link" aria-label="5.2.3、协议版本支持现状的直接链接" title="5.2.3、协议版本支持现状的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>已实现（P0）</strong>：Legacy <code>initialize</code> 握手（默认版本 <code>2025-06-18</code>，可用 <code>protocolVersion(...)</code> 覆盖），握手后发送 <code>notifications/initialized</code>；</li>
<li class=""><strong>未实现（P1）</strong>：Modern / <code>server/discover</code> 自动探测。4.2.3 所述"双时代探测"为目标设计，当前按 legacy 路径工作。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="53官方-sdk-桥接模块p3-规划尚未实现">5.3、官方 SDK 桥接模块（P3 规划，尚未实现）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#53%E5%AE%98%E6%96%B9-sdk-%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%9D%97p3-%E8%A7%84%E5%88%92%E5%B0%9A%E6%9C%AA%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="5.3、官方 SDK 桥接模块（P3 规划，尚未实现）的直接链接" title="5.3、官方 SDK 桥接模块（P3 规划，尚未实现）的直接链接" translate="no">​</a></h2>
<p><strong>状态</strong>：<code>agentforge-mcp-sdk-bridge</code> <strong>当前尚未实现</strong>，属路线图 P3；其依赖隔离策略见 4.2.3。落地后再补充确切的 Maven 坐标、装配 API 与配置样例，避免文档与代码不符。</p>
<p><strong>目标形态</strong>（供实现时对齐）：</p>
<ul>
<li class="">独立 JDK 17 artifact，<code>bridge → core</code> 单向依赖，输出仍是 <code>Map&lt;ToolSpecification, ToolExecutor&gt;</code>；</li>
<li class="">业务侧只把 <code>McpToolFactory</code> 替换为官方桥接工厂，其余（<code>ToolService</code> / <code>ReActAgent</code>）不变；</li>
<li class="">同一 Server <strong>不要</strong>与非桥接栈同时注册，避免工具名冲突。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六验证测试与支持度">六、验证测试与支持度<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E5%85%AD%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95%E4%B8%8E%E6%94%AF%E6%8C%81%E5%BA%A6" class="hash-link" aria-label="六、验证测试与支持度的直接链接" title="六、验证测试与支持度的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="61验证与测试">6.1、验证与测试<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#61%E9%AA%8C%E8%AF%81%E4%B8%8E%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="6.1、验证与测试的直接链接" title="6.1、验证与测试的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="611快速验证stdio真实公开-server">6.1.1、快速验证：stdio（真实公开 Server）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#611%E5%BF%AB%E9%80%9F%E9%AA%8C%E8%AF%81stdio%E7%9C%9F%E5%AE%9E%E5%85%AC%E5%BC%80-server" class="hash-link" aria-label="6.1.1、快速验证：stdio（真实公开 Server）的直接链接" title="6.1.1、快速验证：stdio（真实公开 Server）的直接链接" translate="no">​</a></h3>
<p>最省事的自检——用官方 filesystem Server 起本地子进程，跑通「发现 → 调用 → 回灌」：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">McpStdioQuickTest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Test</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">shouldListAndCallPublicFilesystemServer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throws</span><span class="token plain"> </span><span class="token class-name">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">Path</span><span class="token plain"> dir </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">Files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">createTempDirectory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"mcp-demo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">Path</span><span class="token plain"> file </span><span class="token operator">=</span><span class="token plain"> dir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">resolve</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"a.txt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">McpClient</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">DefaultMcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">transport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">StdioMcpTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">of</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token string" style="color:rgb(255, 121, 198)">"npx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"-y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@modelcontextprotocol/server-filesystem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> dir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"fs"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token function" style="color:rgb(80, 250, 123)">assertNotNull</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolExecutors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs__write_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolExecutors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs__write_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token class-name">ToolExecutionRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs__write_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"{\"path\":"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token class-name">Json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stringify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                    </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">",\"content\":\"hello mcp\"}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">String</span><span class="token plain"> text </span><span class="token operator">=</span><span class="token plain"> toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolExecutors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs__read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token class-name">ToolExecutionRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"fs__read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"{\"path\":"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token class-name">Json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stringify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token function" style="color:rgb(80, 250, 123)">assertTrue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">contains</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"hello mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">close</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p>该用例已落为可运行测试：<code>agentforge-agent-core/src/test/java/cloud/changlu/agentforge/agent/tool/mcp/real/McpRealStudioTest.java</code>（真实 MCP Server + <code>ScriptedChatModel</code> 驱动完整 ReAct 循环，默认运行，可用 <code>-Dagentforge.mcp.live=false</code> 关闭）。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="612快速验证http真实公开-server">6.1.2、快速验证：HTTP（真实公开 Server）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#612%E5%BF%AB%E9%80%9F%E9%AA%8C%E8%AF%81http%E7%9C%9F%E5%AE%9E%E5%85%AC%E5%BC%80-server" class="hash-link" aria-label="6.1.2、快速验证：HTTP（真实公开 Server）的直接链接" title="6.1.2、快速验证：HTTP（真实公开 Server）的直接链接" translate="no">​</a></h3>
<p>换成 Streamable HTTP 传输，其余代码完全一致（唯一差别是 <code>transport</code>）：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">McpHttpQuickTest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Test</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">shouldListPublicHttpServerTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">McpClient</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">DefaultMcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">serverAlias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"wiki"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">transport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">StreamableHttpMcpTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">endpoint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://mcp.deepwiki.com/mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"wiki"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token function" style="color:rgb(80, 250, 123)">assertFalse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isEmpty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// deepwiki 实测暴露 3 个工具</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">close</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>一句话</strong>：stdio 与 HTTP <strong>只差一个 <code>transport</code></strong>，<code>McpToolFactory</code> / <code>ToolService</code> / <code>ReActAgent</code> 用法完全相同。</p>
</blockquote>
<blockquote>
<p>该用例已落为可运行测试：<code>agentforge-agent-core/src/test/java/cloud/changlu/agentforge/agent/tool/mcp/real/McpRealHttpTest.java</code>（真实 DeepWiki 公开服务，发现 + 真实调用 <code>read_wiki_structure</code>）。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="613公开-mcp-服务实测清单">6.1.3、公开 MCP 服务实测清单<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#613%E5%85%AC%E5%BC%80-mcp-%E6%9C%8D%E5%8A%A1%E5%AE%9E%E6%B5%8B%E6%B8%85%E5%8D%95" class="hash-link" aria-label="6.1.3、公开 MCP 服务实测清单的直接链接" title="6.1.3、公开 MCP 服务实测清单的直接链接" translate="no">​</a></h3>
<p>实测时间：2026-10-05，均为免鉴权公开服务：</p>
<table><thead><tr><th>形式</th><th>服务</th><th>启动 / 地址</th><th>实测工具数</th><th>结果</th></tr></thead><tbody><tr><td>stdio</td><td>filesystem</td><td><code>npx -y @modelcontextprotocol/server-filesystem &lt;dir&gt;</code></td><td>14</td><td>✅</td></tr><tr><td>stdio</td><td>memory</td><td><code>npx -y @modelcontextprotocol/server-memory</code></td><td>9</td><td>✅</td></tr><tr><td>stdio</td><td>sequential-thinking</td><td><code>npx -y @modelcontextprotocol/server-sequential-thinking</code></td><td>1</td><td>✅</td></tr><tr><td>stdio</td><td>everything</td><td><code>npx -y @modelcontextprotocol/server-everything</code></td><td>13</td><td>✅</td></tr><tr><td>HTTP</td><td>deepwiki</td><td><code>https://mcp.deepwiki.com/mcp</code></td><td>3</td><td>✅</td></tr><tr><td>HTTP</td><td>context7</td><td><code>https://mcp.context7.com/mcp</code></td><td>2</td><td>✅</td></tr><tr><td>HTTP</td><td>gitmcp</td><td><code>https://gitmcp.io/{owner}/{repo}</code></td><td>—</td><td>⚠️ 需 <code>Mcp-Session-Id</code>（P1）</td></tr></tbody></table>
<ul>
<li class="">典型工具名：filesystem → <code>read_file</code> / <code>write_file</code> / <code>list_directory</code>…；everything → <code>echo</code> / <code>get-sum</code> / <code>get-env</code>…；deepwiki → <code>ask_wiki_question</code> / <code>read_wiki_structure</code> / <code>read_wiki_contents</code>；context7 → <code>resolve-library-id</code> / <code>query-docs</code>。</li>
<li class="">复现命令：</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 公开服务扫描（需显式开启 live）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">mvn </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Dagentforge.mcp.live</span><span class="token operator">=</span><span class="token plain">true </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Dtest</span><span class="token operator">=</span><span class="token plain">McpPublicServersLiveTest,McpStdioLiveTest,McpHttpLiveTest</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 文档案例的真实应用测试（默认运行，-Dagentforge.mcp.live=false 关闭）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">mvn </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Dtest</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">'cloud.changlu.agentforge.agent.tool.mcp.real.**'</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>gitmcp</code> 在 <code>initialize</code> 后要求后续请求回传 <code>Mcp-Session-Id</code>，当前 P0 无状态实现会报 <code>Bad Request: Mcp-Session-Id header is required</code>——这正是有状态 HTTP 会话要留到 P1 的原因。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="614离线单元测试不依赖真实-server">6.1.4、离线单元测试（不依赖真实 Server）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#614%E7%A6%BB%E7%BA%BF%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E4%B8%8D%E4%BE%9D%E8%B5%96%E7%9C%9F%E5%AE%9E-server" class="hash-link" aria-label="6.1.4、离线单元测试（不依赖真实 Server）的直接链接" title="6.1.4、离线单元测试（不依赖真实 Server）的直接链接" translate="no">​</a></h3>
<p>Mock <code>McpClient</code>（<code>FakeMcpClient</code>）注入固定工具与结果，即可离线跑：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">FakeMcpClient</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">FakeMcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"demo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singletonList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">McpTool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">weatherSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"demo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">assertEquals</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"demo__get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">keySet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">iterator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">next</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>已覆盖：<code>JsonRpcCodec</code> 编解码、<code>tools/list</code> 映射、名称消歧、白/黑名单过滤、<code>tools/call</code> 参数透传、<code>isError</code> 分流、协议 / 传输错误失败化、握手时序、HTTP JSON 与 SSE 两种响应形态；另含真实 <code>JdkHttpTransport</code> + 本地 HTTP Server 的端到端用例。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="615风险与一致性">6.1.5、风险与一致性<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#615%E9%A3%8E%E9%99%A9%E4%B8%8E%E4%B8%80%E8%87%B4%E6%80%A7" class="hash-link" aria-label="6.1.5、风险与一致性的直接链接" title="6.1.5、风险与一致性的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>有状态 HTTP</strong>：需回传 <code>Mcp-Session-Id</code>（gitmcp 实测即此限制），P1 补齐。</li>
<li class=""><strong>规范跟进</strong>：自研需持续跟进 <code>2024-11-05 → 2026-07-28</code> 的修订与弃用；对照官方 <strong>MCP Inspector</strong> / <strong>conformance</strong> 自测消息结构。</li>
<li class=""><strong>双时代复杂</strong>：<code>initialize</code> 与逐请求 <code>_meta</code> 并存，探测与缓存要做对（P1）。</li>
<li class=""><strong>HTTP 流式</strong>：请求级 SSE 的解析与取消（关流即取消）需正确实现。</li>
<li class=""><strong>安全</strong>：Tool Poisoning、审批（human-in-the-loop）、凭据管理不可省。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="62支持度分级与路线图">6.2、支持度分级与路线图<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#62%E6%94%AF%E6%8C%81%E5%BA%A6%E5%88%86%E7%BA%A7%E4%B8%8E%E8%B7%AF%E7%BA%BF%E5%9B%BE" class="hash-link" aria-label="6.2、支持度分级与路线图的直接链接" title="6.2、支持度分级与路线图的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>阶段</th><th>能力</th><th>传输</th><th>说明</th></tr></thead><tbody><tr><td><strong>P0</strong></td><td><code>tools/list</code> + <code>tools/call</code></td><td>stdio / Streamable HTTP</td><td>最小可用：发现 + 执行</td></tr><tr><td><strong>P0</strong></td><td>名称消歧 / 工具过滤 / 元数据</td><td>—</td><td><code>serverAlias__</code> 前缀、白名单</td></tr><tr><td><strong>P0</strong></td><td>错误分流（isError / 协议错误）</td><td>—</td><td>回模型自纠</td></tr><tr><td><strong>P1</strong></td><td>双时代兼容 + 超时/取消 + 关闭重启</td><td>stdio / HTTP</td><td>稳定性</td></tr><tr><td><strong>P1</strong></td><td><code>notifications/tools/list_changed</code> 热更新</td><td>stdio / HTTP</td><td>工具表刷新</td></tr><tr><td><strong>P1</strong></td><td>Resources / Prompts（读）</td><td>stdio / HTTP</td><td><code>resources/read</code>、<code>prompts/get</code></td></tr><tr><td><strong>P2</strong></td><td>MRTR（<code>InputRequiredResult</code>）</td><td>stdio / HTTP</td><td>交互式输入</td></tr><tr><td><strong>P2</strong></td><td><code>subscriptions/listen</code></td><td>HTTP</td><td>变更订阅</td></tr><tr><td><strong>P2</strong></td><td>OAuth 2.1 / OIDC</td><td>HTTP</td><td>远程鉴权</td></tr><tr><td><strong>P2</strong></td><td><code>x-mcp-header</code> 镜像</td><td>HTTP</td><td>网关路由</td></tr><tr><td><strong>P3</strong></td><td>Server 侧（暴露 AgentForge <code>@Tool</code> 为 MCP）</td><td>stdio / HTTP</td><td>反向赋能</td></tr><tr><td><strong>P3</strong></td><td>官方 SDK 桥接模块（JDK17）</td><td>全</td><td>可选一致性方案</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="621与-http--local-模式的一致性">6.2.1、与 <code>http</code> / <code>local</code> 模式的一致性<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#621%E4%B8%8E-http--local-%E6%A8%A1%E5%BC%8F%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7" class="hash-link" aria-label="621与-http--local-模式的一致性的直接链接" title="621与-http--local-模式的一致性的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th><code>local</code></th><th><code>http</code></th><th><code>mcp</code>（本文）</th></tr></thead><tbody><tr><td>工厂</td><td><code>LocalToolFactory</code></td><td><code>HttpToolFactory</code></td><td><code>McpToolFactory</code></td></tr><tr><td>执行器</td><td><code>LocalToolExecutor</code></td><td><code>HttpToolExecutor</code></td><td><code>McpToolExecutor</code></td></tr><tr><td>发现</td><td>扫 <code>@Tool</code></td><td>读配置</td><td><code>tools/list</code> 动态</td></tr><tr><td>Schema</td><td>反射生成</td><td>配置生成</td><td>Server 下发</td></tr><tr><td>错误</td><td><code>ToolExecutionResult</code></td><td>文本「HTTP请求失败」</td><td><code>isError</code> 无损映射</td></tr><tr><td>子包</td><td><code>support/</code></td><td><code>domain/enums/support/parser/</code></td><td><code>domain/transport/support/</code></td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：MCP 模式与既有两种模式正交，可同时注册进同一个 <code>ToolService</code>，模型看到统一的工具清单。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七总结与展望">七、总结与展望<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E4%B8%83%E6%80%BB%E7%BB%93%E4%B8%8E%E5%B1%95%E6%9C%9B" class="hash-link" aria-label="七、总结与展望的直接链接" title="七、总结与展望的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="71总结与展望">7.1、总结与展望<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#71%E6%80%BB%E7%BB%93%E4%B8%8E%E5%B1%95%E6%9C%9B" class="hash-link" aria-label="7.1、总结与展望的直接链接" title="7.1、总结与展望的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>调研结论</strong>：三方 stdio / HTTP 都支持；<strong>LangChain4j 自研，Spring AI 与 AgentScope 基于官方 MCP Java SDK</strong>；官方 SDK 是事实标准但要求 JDK 17+。</li>
<li class=""><strong>AgentForge 选择</strong>：<strong>核心自研协议栈（Java 8 / 零依赖，已落地）+ 官方 SDK 可选桥接（P3 规划）</strong>。</li>
<li class=""><strong>接入落点</strong>：<code>McpToolExecutor implements ToolExecutor</code> + <code>McpToolFactory</code>，无缝并入 <code>ToolService</code> 与 ReAct 循环。</li>
<li class=""><strong>支持节奏</strong>：P0 打通 stdio / Streamable HTTP 的 <code>tools/list</code> + <code>tools/call</code>；P1 补热更新与资源提示词；P2 补 MRTR / 订阅 / OAuth；P3 反向 Server 与官方桥接。</li>
<li class=""><strong>展望</strong>：随规范演进补齐 MRTR、<code>subscriptions/listen</code>、OAuth 加固，并探索把 AgentForge 的 <code>@Tool</code> 反向暴露为 MCP Server。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://modelcontextprotocol.io/specification/2026-07-28" target="_blank" rel="noopener noreferrer" class="">MCP 官方规范（2026-07-28）</a></p>
<p>[2]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle" target="_blank" rel="noopener noreferrer" class="">MCP 版本与兼容性</a></p>
<p>[3]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http" target="_blank" rel="noopener noreferrer" class="">MCP Streamable HTTP 传输</a></p>
<p>[4]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio" target="_blank" rel="noopener noreferrer" class="">MCP stdio 传输</a></p>
<p>[5]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" target="_blank" rel="noopener noreferrer" class="">MCP Tools</a></p>
<p>[6]. <a href="https://java.sdk.modelcontextprotocol.io/latest/" target="_blank" rel="noopener noreferrer" class="">官方 MCP Java SDK 文档</a></p>
<p>[7]. <a href="https://github.com/modelcontextprotocol/java-sdk" target="_blank" rel="noopener noreferrer" class="">MCP Java SDK（GitHub）</a></p>
<p>[8]. <a href="https://docs.langchain4j.dev/tutorials/mcp/" target="_blank" rel="noopener noreferrer" class="">LangChain4j MCP 文档</a></p>
<p>[9]. <a href="https://github.com/langchain4j/langchain4j/tree/main/langchain4j-mcp" target="_blank" rel="noopener noreferrer" class="">langchain4j-mcp 模块（GitHub）</a></p>
<p>[10]. <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html" target="_blank" rel="noopener noreferrer" class="">Spring AI MCP 总览</a></p>
<p>[11]. <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html" target="_blank" rel="noopener noreferrer" class="">Spring AI MCP Client Boot Starters</a></p>
<p>[12]. <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html" target="_blank" rel="noopener noreferrer" class="">Spring AI MCP Server Boot Starters</a></p>
<p>[13]. <a href="https://java.agentscope.io/v1/en/docs/task/mcp" target="_blank" rel="noopener noreferrer" class="">AgentScope Java MCP 文档</a></p>
<p>[14]. <a href="https://github.com/agentscope-ai/agentscope-java" target="_blank" rel="noopener noreferrer" class="">AgentScope Java（GitHub）</a></p>
<p>[15]. <a href="https://github.com/modelcontextprotocol/conformance" target="_blank" rel="noopener noreferrer" class="">MCP 一致性测试 conformance</a></p>
<p>[16]. <a href="https://github.com/modelcontextprotocol/inspector" target="_blank" rel="noopener noreferrer" class="">MCP Inspector</a></p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>通用ReActagent层</category>
            <category>Function Calling</category>
            <category>MCP</category>
        </item>
        <item>
            <title><![CDATA[MCP协议转换FunctionCall协议01、MCP协议详解-历史演进与底层原理]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/mcp/MCP协议转换FunctionCall协议01、MCP协议详解-历史演进与底层原理</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/mcp/MCP协议转换FunctionCall协议01、MCP协议详解-历史演进与底层原理</guid>
            <pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[一句话概括：MCP（Model Context Protocol，模型上下文协议）是 Anthropic 在 2024 年底开源、现由 Linux 基金会下 Agentic AI Foundation 治理的开放标准。它用一套统一的 JSON-RPC 协议，把「工具/资源/提示词」从每一个 AI 应用里解耦出来，做成可插拔的 Server，从而把「AI 应用 × 外部能力」的集成成本从 M×N 降到 M+N。]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>一句话概括：MCP（Model Context Protocol，模型上下文协议）是 Anthropic 在 2024 年底开源、现由 Linux 基金会下 Agentic AI Foundation 治理的开放标准。它用一套统一的 JSON-RPC 协议，把「工具/资源/提示词」从每一个 AI 应用里解耦出来，做成可插拔的 Server，从而把「AI 应用 × 外部能力」的集成成本从 M×N 降到 M+N。</p>
</blockquote>
<!-- -->
<br>
<p>本文是一篇 MCP 协议专题：从它的<strong>由来与版本历史</strong>讲起，逐步展开<strong>核心概念、协议设计、Client/Server 底层原理</strong>，并给出<strong>基于 Java 的 Client/Server 快速搭建</strong>与<strong>对接 Agent 的设计流程</strong>，最后落到<strong>安全机制、扩展治理与设计权衡</strong>。</p>
<blockquote>
<p>官方入口：<a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">规范文档 modelcontextprotocol.io</a> ｜ <a href="https://blog.modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">官方博客</a> ｜ <a href="https://github.com/modelcontextprotocol" target="_blank" rel="noopener noreferrer" class="">GitHub 组织</a> ｜ <a href="https://registry.modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">官方注册中心</a> ｜ <a href="https://github.com/modelcontextprotocol/java-sdk" target="_blank" rel="noopener noreferrer" class="">Java SDK</a></p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动一个真实的集成难题">1.1、场景驱动：一个真实的集成难题<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E4%B8%80%E4%B8%AA%E7%9C%9F%E5%AE%9E%E7%9A%84%E9%9B%86%E6%88%90%E9%9A%BE%E9%A2%98" class="hash-link" aria-label="1.1、场景驱动：一个真实的集成难题的直接链接" title="1.1、场景驱动：一个真实的集成难题的直接链接" translate="no">​</a></h3>
<p>在开发一个 AI Agent 产品的过程中，我们就遇到了这样一个具体问题：</p>
<blockquote>
<p>用户希望 Agent 能查天气、查数据库、读 GitHub、发钉钉消息……而这些能力，团队里不同的同学已经用不同语言、不同框架写好了一堆「工具服务」。有的用 Python 写、有的用 Node 写、有的干脆就是一段 shell。<br>
<!-- -->问题是：<strong>每接一个工具，Agent 应用就要为它写一次适配代码</strong>。工具一多，应用端就成了一个大杂烩：一会儿要引入 Python 进程，一会儿要封装 HTTP，一会儿要写 JSON Schema。换个模型厂商，Schema 格式又不一样，全部重来。</p>
</blockquote>
<p>这不是某一家产品的痛点，而是整个行业的痛点。MCP 正是为它而生。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="12function-calling-的m--n-集成灾难">1.2、Function Calling 的「M × N 集成灾难」<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#12function-calling-%E7%9A%84m--n-%E9%9B%86%E6%88%90%E7%81%BE%E9%9A%BE" class="hash-link" aria-label="1.2、Function Calling 的「M × N 集成灾难」的直接链接" title="1.2、Function Calling 的「M × N 集成灾难」的直接链接" translate="no">​</a></h3>
<p>在 MCP 出现之前，主流做法是各家模型厂商的 <strong>Function Calling / Tool Use</strong>：</p>
<ul>
<li class="">OpenAI 用 <code>tools[].function.{name,description,parameters}</code></li>
<li class="">Anthropic 用 <code>tools[].{name,description,input_schema}</code></li>
<li class="">各家 Prompt 模板、强制调用参数、并行调用能力都不完全一致</li>
</ul>
<p>于是形成一个典型的 <strong>M × N 问题</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">M 个 AI 应用（Host / Agent 框架）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ×</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">N 个外部工具（DB、GitHub、Slack、文件系统……）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        =</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">M × N 份适配代码</span><br></div></code></pre></div></div>
<p>每一次新增工具，都要在 M 个应用里重复接入；每一次新增应用，都要重新对接 N 个工具。<strong>成本随规模平方级增长。</strong></p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="13设计灵感来自-lsp-的类比">1.3、设计灵感：来自 LSP 的类比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#13%E8%AE%BE%E8%AE%A1%E7%81%B5%E6%84%9F%E6%9D%A5%E8%87%AA-lsp-%E7%9A%84%E7%B1%BB%E6%AF%94" class="hash-link" aria-label="1.3、设计灵感：来自 LSP 的类比的直接链接" title="1.3、设计灵感：来自 LSP 的类比的直接链接" translate="no">​</a></h3>
<p>MCP 的架构范式直接承袭 <strong>LSP（Language Server Protocol）</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">LSP：M 个编辑器 × N 种编程语言  →  M + N（协议居中）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">MCP：M 个 AI 应用 × N 个外部能力 →  M + N（协议居中）</span><br></div></code></pre></div></div>
<p>正如 LSP 让「编辑器」与「语言服务」通过统一协议解耦，MCP 让「AI 应用」与「外部能力」通过统一协议解耦。这个类比是理解 MCP 一切设计的钥匙——<strong>Host 管编排、Server 管能力、协议管通信</strong>。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="14本文要回答的问题">1.4、本文要回答的问题<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#14%E6%9C%AC%E6%96%87%E8%A6%81%E5%9B%9E%E7%AD%94%E7%9A%84%E9%97%AE%E9%A2%98" class="hash-link" aria-label="1.4、本文要回答的问题的直接链接" title="1.4、本文要回答的问题的直接链接" translate="no">​</a></h3>
<ol>
<li class="">MCP 从哪里来？治理如何演进？<br></li>
<li class="">它有哪些版本？每个版本改了什么？<br></li>
<li class="">当前（2026-07-28）的协议设计全貌是什么？<br></li>
<li class="">一个 MCP Client / Server 内部是怎么跑起来的？<br></li>
<li class="">这些设计背后有哪些权衡？</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二起源与版本历史">二、起源与版本历史<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%BA%8C%E8%B5%B7%E6%BA%90%E4%B8%8E%E7%89%88%E6%9C%AC%E5%8E%86%E5%8F%B2" class="hash-link" aria-label="二、起源与版本历史的直接链接" title="二、起源与版本历史的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="21发布与开源">2.1、发布与开源<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#21%E5%8F%91%E5%B8%83%E4%B8%8E%E5%BC%80%E6%BA%90" class="hash-link" aria-label="2.1、发布与开源的直接链接" title="2.1、发布与开源的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>2024-11-05</strong>：MCP 首个规范版本发布，定义 client-server 架构、JSON-RPC 2.0、<code>tools</code>/<code>resources</code>/<code>prompts</code> 三大原语，以及 <code>stdio</code> 与 <code>HTTP+SSE</code> 两种传输。</li>
<li class=""><strong>2024-11-25</strong>：Anthropic <strong>公开宣布并开源</strong> MCP，同时发布 Python / TypeScript SDK 与 Google Drive、Slack、GitHub、Git、Postgres 等参考 Server。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：版本号 <code>2024-11-05</code> 早于公开宣布日 <code>2024-11-25</code>。这是因为 MCP 采用<strong>日期型版本号</strong>，其含义是「<strong>最后一次包含不兼容变更的日期</strong>」，而非发布日。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="22版本号与状态规则">2.2、版本号与状态规则<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#22%E7%89%88%E6%9C%AC%E5%8F%B7%E4%B8%8E%E7%8A%B6%E6%80%81%E8%A7%84%E5%88%99" class="hash-link" aria-label="2.2、版本号与状态规则的直接链接" title="2.2、版本号与状态规则的直接链接" translate="no">​</a></h3>
<ul>
<li class="">版本标识形如 <code>YYYY-MM-DD</code>；协议版本<strong>不因向后兼容的更新而递增</strong>。</li>
<li class="">每个修订有三种状态：<strong>Draft</strong>（草稿）、<strong>Current</strong>（当前）、<strong>Final</strong>（已定稿）。</li>
<li class="">单个特性还可被标记为 <strong>Deprecated</strong>（弃用），受正式的<strong>特性生命周期与弃用策略</strong>约束（2026-07-28 引入，至少 12 个月弃用窗口）。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="23版本时间线与采用里程碑">2.3、版本时间线与采用里程碑<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#23%E7%89%88%E6%9C%AC%E6%97%B6%E9%97%B4%E7%BA%BF%E4%B8%8E%E9%87%87%E7%94%A8%E9%87%8C%E7%A8%8B%E7%A2%91" class="hash-link" aria-label="2.3、版本时间线与采用里程碑的直接链接" title="2.3、版本时间线与采用里程碑的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>日期</th><th>事件</th><th>类型</th></tr></thead><tbody><tr><td>2024-11-05</td><td>首个规范 <code>2024-11-05</code>：client-server、JSON-RPC 2.0、tools/resources/prompts、stdio + HTTP+SSE</td><td>规范修订</td></tr><tr><td>2024-11-25</td><td>Anthropic 宣布并开源 MCP，发布 Python / TS SDK 与参考 Server</td><td>发布</td></tr><tr><td>2025-03-19</td><td>Microsoft 在 Copilot Studio 引入 MCP 支持</td><td>采用</td></tr><tr><td>2025-03-26</td><td>规范 <code>2025-03-26</code>：OAuth 2.1 授权、Streamable HTTP 取代 HTTP+SSE、工具注解、音频、补全、JSON-RPC 批处理</td><td>规范修订</td></tr><tr><td>2025-03-26</td><td>OpenAI 在 Agents SDK 引入 MCP</td><td>采用</td></tr><tr><td>2025-04-09</td><td>Google Gemini 承诺支持 MCP</td><td>采用</td></tr><tr><td>2025-05-19</td><td>Microsoft 在 Build 2025 全面支持 MCP；微软与 GitHub 加入指导委员会</td><td>治理/采用</td></tr><tr><td>2025-05-21</td><td>OpenAI 在 Responses API 支持远程 MCP Server，加入指导委员会</td><td>采用/治理</td></tr><tr><td>2025-06-18</td><td>规范 <code>2025-06-18</code>：结构化工具输出、elicitation、资源链接；移除 JSON-RPC 批处理；<code>MCP-Protocol-Version</code> 头</td><td>规范修订</td></tr><tr><td>2025-09-05</td><td>官方 PHP SDK 发布</td><td>生态</td></tr><tr><td>2025-09-08</td><td>官方 MCP Registry 预览发布</td><td>生态</td></tr><tr><td>2025-11-25</td><td>规范 <code>2025-11-25</code>（一周年）：OpenID Connect Discovery、图标、标准枚举 elicitation、sampling 支持工具调用、实验 Tasks、JSON Schema 2020-12</td><td>规范修订</td></tr><tr><td>2025-12-09</td><td>Anthropic 将 MCP 捐赠给 Linux 基金会下新成立的 Agentic AI Foundation（AAIF）</td><td>治理</td></tr><tr><td>2026-01-26</td><td>MCP Apps 作为首个官方扩展发布</td><td>扩展</td></tr><tr><td>2026-05-21</td><td>下一版 <code>2026-07-28</code> 发布候选（Release Candidate）</td><td>规范修订</td></tr><tr><td>2026-06-29</td><td>四个一级（Tier 1）SDK 发布实现该候选版的 Beta 版本</td><td>生态</td></tr><tr><td>2026-07-28</td><td>规范 <code>2026-07-28</code> 成为 <strong>Current</strong>；四个一级 SDK 同日支持</td><td>规范修订</td></tr></tbody></table>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="24各版本关键设计变化">2.4、各版本关键设计变化<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#24%E5%90%84%E7%89%88%E6%9C%AC%E5%85%B3%E9%94%AE%E8%AE%BE%E8%AE%A1%E5%8F%98%E5%8C%96" class="hash-link" aria-label="2.4、各版本关键设计变化的直接链接" title="2.4、各版本关键设计变化的直接链接" translate="no">​</a></h3>
<p><strong><code>2025-03-26</code>：传输与授权的大跨步</strong></p>
<ul>
<li class="">新增 <strong>OAuth 2.1</strong> 授权框架；</li>
<li class="">用 <strong>Streamable HTTP</strong> 取代 HTTP+SSE（当时仍保留 session：<code>Mcp-Session-Id</code> 头、可 GET 打开独立 SSE 流、可用 <code>Last-Event-ID</code> 恢复）；</li>
<li class="">新增工具注解、音频内容、参数补全、JSON-RPC 批处理。</li>
</ul>
<p><strong><code>2025-06-18</code>：结构化输出与安全定位</strong></p>
<ul>
<li class="">新增<strong>结构化工具输出</strong>（<code>outputSchema</code> / <code>structuredContent</code>）、<strong>elicitation</strong>（服务端向用户索要输入）、工具结果中的<strong>资源链接</strong>；</li>
<li class="">把 MCP Server 明确定位为 <strong>OAuth Resource Server</strong>，要求 <strong>Resource Indicators（RFC 8707）</strong>；</li>
<li class=""><strong>移除 JSON-RPC 批处理</strong>；</li>
<li class="">要求后续 HTTP 请求携带 <code>MCP-Protocol-Version</code> 头。</li>
</ul>
<p><strong><code>2025-11-25</code>：一周年版本</strong></p>
<ul>
<li class="">授权服务端发现增强为 <strong>OpenID Connect Discovery 1.0</strong>，支持通过 <code>WWW-Authenticate</code> 做增量授权范围同意；</li>
<li class="">工具/资源/模板/提示词支持<strong>图标元数据</strong>；</li>
<li class="">elicitation 枚举改为标准化的 titled/untitled/单/多选与 URL 模式；</li>
<li class="">sampling 支持工具调用；新增 <strong>OAuth Client ID Metadata Documents</strong> 注册机制；</li>
<li class="">引入<strong>实验性 Tasks</strong>；确立 <strong>JSON Schema 2020-12</strong> 为默认方言。</li>
</ul>
<p><strong><code>2026-07-28</code>：无状态化范式修订（当前版本）</strong></p>
<p>被称为「发布以来最大修订」，要点：<strong>无状态核心、移除握手与 session、<code>server/discover</code>、MRTR、订阅流、扩展框架、授权加固、正式弃用策略</strong>。详见第四章。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="25治理">2.5、治理<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#25%E6%B2%BB%E7%90%86" class="hash-link" aria-label="2.5、治理的直接链接" title="2.5、治理的直接链接" translate="no">​</a></h3>
<ul>
<li class="">2025 年起，Microsoft、GitHub、OpenAI 等先后加入 MCP 指导委员会；</li>
<li class="">2025-12-09，Anthropic 将 MCP <strong>捐赠</strong>给 Linux 基金会下新建的 <strong>Agentic AI Foundation（AAIF）</strong>（由 Anthropic、Block、OpenAI 共同发起），官方声明治理模型不变；</li>
<li class="">规范采用 <strong>SEP（Specification Enhancement Proposal）</strong> 的 PR 工作流。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三核心概念">三、核心概念<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B8%89%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="三、核心概念的直接链接" title="三、核心概念的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="31host--client--server-三角色">3.1、Host / Client / Server 三角色<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#31host--client--server-%E4%B8%89%E8%A7%92%E8%89%B2" class="hash-link" aria-label="3.1、Host / Client / Server 三角色的直接链接" title="3.1、Host / Client / Server 三角色的直接链接" translate="no">​</a></h3>
<p>MCP 采用 <strong>client-host-server</strong> 架构，注意这里有两个「C」，容易混淆：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────────────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Host 进程（AI 应用，如 Claude Desktop / IDE / Agent 框架） │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── Client 1  ──1:1──►  Server 1（本地文件 &amp; Git）       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── Client 2  ──1:1──►  Server 2（数据库）               │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── Client 3  ──1:1──►  Server 3（远程 SaaS API）        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────────────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>Host（宿主）</strong>：创建并管理多个 Client，控制连接权限与生命周期，执行安全策略与用户授权决策，聚合上下文给 LLM。</li>
<li class=""><strong>Client（客户端）</strong>：由 Host 创建，<strong>与某一个 Server 严格 1:1</strong>；对每个请求附带协议版本与能力；双向路由消息；管理订阅与通知；维持 Server 间的安全边界。</li>
<li class=""><strong>Server（服务端）</strong>：通过原语对外暴露具体能力，独立、单一职责；可以是本地进程，也可以是远程服务。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：MCP 是<strong>无状态协议</strong>——每个请求自带处理所需的全部信息（版本、能力、身份），Server 不得依赖连接或先前请求推断状态。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="32三大-server-原语与控制权">3.2、三大 Server 原语与控制权<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#32%E4%B8%89%E5%A4%A7-server-%E5%8E%9F%E8%AF%AD%E4%B8%8E%E6%8E%A7%E5%88%B6%E6%9D%83" class="hash-link" aria-label="3.2、三大 Server 原语与控制权的直接链接" title="3.2、三大 Server 原语与控制权的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>原语</th><th>由谁控制</th><th>用途</th><th>对应方法</th></tr></thead><tbody><tr><td><strong>Tools</strong></td><td><strong>模型控制</strong>（model-controlled）</td><td>可被 LLM 调用的函数</td><td><code>tools/list</code>、<code>tools/call</code></td></tr><tr><td><strong>Resources</strong></td><td><strong>应用控制</strong>（application-controlled）</td><td>可读上下文，按 URI 标识</td><td><code>resources/list</code>、<code>resources/read</code></td></tr><tr><td><strong>Prompts</strong></td><td><strong>用户控制</strong>（user-controlled）</td><td>可复用的提示词模板</td><td><code>prompts/list</code>、<code>prompts/get</code></td></tr></tbody></table>
<p>设计上三者的「控制权」明显不同：工具由模型自主决定调用，资源由应用决定何时注入上下文，提示词由用户显式选择。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="33四条设计原则">3.3、四条设计原则<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#33%E5%9B%9B%E6%9D%A1%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99" class="hash-link" aria-label="3.3、四条设计原则的直接链接" title="3.3、四条设计原则的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><strong>Server 应极易构建</strong>：复杂编排由 Host 承担，Server 只专注单一能力。</li>
<li class=""><strong>Server 应高度可组合</strong>：多个 Server 可无缝组合，共享同一协议。</li>
<li class=""><strong>Server 不能读整个对话、也看不到其他 Server</strong>：完整对话历史只留在 Host；Server 之间相互隔离，跨 Server 交互由 Host 控制。</li>
<li class=""><strong>特性可渐进添加</strong>：核心协议最小化，其他能力按需声明与协商。</li>
</ol>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="34能力协商capability-negotiation">3.4、能力协商（Capability Negotiation）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#34%E8%83%BD%E5%8A%9B%E5%8D%8F%E5%95%86capability-negotiation" class="hash-link" aria-label="3.4、能力协商（Capability Negotiation）的直接链接" title="3.4、能力协商（Capability Negotiation）的直接链接" translate="no">​</a></h3>
<p>能力协商是 MCP 的核心机制，但<strong>协商方式在 2026-07-28 发生了根本变化</strong>：</p>
<ul>
<li class=""><strong>旧模型（≤ 2025-11-25）</strong>：通过 <code>initialize</code> 握手，一次性交换并确定整个 session 的能力。</li>
<li class=""><strong>新模型（2026-07-28）</strong>：无握手；客户端在<strong>每个请求</strong>的 <code>_meta.io.modelcontextprotocol/clientCapabilities</code> 中声明能力；服务端通过 <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/discover" target="_blank" rel="noopener noreferrer" class=""><code>server/discover</code></a> 一次性公布其支持的版本与能力。</li>
</ul>
<p>典型能力：服务端 <code>tools</code> / <code>resources</code> / <code>prompts</code> / <code>logging</code> / <code>completions</code>；客户端 <code>roots</code> / <code>sampling</code> / <code>elicitation</code>。</p>
<blockquote>
<p><strong>重要规则</strong>：服务端 <strong>MUST NOT</strong> 依赖客户端未声明的能力；若处理某请求需要客户端未声明的能力，服务端 <strong>MUST</strong> 返回 <code>-32021 MissingRequiredClientCapability</code> 并在 <code>data.requiredCapabilities</code> 中列出。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四协议核心设计">四、协议核心设计<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%9B%9B%E5%8D%8F%E8%AE%AE%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="四、协议核心设计的直接链接" title="四、协议核心设计的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="41消息模型json-rpc-20">4.1、消息模型：JSON-RPC 2.0<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#41%E6%B6%88%E6%81%AF%E6%A8%A1%E5%9E%8Bjson-rpc-20" class="hash-link" aria-label="4.1、消息模型：JSON-RPC 2.0的直接链接" title="4.1、消息模型：JSON-RPC 2.0的直接链接" translate="no">​</a></h3>
<p>MCP 所有消息都遵循 <strong>JSON-RPC 2.0</strong>，且消息 <strong>MUST</strong> 为 UTF-8。消息类型：</p>
<div class="language-jsonc codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsonc codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 1) Request：请求，MUST 带非 null 的 id（不得与未响应的请求 id 重复）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { /* ... */ } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 2) Result Response：成功响应，MUST 带 resultType（2026-07-28 起）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 1, "result": { "resultType": "complete", "content": [ /* ... */ ] } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 3) Error Response：失败响应，MUST 带 code 与 message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params" } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 4) Notification：通知，MUST NOT 带 id</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>设计要点</strong>：MCP 规定「<strong>客户端只发请求/通知，服务端只发响应/通知</strong>」——服务端不主动发请求（2026-07-28 之后），客户端不发响应。这一约束是无状态与单向流设计的基石。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="42_meta请求级协议元数据">4.2、<code>_meta</code>：请求级协议元数据<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#42_meta%E8%AF%B7%E6%B1%82%E7%BA%A7%E5%8D%8F%E8%AE%AE%E5%85%83%E6%95%B0%E6%8D%AE" class="hash-link" aria-label="42_meta请求级协议元数据的直接链接" title="42_meta请求级协议元数据的直接链接" translate="no">​</a></h3>
<p>所有协议元数据随<strong>消息体</strong>携带（传输层可选地镜像到信封头）。客户端请求的 <code>_meta</code> 字段：</p>
<table><thead><tr><th>键</th><th>类型</th><th>必填</th><th>说明</th></tr></thead><tbody><tr><td><code>io.modelcontextprotocol/protocolVersion</code></td><td>string</td><td>是</td><td>本次请求使用的协议版本</td></tr><tr><td><code>io.modelcontextprotocol/clientCapabilities</code></td><td>object</td><td>是</td><td>客户端相关能力</td></tr><tr><td><code>io.modelcontextprotocol/clientInfo</code></td><td>object</td><td>否</td><td>客户端名称与版本（SHOULD）</td></tr><tr><td><code>io.modelcontextprotocol/logLevel</code></td><td>string</td><td>否</td><td>本请求的最小日志级别</td></tr></tbody></table>
<p>服务端 <strong>SHOULD</strong> 在每个结果的 <code>_meta</code> 中回 <code>io.modelcontextprotocol/serverInfo</code>。此外 <code>traceparent</code> / <code>tracestate</code> / <code>baggage</code> 被保留用于 OpenTelemetry 追踪上下文。</p>
<blockquote>
<p><strong>重点</strong>：<code>protocolVersion</code> 与 <code>clientCapabilities</code> 缺失即为<strong>畸形请求</strong>，服务端 <strong>MUST</strong> 以 <code>-32602</code> 拒绝（HTTP 上返回 <code>400 Bad Request</code>）。这就是「请求自描述、不依赖连接状态」的具体落地。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="43resulttype多态结果设计">4.3、<code>resultType</code>：多态结果设计<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#43resulttype%E5%A4%9A%E6%80%81%E7%BB%93%E6%9E%9C%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="43resulttype多态结果设计的直接链接" title="43resulttype多态结果设计的直接链接" translate="no">​</a></h3>
<p>2026-07-28 为所有结果引入<strong>必填</strong>的 <code>resultType</code>：</p>
<ul>
<li class=""><code>"complete"</code>：正常完成，<code>result</code> 含最终内容；</li>
<li class=""><code>"input_required"</code>：需要客户端补充输入（见 4.8 MRTR）；</li>
<li class="">扩展可新增其他 <code>resultType</code>，但必须来自核心集合或已声明扩展；</li>
<li class="">客户端遇无法识别的 <code>resultType</code> <strong>MUST</strong> 视为非法；</li>
<li class="">对不返回 <code>resultType</code> 的旧版服务端，客户端 <strong>MUST</strong> 按 <code>"complete"</code> 处理。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="44错误模型与错误码">4.4、错误模型与错误码<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#44%E9%94%99%E8%AF%AF%E6%A8%A1%E5%9E%8B%E4%B8%8E%E9%94%99%E8%AF%AF%E7%A0%81" class="hash-link" aria-label="4.4、错误模型与错误码的直接链接" title="4.4、错误模型与错误码的直接链接" translate="no">​</a></h3>
<p>MCP 将错误分为两类，<strong>处理方式不同</strong>：</p>
<table><thead><tr><th>类型</th><th>含义</th><th>返回方式</th><th>模型可自纠</th></tr></thead><tbody><tr><td><strong>Protocol Error</strong></td><td>请求结构问题（未知工具、畸形请求、服务端错误）</td><td>JSON-RPC <code>error</code> 对象</td><td>通常不能</td></tr><tr><td><strong>Tool Execution Error</strong></td><td>业务/API/校验错误</td><td><code>result.isError = true</code> + 文本</td><td><strong>能</strong></td></tr></tbody></table>
<p>错误码分区策略：</p>
<ul>
<li class=""><code>-32700</code>、<code>-32600</code>~<code>-32603</code>：标准 JSON-RPC 通用错误；</li>
<li class=""><code>-32000</code>~<code>-32019</code>：<strong>遗留</strong>实现自定义区（新实现不应再分配）；</li>
<li class=""><code>-32020</code>~<code>-32099</code>：<strong>保留给 MCP 规范</strong>（见 <a href="https://modelcontextprotocol.io/specification/2026-07-28/schema" target="_blank" rel="noopener noreferrer" class="">Schema 错误定义</a>）。2026-07-28 定义：</li>
</ul>
<table><thead><tr><th>码</th><th>名称</th></tr></thead><tbody><tr><td><code>-32020</code></td><td><code>HeaderMismatch</code></td></tr><tr><td><code>-32021</code></td><td><code>MissingRequiredClientCapability</code></td></tr><tr><td><code>-32022</code></td><td><code>UnsupportedProtocolVersion</code></td></tr></tbody></table>
<p>历史码 <code>-32002</code>（资源未找到，≤2025-11-25）被 <code>-32602</code> 取代，但客户端<strong>SHOULD</strong> 仍兼容旧服务端的 <code>-32002</code>。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="45传输设计">4.5、传输设计<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#45%E4%BC%A0%E8%BE%93%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="4.5、传输设计的直接链接" title="4.5、传输设计的直接链接" translate="no">​</a></h3>
<p>协议语义在所有传输上一致；传输只负责「分帧、投递、取消、终止」。标准传输有两种：</p>
<p><strong>stdio</strong>（<a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio" target="_blank" rel="noopener noreferrer" class="">规范</a>）</p>
<ul>
<li class="">Client 以子进程方式启动 Server，通过其标准流通信；</li>
<li class="">每个消息一行 JSON，<strong>不得内嵌换行</strong>；</li>
<li class="">Server <strong>MUST NOT</strong> 往 <code>stdout</code> 写非 MCP 消息；日志走 <code>stderr</code>；</li>
<li class="">取消：发 <code>notifications/cancelled</code>（单通道，无 per-request stream）；</li>
<li class="">关闭：关 stdin → 等退出 → <code>SIGTERM</code> → <code>SIGKILL</code>（Windows 用 <code>TerminateProcess</code>/Job Objects）。</li>
</ul>
<p><strong>Streamable HTTP（2026-07-28 形态）</strong>（<a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http" target="_blank" rel="noopener noreferrer" class="">规范</a>）</p>
<ul>
<li class="">单一 <strong>MCP endpoint</strong>，只用 <strong>POST</strong>；每个请求独立一个 POST；</li>
<li class="">请求头 MUST 含 <code>Accept: application/json, text/event-stream</code>、<code>MCP-Protocol-Version</code>、<code>Mcp-Method</code>，<code>tools/call</code>/<code>resources/read</code>/<code>prompts/get</code> 还要 <code>Mcp-Name</code>；</li>
<li class="">响应可为单个 JSON，或<strong>请求级 SSE 流</strong>（先推通知，最后一条是最终响应）；</li>
<li class="">取消 = 关闭该请求的响应流；</li>
<li class="">2026-07-28 <strong>移除</strong>：HTTP GET 流、<code>Mcp-Session-Id</code>、<code>Last-Event-ID</code> 可恢复性。</li>
</ul>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /mcp HTTP/1.1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Accept: application/json, text/event-stream</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">MCP-Protocol-Version: 2026-07-28</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Mcp-Method: tools/call</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Mcp-Name: get_weather</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  "params": { "name": "get_weather", "arguments": { "location": "Seattle, WA" },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               "io.modelcontextprotocol/clientCapabilities": {} } } }</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>弊端说明</strong>：旧版 HTTP+SSE（2024-11-05）自 2025-03-26 被取代，2026-07-28 正式归入 <strong>Deprecated</strong>，新实现不应采用。</p>
<p><strong>注意</strong>：<code>x-mcp-header</code> 允许把原始类型参数（string/integer/boolean，不含 number）镜像成 <code>Mcp-Param-{Name}</code> 头，便于网关路由；但头对中间网络可见，<strong>不得</strong>标记密码/Token/PII。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="46生命周期legacy-握手-vs-modern-逐请求">4.6、生命周期：legacy 握手 vs modern 逐请求<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#46%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9Flegacy-%E6%8F%A1%E6%89%8B-vs-modern-%E9%80%90%E8%AF%B7%E6%B1%82" class="hash-link" aria-label="4.6、生命周期：legacy 握手 vs modern 逐请求的直接链接" title="4.6、生命周期：legacy 握手 vs modern 逐请求的直接链接" translate="no">​</a></h3>
<p><strong>Legacy（≤ 2025-11-25）</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">initialize → 服务端返回能力 → notifications/initialized → 正常操作 → 关闭</span><br></div></code></pre></div></div>
<p><code>initialize</code> 请求/响应结构：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 请求</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"initialize"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2025-06-18"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"capabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"roots"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"listChanged"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"sampling"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"clientInfo"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ExampleClient"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2025-06-18"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"capabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"listChanged"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"serverInfo"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ExampleServer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><strong>Modern（2026-07-28）</strong>：无握手；版本与能力随每个请求的 <code>_meta</code> 携带；<code>server/discover</code> 可选，用于一次性获取版本/能力/身份。</p>
<p><strong>双时代兼容</strong>是现实刚需（新旧 Server 并存），官方给出探测机制：</p>
<ul>
<li class="">stdio：先发 <code>server/discover</code>；成功 → 现代；返回非现代错误或超时 → 回退 <code>initialize</code>；</li>
<li class="">HTTP：先发现代请求；遇 <code>400</code> 先看响应体——可识别的现代错误 → 现代；否则回退 <code>initialize</code>。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：时代判定是<strong>服务端属性</strong>，应缓存（stdio 按进程、HTTP 按 origin），避免每请求探测。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="47无状态化设计2026-07-28-核心">4.7、无状态化设计（2026-07-28 核心）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#47%E6%97%A0%E7%8A%B6%E6%80%81%E5%8C%96%E8%AE%BE%E8%AE%A12026-07-28-%E6%A0%B8%E5%BF%83" class="hash-link" aria-label="4.7、无状态化设计（2026-07-28 核心）的直接链接" title="4.7、无状态化设计（2026-07-28 核心）的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>变化</th><th>说明</th></tr></thead><tbody><tr><td>移除 session 与 <code>Mcp-Session-Id</code></td><td>请求可由任意 Server 实例处理，适配无状态部署</td></tr><tr><td>移除 <code>initialize</code>/<code>initialized</code> 握手</td><td>版本与能力改为每请求 <code>_meta</code></td></tr><tr><td>新增 <code>server/discover</code></td><td>一次性公布 supportedVersions / capabilities / identity</td></tr><tr><td>列表端点不再随连接变化</td><td><code>tools/list</code> 不因连接而异，但可因授权凭据而异</td></tr></tbody></table>
<p>无状态的直接要求：<strong>跨调用的状态</strong>必须由 Server <strong>显式</strong>返回一个 handle（如 <code>basket_id</code>），并在后续调用中由客户端作为普通参数传回。协议本身没有「状态句柄」概念——从线缆视角看，它只是一个普通字符串。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="48mrtr多轮往返请求">4.8、MRTR：多轮往返请求<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#48mrtr%E5%A4%9A%E8%BD%AE%E5%BE%80%E8%BF%94%E8%AF%B7%E6%B1%82" class="hash-link" aria-label="4.8、MRTR：多轮往返请求的直接链接" title="4.8、MRTR：多轮往返请求的直接链接" translate="no">​</a></h3>
<p>旧版允许 Server 通过 SSE 流<strong>主动</strong>发请求（<code>sampling/createMessage</code>、<code>elicitation/create</code>、<code>roots/list</code>）。2026-07-28 改为：</p>
<ul>
<li class="">Server 在响应里返回 <code>InputRequiredResult</code>（<code>resultType: "input_required"</code>），<code>inputRequests</code> 字段描述所需输入；</li>
<li class="">Client 补齐后，用<strong>新的 id</strong> 重放原请求，并在 <code>params.inputResponses</code> 中带回答案（可带 <code>requestState</code> 透传服务端状态）。</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Client ── tools/call(id:1) ──► Server</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Client ◄─ InputRequiredResult（inputRequests: elicitation/create）── Server</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Client ── tools/call(id:2, 原参数 + inputResponses) ──► Server</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Client ◄─ 最终 result ── Server</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：MRTR（<a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr" target="_blank" rel="noopener noreferrer" class="">Multi Round-Trip Requests</a>，SEP-2322）把「服务端反向请求」改造为「客户端主动重试」，从而与无状态模型自洽。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="49订阅与通知">4.9、订阅与通知<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#49%E8%AE%A2%E9%98%85%E4%B8%8E%E9%80%9A%E7%9F%A5" class="hash-link" aria-label="4.9、订阅与通知的直接链接" title="4.9、订阅与通知的直接链接" translate="no">​</a></h3>
<p>2026-07-28 用<strong>长生命周期的 POST 响应流</strong> <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions" target="_blank" rel="noopener noreferrer" class=""><code>subscriptions/listen</code></a> 取代旧的 GET 流与 <code>resources/subscribe</code>：</p>
<ul>
<li class="">Client 声明订阅类型（<code>toolsListChanged</code>、<code>promptsListChanged</code>、<code>resourcesListChanged</code>、<code>resourceSubscriptions</code>）；</li>
<li class="">Server 先回 <code>notifications/subscriptions/acknowledged</code>，随后在该流推送变更通知；</li>
<li class="">每条通知带 <code>_meta.io.modelcontextprotocol/subscriptionId</code> 关联来源订阅。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：请求级通知（<code>notifications/progress</code>、<code>notifications/message</code>）只走<strong>该请求自己的响应流</strong>。另外，2026-07-28 移除了 SSE 可恢复性，断流即丢请求。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="410列表与缓存">4.10、列表与缓存<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#410%E5%88%97%E8%A1%A8%E4%B8%8E%E7%BC%93%E5%AD%98" class="hash-link" aria-label="4.10、列表与缓存的直接链接" title="4.10、列表与缓存的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" target="_blank" rel="noopener noreferrer" class=""><code>tools/list</code></a>、<code>prompts/list</code>、<code>resources/list</code> 等支持<strong>分页</strong>（<code>cursor</code> / <code>nextCursor</code>）与<strong>缓存</strong>；</li>
<li class="">2026-07-28 要求列表/读取结果带 <code>ttlMs</code>（新鲜度提示）与 <code>cacheScope</code>（<code>public</code>/<code>private</code>，控制共享中间层是否可缓存）；</li>
<li class="">Server <strong>SHOULD</strong> 稳定排序，便于客户端缓存与 LLM Prompt Cache 命中；</li>
<li class="">工具定义包含 <code>name</code>、<code>title</code>、<code>description</code>、<code>inputSchema</code>（默认 JSON Schema 2020-12）、可选 <code>outputSchema</code>、<code>icons</code>、<code>annotations</code>。</li>
</ul>
<p><strong>工具定义与调用示例：</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// tools/list → tools[]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"title"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Weather Information Provider"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Get current weather information for a location"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"inputSchema"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"City name or zip code"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"location"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"outputSchema"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"temperature"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"number"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// tools/call 成功响应（非结构化文本 + 结构化内容并存）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"resultType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"complete"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"temperature\": 22.5, \"conditions\": \"Partly cloudy\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"structuredContent"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"temperature"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">22.5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"conditions"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Partly cloudy"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"isError"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：工具名 1~128 字符、区分大小写、仅允许 <code>A-Za-z0-9_-</code> 与 <code>.</code>，且在<strong>单个 Server 内唯一</strong>；<code>inputSchema</code> <strong>MUST</strong> 是合法 JSON Schema（不能为 <code>null</code>）；无参数工具推荐 <code>{ "type": "object", "additionalProperties": false }</code>。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五核心-mcp-client--server-的底层原理">五、核心 MCP Client / Server 的底层原理<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%BA%94%E6%A0%B8%E5%BF%83-mcp-client--server-%E7%9A%84%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86" class="hash-link" aria-label="五、核心 MCP Client / Server 的底层原理的直接链接" title="五、核心 MCP Client / Server 的底层原理的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>本节回答</strong>：抛开框架，MCP 客户端/服务端内部到底怎么跑？</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="51消息编解码与请求关联">5.1、消息编解码与请求关联<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#51%E6%B6%88%E6%81%AF%E7%BC%96%E8%A7%A3%E7%A0%81%E4%B8%8E%E8%AF%B7%E6%B1%82%E5%85%B3%E8%81%94" class="hash-link" aria-label="5.1、消息编解码与请求关联的直接链接" title="5.1、消息编解码与请求关联的直接链接" translate="no">​</a></h3>
<p>无论是 stdio 还是 HTTP，客户端核心都是一张 <strong><code>id → 等待中的 Future</code></strong> 路由表：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">发送请求：  id = nextId++; pending.put(id, future);  transport.send(jsonRpcRequest)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">收到消息：  if 有 id 且 有 result/error  → pending.remove(id).complete(...)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">           if 无 id 且 method=notifications/* → 交给通知分发器</span><br></div></code></pre></div></div>
<p>以 LangChain4j 为例，<code>McpOperationHandler</code> 内部就是 <code>Map&lt;Long, CompletableFuture&lt;JsonNode&gt;&gt;</code>：收到「带 id 且带 result/error」的消息就完成对应 Future；收到 <code>ping</code> 请求就回 <code>McpPingResponse</code>；收到 <code>notifications/message</code> 就转给日志消费者。</p>
<blockquote>
<p><strong>重点</strong>：协议是<strong>异步请求-响应</strong>，但实际使用通常暴露同步方法（<code>CompletableFuture.get()</code> 或阻塞等待），所以「超时」必须在客户端强制实现——这是 MCP 规范明确建议的。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="52客户端分层">5.2、客户端分层<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#52%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%88%86%E5%B1%82" class="hash-link" aria-label="5.2、客户端分层的直接链接" title="5.2、客户端分层的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌──────────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Client / Protocol 层                          │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  版本协商、能力声明、id→Future 路由、超时       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├──────────────────────────────────────────────┤</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  Transport 层（stdio / Streamable HTTP）       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│  消息分帧、收发、取消、关闭                     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└──────────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<p>关键点：同步/异步的选择、超时与取消、双时代探测与缓存、子进程重启与请求重试。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="53传输实现拆解">5.3、传输实现拆解<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#53%E4%BC%A0%E8%BE%93%E5%AE%9E%E7%8E%B0%E6%8B%86%E8%A7%A3" class="hash-link" aria-label="5.3、传输实现拆解的直接链接" title="5.3、传输实现拆解的直接链接" translate="no">​</a></h3>
<p><strong>stdio：</strong> 用 <code>ProcessBuilder</code> 启动子进程，两个后台线程分别读 <code>stdout</code> / 写 <code>stdin</code>；消息按行分帧；<code>stderr</code> 仅日志；进程退出时上层可重启并重试丢失请求。</p>
<p><strong>Streamable HTTP：</strong> 基于普通 HTTP 客户端，POST 单一 endpoint；根据响应的 <code>Content-Type</code> 决定「JSON 直读」还是「SSE 流式逐条读，最后一条为最终响应」；取消即关闭响应流。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="54服务端toolslist-与-toolscall">5.4、服务端：<code>tools/list</code> 与 <code>tools/call</code><a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#54%E6%9C%8D%E5%8A%A1%E7%AB%AFtoolslist-%E4%B8%8E-toolscall" class="hash-link" aria-label="54服务端toolslist-与-toolscall的直接链接" title="54服务端toolslist-与-toolscall的直接链接" translate="no">​</a></h3>
<p>服务端要做的事：解析 JSON-RPC → 按 <code>method</code> 分发 → 调用业务实现 → 封装标准响应/错误。要点：</p>
<ul>
<li class="">声明能力：<code>{ "capabilities": { "tools": { "listChanged": true } } }</code>（旧版在 <code>initialize</code> 结果、新版在 <code>server/discover</code> 结果）；</li>
<li class=""><code>tools/list</code> 返回工具定义（支持分页/缓存、稳定排序）；</li>
<li class=""><code>tools/call</code> 执行并返回 <code>content</code> / <code>structuredContent</code> / <code>isError</code>；</li>
<li class="">严格区分「协议错误（JSON-RPC error）」与「工具执行错误（<code>isError=true</code>）」。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="55参考实现的层次结构">5.5、参考实现的层次结构<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#55%E5%8F%82%E8%80%83%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84" class="hash-link" aria-label="5.5、参考实现的层次结构的直接链接" title="5.5、参考实现的层次结构的直接链接" translate="no">​</a></h3>
<p><strong>LangChain4j（Java）：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">McpToolProvider (ToolProvider)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      │  聚合 1..N 个 McpClient，过滤/改名</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">DefaultMcpClient (McpClient)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      │  管理 JSON-RPC 生命周期（initialize、listTools、callTool）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">McpTransport（Stdio / StreamableHttp / WebSocket / Docker）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      │  只负责消息收发</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>McpToolExecutor</code> 实现框架的 <code>ToolExecutor</code>，内部调用 <code>McpClient.executeTool(...)</code>；</li>
<li class=""><code>ToolSpecificationHelper</code> 负责 MCP <code>inputSchema</code> ↔ 框架 <code>ToolSpecification</code> 的转换（支持 object/array/enum/anyOf）。</li>
</ul>
<p><strong>Spring AI（Java）：</strong></p>
<ul>
<li class=""><code>SyncMcpToolCallback(client, tool)</code> / <code>AsyncMcpToolCallback</code> 把 MCP 工具适配成 <code>ToolCallback</code>；</li>
<li class=""><code>SyncMcpToolCallbackProvider</code> 从多个 <code>McpSyncClient</code> 聚合工具。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="56主流对接模式对比">5.6、主流「对接模式」对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#56%E4%B8%BB%E6%B5%81%E5%AF%B9%E6%8E%A5%E6%A8%A1%E5%BC%8F%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="5.6、主流「对接模式」对比的直接链接" title="5.6、主流「对接模式」对比的直接链接" translate="no">​</a></h3>
<p><strong>方式 A：框架侧工具适配器（Adapter / Provider）——主流</strong></p>
<p>客户端 SDK 里实现一个「MCP 工具 → 框架工具」的适配层（即 5.5 中的模式）。</p>
<ul>
<li class="">优点：接入成本低、动态发现、与框架工具系统复用同一套循环；</li>
<li class="">缺点：需要框架侧实现协议客户端与 Schema 映射。</li>
</ul>
<p><strong>方式 B：MCP 网关 / 注册中心聚合——平台化</strong></p>
<p>用网关（如 AWS Bedrock AgentCore Gateway）或注册中心（MCP Registry）把多个 MCP Server 聚合到统一入口。</p>
<ul>
<li class="">优点：集中治理、鉴权、限流、可观测；</li>
<li class="">缺点：引入网关运维成本；多一层网络跳数。</li>
</ul>
<p><strong>方式 C：动态物化为本地工具——取巧</strong></p>
<p>运行时拉取 <code>tools/list</code>，用动态代理或代码生成把 MCP 工具伪装成本地方法。</p>
<ul>
<li class="">优点：对上层透明；</li>
<li class="">缺点：难以承载复杂 JSON Schema 参数；调试不直观；不推荐长期使用。</li>
</ul>
<p><strong>方式 D：MCP Server 升级为独立 Runtime / SubAgent</strong></p>
<p>把 Server 当作独立 Agent 运行时，主 Agent 以「任务委派」方式调用（类似 A2A）。</p>
<ul>
<li class="">优点：适合复杂、有状态、长任务；</li>
<li class="">缺点：重量级，背离 MCP「简单 Server」的初衷。</li>
</ul>
<blockquote>
<p><strong>结论</strong>：绝大多数场景选 <strong>方式 A</strong>，这是投入产出比最高的路线。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="57基于-java-快速搭建-mcp-client--server">5.7、基于 Java 快速搭建 MCP Client / Server<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#57%E5%9F%BA%E4%BA%8E-java-%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA-mcp-client--server" class="hash-link" aria-label="5.7、基于 Java 快速搭建 MCP Client / Server的直接链接" title="5.7、基于 Java 快速搭建 MCP Client / Server的直接链接" translate="no">​</a></h3>
<blockquote>
<p>用官方 <strong>MCP Java SDK</strong>（<code>io.modelcontextprotocol.sdk</code>）可以在几十行内跑通 Client/Server。要点：SDK 要求 <strong>JDK 17+</strong>；核心模块 <code>mcp</code> 内置 <strong>STDIO / Streamable HTTP / SSE(legacy)</strong> 三种传输；客户端默认用 JDK <code>HttpClient</code>，JSON 默认 Jackson，服务端 core 内置 Servlet 传输；提供同步 / 异步双 API（异步基于 Reactor）。</p>
</blockquote>
<p><strong>1）引入依赖</strong></p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">dependencyManagement</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">dependencies</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">dependency</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">io.modelcontextprotocol.sdk</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">mcp-bom</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">2.0.1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">version</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">type</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">pom</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">type</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">scope</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">import</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">scope</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">dependency</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">dependencies</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">dependencyManagement</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">dependency</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">io.modelcontextprotocol.sdk</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">groupId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">mcp</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">artifactId</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">dependency</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></div></code></pre></div></div>
<p><strong>2）Client：连接本地 stdio Server 并调用工具</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 1. 传输：启动本地子进程（例如官方 everything server）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ServerParameters</span><span class="token plain"> params </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">ServerParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"npx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"-y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@modelcontextprotocol/server-everything"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"dir"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">McpTransport</span><span class="token plain"> transport </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">StdioClientTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">params</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">McpJsonDefaults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMapper</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. 同步客户端 + 能力 + 超时</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">McpSyncClient</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">sync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">transport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">requestTimeout</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Duration</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ofSeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">capabilities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ClientCapabilities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">roots</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">sampling</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">elicitation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 3. 初始化（完成版本/能力协商）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">InitializeResult</span><span class="token plain"> init </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">initialize</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">String</span><span class="token plain"> version </span><span class="token operator">=</span><span class="token plain"> init</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">protocolVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 4. 发现工具 + 调用工具</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ListToolsResult</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">listTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">CallToolResult</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">callTool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">CallToolRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"calculator"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">of</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"operation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"add"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"a"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"b"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 5. 优雅关闭</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">closeGracefully</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>连接远程 Server 只需换传输（Streamable HTTP）：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">McpTransport</span><span class="token plain"> transport </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">HttpClientStreamableHttpTransport</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"http://your-mcp-server"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">endpoint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>listTools()</code> / <code>listResources()</code> / <code>listPrompts()</code> 都支持分页，需循环直到 <code>nextCursor == null</code> 才能拿全量工具。</p>
</blockquote>
<p><strong>3）Server：暴露一个工具</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">StdioServerTransportProvider</span><span class="token plain"> transportProvider </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">StdioServerTransportProvider</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">McpJsonDefaults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMapper</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">McpSyncServer</span><span class="token plain"> server </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpServer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">sync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">transportProvider</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">serverInfo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"my-server"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">capabilities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ServerCapabilities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">var</span><span class="token plain"> calculator </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">SyncToolSpecification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"calculator"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> inputSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Basic calculator"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">callHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">exchange</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> a </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"a"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> b </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"b"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">CallToolResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">List</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">of</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">McpSchema</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token class-name">TextContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Result: "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">a </span><span class="token operator">+</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addTool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">calculator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>暴露为远程 HTTP 服务时，把传输换成 core 内置的 <code>HttpServletStreamableServerTransportProvider</code>（或 Spring WebMVC / WebFlux 提供者），并把 MCP endpoint 注册到 <code>/mcp</code>。</p>
<p><strong>4）三种传输的 Java 对照</strong></p>
<table><thead><tr><th>传输</th><th>Client 类</th><th>Server 类</th><th>备注</th></tr></thead><tbody><tr><td>STDIO</td><td><code>StdioClientTransport</code></td><td><code>StdioServerTransportProvider</code></td><td>本地子进程</td></tr><tr><td>Streamable HTTP</td><td><code>HttpClientStreamableHttpTransport</code></td><td><code>HttpServletStreamableServerTransportProvider</code></td><td>远程；Spring 另有 WebMVC/WebFlux</td></tr><tr><td>SSE（legacy）</td><td><code>HttpClientSseClientTransport</code></td><td><code>HttpServletSseServerTransportProvider</code></td><td>已弃用，仅兼容旧 Server</td></tr></tbody></table>
<p><strong>5）更上层的一键方案</strong></p>
<ul>
<li class=""><strong>Spring AI</strong>：Boot Starter + <code>@McpTool</code> / <code>@McpToolParam</code> 注解 + <code>SyncMcpToolCallback</code>，把 MCP 工具直接适配成 Spring 的工具回调。</li>
<li class=""><strong>AgentScope Java</strong>：<code>McpClientBuilder</code> + <code>Toolkit.registerMcpClient(...)</code>，见第六章。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="58java-生态实现选型">5.8、Java 生态实现选型<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#58java-%E7%94%9F%E6%80%81%E5%AE%9E%E7%8E%B0%E9%80%89%E5%9E%8B" class="hash-link" aria-label="5.8、Java 生态实现选型的直接链接" title="5.8、Java 生态实现选型的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>方案</th><th>定位</th><th>连接</th><th>工具适配</th><th>适合</th></tr></thead><tbody><tr><td><strong>官方 MCP Java SDK</strong></td><td>协议层参考实现</td><td><code>McpClient</code> / <code>McpServer</code></td><td>自行适配</td><td>需要完全掌控协议细节</td></tr><tr><td><strong>Spring AI</strong></td><td>Spring 生态集成</td><td><code>McpSyncClient</code> / Starter</td><td><code>SyncMcpToolCallback</code> / <code>@McpTool</code></td><td>Spring Boot 应用</td></tr><tr><td><strong>LangChain4j</strong></td><td>LLM 应用框架</td><td><code>McpTransport</code> + <code>DefaultMcpClient</code></td><td><code>McpToolExecutor</code> / <code>McpToolProvider</code></td><td>已用 LangChain4j 的 AI Service</td></tr><tr><td><strong>AgentScope Java</strong></td><td>多 Agent 框架</td><td><code>McpClientBuilder</code></td><td><code>Toolkit.registerMcpClient</code></td><td>多 Agent 编排、需要工具治理</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：选型不看「谁更全」，而看「你的 Agent 运行在哪个生态」。协议行为由 SDK 保证，差异主要在工具治理、注解体验与编排能力。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六对接-agent-的-mcp-设计流程">六、对接 Agent 的 MCP 设计流程<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%85%AD%E5%AF%B9%E6%8E%A5-agent-%E7%9A%84-mcp-%E8%AE%BE%E8%AE%A1%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="六、对接 Agent 的 MCP 设计流程的直接链接" title="六、对接 Agent 的 MCP 设计流程的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>本节回答</strong>：把一个或多个 MCP Server 接进 Agent 时，应该按什么流程设计？参考 <strong>AgentScope（Java）</strong> 的实现，可归纳为「连接 → 发现 → 映射 → 执行 → 生命周期 → 安全」六步。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="61总体流程">6.1、总体流程<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#61%E6%80%BB%E4%BD%93%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="6.1、总体流程的直接链接" title="6.1、总体流程的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">① 连接 Connect       选传输、建 Client、初始化/能力协商、超时</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">② 发现 Discover      tools/list（分页）→ 工具清单</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">③ 映射 Map           MCP Tool → Agent 工具定义（inputSchema 透传 + 命名空间消歧）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">④ 执行 Execute       Agent 工具循环 → tools/call → content/isError → 回填</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">⑤ 生命周期 Lifecycle listChanged/subscriptions、连接复用、关闭/重启</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">⑥ 安全 Governance     审批、白名单、Tool Poisoning、凭据</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="62两个关键时机发现期拉取-vs-执行期调用">6.2、两个关键时机：发现期拉取 vs 执行期调用<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#62%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E6%97%B6%E6%9C%BA%E5%8F%91%E7%8E%B0%E6%9C%9F%E6%8B%89%E5%8F%96-vs-%E6%89%A7%E8%A1%8C%E6%9C%9F%E8%B0%83%E7%94%A8" class="hash-link" aria-label="6.2、两个关键时机：发现期拉取 vs 执行期调用的直接链接" title="6.2、两个关键时机：发现期拉取 vs 执行期调用的直接链接" translate="no">​</a></h3>
<blockquote>
<p><strong>核心结论</strong>：<code>tools/list</code>（拉取工具）发生在 <strong>Agent 启动 / 注册期</strong>，进主循环之前；<code>tools/call</code>（调用工具）发生在 <strong>每轮 ReAct 的 Act 阶段</strong>，模型选中工具之后。二者时机不同、频次也不同。</p>
</blockquote>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">【启动 / 注册期 · 通常只做一次】</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  connect → initialize / server.discover（版本 + 能力协商）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     └─ tools/list ──► 消歧 · 过滤 · 分组 ──► 注册进 Agent 的“工具表”（缓存）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        （此表即后续每轮提供给模型的工具声明）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">【每次 run · 每轮对话循环】</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  loop {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    Think：组装请求（把缓存的工具声明序列化进 LLM 的 tools[]）→ 调 LLM</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">             ├─ 模型未选工具 → 结束本轮</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">             └─ 模型返回 tool_call(s)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    Act：  按 name 查“工具表” → tools/call ──► MCP Server 执行</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">             └─ content / structuredContent / isError 写回 memory → 回到 Think</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">【运行期 · 可选】</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  notifications/tools/list_changed / subscriptions/listen</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     └─ 重新 tools/list → 刷新工具表（一般安排在轮次之间，避免打断推理）</span><br></div></code></pre></div></div>
<p><strong>几个容易混淆的点：</strong></p>
<ol>
<li class=""><strong><code>tools/list</code> 不是每轮都发</strong>：发现一次后缓存；模型每轮请求用的都是这份缓存，不会反复调用协议。</li>
<li class=""><strong>「把工具声明给模型」≠ <code>tools/list</code></strong>：Think 阶段只是把缓存声明序列化进 LLM 请求的 <code>tools[]</code> 字段，属于本地数据组装。</li>
<li class=""><strong><code>tools/call</code> 只在模型选中工具时发生</strong>：一轮响应可能包含 0 个、1 个或多个 tool_call；Act 阶段逐个（或并行）执行。</li>
<li class=""><strong>刷新时机</strong>：仅当服务端声明 <code>listChanged</code>（或订阅 <code>subscriptions/listen</code>）时才需重新发现；刷新安排在轮次之间，避免中途改变工具集导致模型上下文不一致。</li>
<li class=""><strong>懒发现变体</strong>：少数框架在首次会话 / 首次用到时才拉取，或按分组懒激活子集；但「先发现、后执行」的骨架不变。</li>
</ol>
<p><strong>时机对照表：</strong></p>
<table><thead><tr><th>动作</th><th>时机</th><th>频次</th><th>触发者</th></tr></thead><tbody><tr><td><code>connect</code> / <code>initialize</code></td><td>Agent 启动</td><td>一次</td><td>Agent 初始化</td></tr><tr><td><code>tools/list</code>（拉取）</td><td>注册期（循环前）</td><td>一次；<code>listChanged</code> 时再拉</td><td>Agent 初始化 / 变更通知</td></tr><tr><td>工具声明进 LLM 请求</td><td>每轮 Think</td><td>每轮</td><td>每次模型调用</td></tr><tr><td><code>tools/call</code>（调用）</td><td>每轮 Act</td><td>有 tool_call 时</td><td>模型选择 + 工具循环</td></tr><tr><td><code>close</code></td><td>Agent 销毁</td><td>一次</td><td>生命周期管理</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="63步骤一连接connect">6.3、步骤一：连接（Connect）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#63%E6%AD%A5%E9%AA%A4%E4%B8%80%E8%BF%9E%E6%8E%A5connect" class="hash-link" aria-label="6.3、步骤一：连接（Connect）的直接链接" title="6.3、步骤一：连接（Connect）的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>选传输</strong>：本地工具用 stdio（子进程），远程用 Streamable HTTP，仅兼容旧服务端才用 SSE。</li>
<li class=""><strong>建 Client</strong>：声明能力（roots / sampling / elicitation）、设置请求超时与初始化超时。</li>
<li class=""><strong>版本兼容</strong>：新规范为逐请求 <code>_meta</code>；兼容旧服务端时做双时代探测（见 4.6）。AgentScope 通过 <code>protocolVersions(...)</code> 显式声明支持的版本：</li>
</ul>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">McpClientWrapper</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">McpClientBuilder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">create</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"filesystem-mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stdioTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"npx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"-y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"@modelcontextprotocol/server-filesystem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/tmp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">timeout</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Duration</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ofSeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">120</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">initializationTimeout</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Duration</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ofSeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">30</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">protocolVersions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"2024-11-05"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2025-03-26"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2025-06-18"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildAsync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">block</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：AgentScope 默认只声明 <code>2024-11-05</code>，连接较新的 Server 必须显式 <code>protocolVersions(...)</code>，否则会报 "Unsupported protocol version"。HTTP 传输可另加 <code>.header("Authorization", "Bearer ...")</code> 注入凭据。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="64步骤二工具发现discover">6.4、步骤二：工具发现（Discover）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#64%E6%AD%A5%E9%AA%A4%E4%BA%8C%E5%B7%A5%E5%85%B7%E5%8F%91%E7%8E%B0discover" class="hash-link" aria-label="6.4、步骤二：工具发现（Discover）的直接链接" title="6.4、步骤二：工具发现（Discover）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">用 <code>tools/list</code> 拉全量（<strong>分页循环</strong>）；服务端声明 <code>listChanged</code> 时可订阅变更。</li>
<li class=""><strong>命名空间消歧</strong>：AgentScope 用 <code>mcp__{server_name}__{tool_name}</code>，避免多 Server 重名（例如两个 Server 都有 <code>search</code>）。</li>
<li class=""><strong>过滤</strong>：白名单 / 黑名单（<code>enableTools</code> / <code>disableTools</code>），做最小暴露。</li>
<li class=""><strong>分组</strong>：按 Server 或用途分组，按需激活（tool groups），减少一次性塞给模型的工具数量。</li>
</ul>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">Toolkit</span><span class="token plain"> toolkit </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Toolkit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolkit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">registerMcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">mcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">block</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">                 </span><span class="token comment" style="color:rgb(98, 114, 164)">// 全量注册</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 或过滤 + 分组</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolkit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">registration</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">mcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">mcpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">enableTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">List</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">of</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"read_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"list_directory"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">disableTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">List</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">of</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"delete_file"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">group</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"filesystem"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="65步骤三映射为-agent-工具本质是转换成-function-calling-协议">6.5、步骤三：映射为 Agent 工具——本质是转换成 Function Calling 协议<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#65%E6%AD%A5%E9%AA%A4%E4%B8%89%E6%98%A0%E5%B0%84%E4%B8%BA-agent-%E5%B7%A5%E5%85%B7%E6%9C%AC%E8%B4%A8%E6%98%AF%E8%BD%AC%E6%8D%A2%E6%88%90-function-calling-%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="6.5、步骤三：映射为 Agent 工具——本质是转换成 Function Calling 协议的直接链接" title="6.5、步骤三：映射为 Agent 工具——本质是转换成 Function Calling 协议的直接链接" translate="no">​</a></h3>
<blockquote>
<p><strong>核心结论</strong>：MCP 工具接入 Agent 的本质，就是一次<strong>协议转换</strong>——把 MCP <code>tools/list</code> 拉到的工具，转换成当前模型 Provider 的 <strong>Function Calling 协议</strong>（OpenAI 的 <code>tools[].function</code> / Anthropic 的 <code>tools[]</code>），随每次 LLM 请求下发给模型选择；模型返回 <code>tool_call</code> 后，再<strong>反向映射</strong>回 MCP <code>tools/call</code>。一句话：<strong>MCP 管「来源与执行」，Function Calling 管「呈现与选择」</strong>。</p>
</blockquote>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">MCP Server ──tools/list──► 工具清单 Tool{name, description, inputSchema}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 │  适配层：MCP → Function Calling</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                   模型 Function-Calling schema</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                   （OpenAI tools[].function / Anthropic tools[]）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 │  随每次 LLM 请求下发</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          LLM 选择 → tool_call</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 │  适配层：反向解析 name + arguments</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">MCP Server ◄──tools/call── 原始工具名 + arguments</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                 │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    CallToolResult ──► tool result 消息 ──► 回灌 LLM</span><br></div></code></pre></div></div>
<p><strong>正向映射（MCP Tool → Function Calling）：</strong></p>
<table><thead><tr><th>MCP Tool（来源）</th><th>OpenAI</th><th>Anthropic</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>function.name</code></td><td><code>name</code></td></tr><tr><td><code>description</code></td><td><code>function.description</code></td><td><code>description</code></td></tr><tr><td><code>inputSchema</code>（JSON Schema 2020-12）</td><td><code>function.parameters</code></td><td><code>input_schema</code></td></tr><tr><td><code>outputSchema</code></td><td>一般忽略（或用于 strict）</td><td>忽略</td></tr><tr><td><code>annotations</code> / <code>icons</code></td><td>部分映射</td><td>部分映射</td></tr></tbody></table>
<blockquote>
<p><code>inputSchema</code> 本身就是 JSON Schema，几乎可直接透传为 <code>parameters</code> / <code>input_schema</code>——这正是 MCP 与 Function Calling 能低成本对接的关键。</p>
</blockquote>
<p><strong>反向映射（模型 tool_call → MCP <code>tools/call</code>）：</strong></p>
<table><thead><tr><th>模型返回</th><th>处理</th></tr></thead><tbody><tr><td><code>name</code></td><td>去掉消歧前缀 → 还原成 MCP 原始工具名</td></tr><tr><td><code>arguments</code>（OpenAI 是 JSON <strong>字符串</strong>；Anthropic <code>input</code> 是 <strong>对象</strong>）</td><td>归一化成 Map → <code>tools/call</code> 的 <code>arguments</code></td></tr><tr><td><code>id</code> / <code>tool_use_id</code></td><td>保留，用于把结果关联回该次调用</td></tr></tbody></table>
<p><strong>结果回灌（MCP CallToolResult → 模型 tool result）：</strong></p>
<table><thead><tr><th>MCP 返回</th><th>转成</th></tr></thead><tbody><tr><td><code>content[]</code>（text / image / audio / resource）</td><td>ToolMessage / <code>tool_result</code> block 的内容</td></tr><tr><td><code>structuredContent</code></td><td>结构化输出链路（或序列化进文本）</td></tr><tr><td><code>isError = true</code></td><td>标记该 tool result 为错误，交给模型自纠</td></tr></tbody></table>
<p><strong>几个容易踩的点：</strong></p>
<ol>
<li class=""><strong>这是格式转换，不是协议翻译</strong>：各家的 Function Calling 字段名不同，<code>inputSchema → parameters / input_schema</code> 由 Provider Adapter 负责。</li>
<li class=""><strong>每次 LLM 请求都要带工具声明</strong>（Chat API 无状态），但 <code>tools/list</code> 只拉一次并缓存——两者别混。</li>
<li class=""><strong>名字要消歧</strong>：多 Server 聚合后，模型看到的 <code>name</code> 与 MCP 原始 <code>name</code> 可能不一致，需维护映射表。</li>
<li class=""><strong>arguments 形态不一</strong>：OpenAI 是 JSON 字符串、Anthropic 是对象，映射时要归一化。</li>
<li class=""><strong>MCP 不负责「模型选工具」</strong>：选择靠模型的 Function Calling 能力，MCP 只保证「工具定义统一 + 执行统一」。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="66步骤四执行与回填execute">6.6、步骤四：执行与回填（Execute）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#66%E6%AD%A5%E9%AA%A4%E5%9B%9B%E6%89%A7%E8%A1%8C%E4%B8%8E%E5%9B%9E%E5%A1%ABexecute" class="hash-link" aria-label="6.6、步骤四：执行与回填（Execute）的直接链接" title="6.6、步骤四：执行与回填（Execute）的直接链接" translate="no">​</a></h3>
<ul>
<li class="">Agent 的 ReAct/Tool 循环选中工具后 → 发起 <code>tools/call(name, arguments)</code>。</li>
<li class="">结果处理三条分流：<!-- -->
<ul>
<li class=""><code>content</code>（text / image / audio / resource）→ 拼接为文本回灌模型；</li>
<li class=""><code>structuredContent</code> → 透传给支持结构化输出的链路；</li>
<li class=""><code>isError = true</code> → 作为「<strong>工具执行错误</strong>」回给模型自纠（<strong>不要</strong>抛异常中断对话）；</li>
<li class="">JSON-RPC <code>error</code>（<strong>协议错误</strong>）→ 走框架错误处理链路。</li>
</ul>
</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Agent tool loop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │ 选中工具 github__search_repos</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">MCP callTool("search_repos", args)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">result.content / structuredContent / isError</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">回填对话，模型继续下一轮</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="67步骤五生命周期与热更新lifecycle">6.7、步骤五：生命周期与热更新（Lifecycle）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#67%E6%AD%A5%E9%AA%A4%E4%BA%94%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E4%B8%8E%E7%83%AD%E6%9B%B4%E6%96%B0lifecycle" class="hash-link" aria-label="6.7、步骤五：生命周期与热更新（Lifecycle）的直接链接" title="6.7、步骤五：生命周期与热更新（Lifecycle）的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>连接复用</strong>：一个 Client 长连接服务整个 Agent 生命周期。</li>
<li class=""><strong>热更新</strong>：<code>notifications/tools/list_changed</code> → 重新 <code>tools/list</code>；2026-07-28 用 <code>subscriptions/listen</code> 订阅变更。</li>
<li class=""><strong>关闭</strong>：stdio 关 stdin → 等待 → 强杀；HTTP 关闭连接。</li>
<li class=""><strong>容错</strong>：stdio 子进程崩溃自动重启并重试；HTTP 超时重试。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="68步骤六安全与治理governance">6.8、步骤六：安全与治理（Governance）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#68%E6%AD%A5%E9%AA%A4%E5%85%AD%E5%AE%89%E5%85%A8%E4%B8%8E%E6%B2%BB%E7%90%86governance" class="hash-link" aria-label="6.8、步骤六：安全与治理（Governance）的直接链接" title="6.8、步骤六：安全与治理（Governance）的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>Human-in-the-loop</strong>：敏感工具调用前审批（AgentScope 有 Permission / Hook 体系，其他框架可用中间件实现）。</li>
<li class=""><strong>最小暴露</strong>：只注册必要的工具（白名单优先）。</li>
<li class=""><strong>Tool Poisoning</strong>：工具描述与注解一律作为不可信输入。</li>
<li class=""><strong>凭据</strong>：HTTP 传输用 OAuth / API Key（<code>header(...)</code>），stdio 用环境变量。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="69参考实现对比">6.9、参考实现对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#69%E5%8F%82%E8%80%83%E5%AE%9E%E7%8E%B0%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="6.9、参考实现对比的直接链接" title="6.9、参考实现对比的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>框架</th><th>连接</th><th>发现 / 注册</th><th>执行适配</th><th>特色</th></tr></thead><tbody><tr><td><strong>AgentScope Java</strong></td><td><code>McpClientBuilder</code>（StdIO / SSE / StreamableHTTP）</td><td><code>Toolkit.registerMcpClient</code></td><td>Toolkit 统一工具表</td><td>工具过滤 / 分组、Higress 网关语义检索、elicitation 回调</td></tr><tr><td><strong>LangChain4j</strong></td><td><code>McpTransport</code> + <code>DefaultMcpClient</code></td><td><code>McpToolProvider</code></td><td><code>McpToolExecutor</code></td><td>与 AiServices / ToolProvider 对齐</td></tr><tr><td><strong>Spring AI</strong></td><td><code>McpSyncClient</code> / <code>McpAsyncClient</code></td><td><code>SyncMcpToolCallbackProvider</code></td><td><code>SyncMcpToolCallback</code></td><td>Boot Starter、<code>@McpTool</code> 注解、Security 集成</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：无论用哪个框架，Agent 接入 MCP 的骨架都是一致的——「<strong>Client 连接 → tools/list 发现 → 工具适配 → tools/call 执行 → 生命周期与安全治理</strong>」。差别只在 API 形态与治理能力。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="610底层调用全过程逐帧详解">6.10、底层调用全过程（逐帧详解）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#610%E5%BA%95%E5%B1%82%E8%B0%83%E7%94%A8%E5%85%A8%E8%BF%87%E7%A8%8B%E9%80%90%E5%B8%A7%E8%AF%A6%E8%A7%A3" class="hash-link" aria-label="6.10、底层调用全过程（逐帧详解）的直接链接" title="6.10、底层调用全过程（逐帧详解）的直接链接" translate="no">​</a></h3>
<blockquote>
<p>本节把「MCP 接入 Agent」拆到<strong>协议帧级别</strong>：从 Agent 启动连接，到工具发现、格式转换、模型选择、工具执行、结果回灌、循环收敛、刷新与关闭，每一步都给出真实的 JSON / 调用片段。示例统一使用：Server 别名 <code>weather</code>，MCP 原始工具名 <code>get_weather</code>，暴露给模型的工具名 <code>weather__get_weather</code>。</p>
</blockquote>
<p><strong>全景时序（一次完整 run）：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">[启动期]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  0. 装配 MCP Client（传输 + 能力 + 超时）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  1. 建立连接（stdio 起子进程 / HTTP 建连接）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  2. 初始化：server/discover（现代）或 initialize（传统）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  3. tools/list（分页拉全量）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  4. 消歧/过滤/分组 → 写入 ToolRegistry，并建映射表</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        llmName(weather__get_weather) ⇄ (server=weather, mcpName=get_weather, executor)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[每次 run]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  loop {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    5. Think：组装 ChatRequest（tools = 缓存的声明转 provider 格式）→ 调 LLM</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    6. LLM 返回：text 或 tool_calls[]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    7. 若有 tool_calls → Act：</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         a. 用 llmName 查映射表得到 (mcpName, executor)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         b. arguments 归一化成 Map</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         c. before-tool 中间件（审批/日志）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         d. executor → tools/call（JSON-RPC）→ MCP Server 执行</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         e. result(content/structuredContent/isError) → tool result 消息（关联 call id）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         f. after-tool 中间件</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">         g. 写回 memory</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    8. 回到 5（带 tool results）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    9. LLM 不再返回 tool_calls → 输出最终答案 → run 结束</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[运行期]    10. list_changed / subscriptions → 重新 tools/list → 刷新注册表</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[销毁期]    11. close：stdio 关 stdin→等待→强杀；HTTP 关闭连接</span><br></div></code></pre></div></div>
<br>
<p><strong>帧 0-1：装配与连接</strong></p>
<ul>
<li class="">选传输：本地用 stdio（<code>ProcessBuilder</code> 起子进程），远程用 Streamable HTTP。</li>
<li class="">装配 Client：声明能力（roots / sampling / elicitation）、请求超时、初始化超时。</li>
</ul>
<br>
<p><strong>帧 2：初始化（现代版 <code>server/discover</code>）</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → 请求（stdio：一整行 JSON）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"discover-1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"server/discover"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"_meta"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-07-28"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/clientInfo"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"MyAgent"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/clientCapabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"discover-1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"resultType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"complete"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"supportedVersions"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"2026-07-28"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"capabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"listChanged"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"_meta"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"io.modelcontextprotocol/serverInfo"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather-server"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.1.0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>传统版（≤ 2025-11-25）</strong>：先 <code>initialize</code> 请求/响应协商版本与能力，再补一条 <code>notifications/initialized</code>。现代版无握手，能力随每个请求的 <code>_meta</code> 携带。</p>
</blockquote>
<br>
<p><strong>帧 3：工具发现（<code>tools/list</code>）</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → 请求</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tools/list"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"_meta"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-07-28"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/clientCapabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"resultType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"complete"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Get current weather for a city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"inputSchema"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"City name"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"location"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"nextCursor"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"ttlMs"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">300000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"cacheScope"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"public"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>nextCursor</code> 非空时要继续拉下一页；<code>ttlMs</code> / <code>cacheScope</code> 是缓存提示。拉完后进入注册阶段。</p>
</blockquote>
<br>
<p><strong>帧 4：注册与映射（本地内存结构）</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolRegistry（缓存，供每轮 Think 复用）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">┌────────────────────────┬──────────┬──────────────┬─────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│ llmName                │ server   │ mcpName      │ executor                │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├────────────────────────┼──────────┼──────────────┼─────────────────────────┤</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│ weather__get_weather   │ weather  │ get_weather  │ McpToolExecutor(#1)     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└────────────────────────┴──────────┴──────────────┴─────────────────────────┘</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>llmName</code>：加 Server 前缀消歧后、暴露给模型的名字。</li>
<li class=""><code>mcpName</code>：MCP Server 上的原始工具名，<code>tools/call</code> 时使用。</li>
<li class=""><code>executor</code>：绑定该 Server Client 的执行器。</li>
</ul>
<br>
<p><strong>帧 5：Think（组装 LLM 请求，转换成 Function Calling）</strong></p>
<p>把注册表里的声明转换成 Provider 的 Function Calling 格式，随请求下发（<strong>这一步不是 <code>tools/list</code>，是本地序列化</strong>）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-x"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"messages"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"system"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"You are a helpful assistant."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天天气怎么样？"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather__get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Get current weather for a city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"parameters"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"City name"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"location"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<p><strong>帧 6：LLM 选择工具（返回 tool_call）</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl-1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_calls"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_abc"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather__get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"location\":\"Hangzhou\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<p><strong>帧 7：Act（反向映射 → <code>tools/call</code>）</strong></p>
<ul>
<li class="">用 <code>llmName = weather__get_weather</code> 查表 → <code>(server=weather, mcpName=get_weather, executor)</code>。</li>
<li class="">解析 <code>arguments</code>（OpenAI 是 JSON 字符串）→ <code>{"location":"Hangzhou"}</code>。</li>
<li class="">触发 before-tool 中间件（审批 / 日志 / 埋点）。</li>
</ul>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → tools/call</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tools/call"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hangzhou"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"_meta"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-07-28"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"io.modelcontextprotocol/clientCapabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"resultType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"complete"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hangzhou: 26°C, sunny"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"structuredContent"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"tempC"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">26</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"condition"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"sunny"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"isError"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p>HTTP 传输下，同一请求还会带上头：<code>MCP-Protocol-Version</code>、<code>Mcp-Method: tools/call</code>、<code>Mcp-Name: get_weather</code>。</p>
</blockquote>
<br>
<p><strong>帧 8：结果回灌（MCP result → tool result 消息）</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_abc"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hangzhou: 26°C, sunny"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>content[]</code> 拼接为文本；<code>structuredContent</code> 可走结构化链路。</li>
<li class=""><code>isError=true</code> → 作为「工具执行错误」回给模型自纠；JSON-RPC <code>error</code> → 走协议错误链路。</li>
<li class="">触发 after-tool 中间件，写完 memory 后回到帧 5。</li>
</ul>
<br>
<p><strong>帧 9：再次 Think 并收敛</strong></p>
<p>第二次 LLM 请求带上 tool result；模型不再返回 <code>tool_calls</code>，直接输出最终答案，run 结束。若一轮响应里有多个 <code>tool_call</code>，则逐个（或并行）执行帧 7-8，再合并回灌。</p>
<br>
<p><strong>帧 10-11：运行期刷新与关闭</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">运行期（可选）：</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  ← notifications/tools/list_changed            （Server 声明 listChanged 时）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  → tools/list（重新拉取）→ 更新 ToolRegistry（安排在轮次之间）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">销毁期：</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  stdio：关闭 stdin → 等待进程退出 → 超时 SIGTERM → 再超时 SIGKILL</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  HTTP ：关闭连接 / 连接池</span><br></div></code></pre></div></div>
<br>
<p><strong>底层状态与数据结构一览：</strong></p>
<table><thead><tr><th>结构</th><th>作用</th></tr></thead><tbody><tr><td><code>pending: id → Future</code></td><td>JSON-RPC 请求/响应关联</td></tr><tr><td><code>ToolRegistry</code></td><td>llmName → (server, mcpName, executor, schema) 映射与缓存</td></tr><tr><td><code>messages / memory</code></td><td>system + 历史 + tool result，逐轮累积</td></tr><tr><td><code>capabilities</code></td><td>双方能力，决定可用特性（tools/resources/sampling…）</td></tr><tr><td><code>protocolVersions</code></td><td>支持的版本集合，用于协商与回退</td></tr></tbody></table>
<p><strong>分支与异常：</strong></p>
<ul>
<li class=""><strong>协议错误 vs 工具执行错误</strong>：前者走 JSON-RPC <code>error</code>，后者走 <code>result.isError=true</code>，后者应回给模型自纠。</li>
<li class=""><strong>版本不匹配</strong>：<code>UnsupportedProtocolVersionError</code>（<code>-32022</code>）→ 从 <code>supported</code> 选版本重试；旧 Server → 回退 <code>initialize</code>。</li>
<li class=""><strong>超时 / 取消</strong>：客户端强制超时；stdio 发 <code>notifications/cancelled</code>，HTTP 关闭响应流。</li>
<li class=""><strong>并行 tool_calls</strong>：一轮可含多个调用，需按 <code>id</code> 分别回灌，顺序不影响关联。</li>
<li class=""><strong>列表变更</strong>：<code>list_changed</code> 触发重拉，刷新注册表与映射。</li>
</ul>
<br>
<p><strong>三方时序图（Agent/Host ↔ MCP Client ↔ MCP Server ↔ LLM）：</strong></p>
<div class="language-mermaid codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mermaid codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">sequenceDiagram</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    participant H as Agent/Host</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    participant C as MCP Client</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    participant S as MCP Server</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    participant M as LLM</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    Note over H,C: 启动期（一次）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    C-&gt;&gt;S: server/discover（或 initialize）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    S--&gt;&gt;C: supportedVersions + capabilities</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    C-&gt;&gt;S: tools/list</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    S--&gt;&gt;C: tools[]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    Note over H: 消歧/过滤/分组 → ToolRegistry</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    loop 每轮对话</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        H-&gt;&gt;M: ChatRequest（messages + tools（Function Calling 格式））</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        M--&gt;&gt;H: tool_calls[]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        H-&gt;&gt;C: 反向映射（llmName → server + mcpName）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        C-&gt;&gt;S: tools/call(name, arguments)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        S--&gt;&gt;C: content / structuredContent / isError</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        H-&gt;&gt;M: tool result（关联 call id）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        M--&gt;&gt;H: 最终答案 或 继续调用工具</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    end</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    Note over C,S: 运行期（可选）list_changed → 重新 tools/list</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-a传统版初始化-2025-11-25">补充帧 A：传统版初始化（≤ 2025-11-25）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-a%E4%BC%A0%E7%BB%9F%E7%89%88%E5%88%9D%E5%A7%8B%E5%8C%96-2025-11-25" class="hash-link" aria-label="补充帧 A：传统版初始化（≤ 2025-11-25）的直接链接" title="补充帧 A：传统版初始化（≤ 2025-11-25）的直接链接" translate="no">​</a></h4>
<p>现代版无握手；连接旧 Server 时走 <code>initialize</code> 握手：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → initialize</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"initialize"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2025-06-18"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"capabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"roots"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"listChanged"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"clientInfo"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"MyAgent"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0.0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← 响应</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"protocolVersion"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2025-06-18"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"capabilities"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"listChanged"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"serverInfo"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather-server"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.1.0"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → 客户端就绪通知（无 id）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"notifications/initialized"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-bhttp--sse-流式响应长耗时工具">补充帧 B：HTTP + SSE 流式响应（长耗时工具）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-bhttp--sse-%E6%B5%81%E5%BC%8F%E5%93%8D%E5%BA%94%E9%95%BF%E8%80%97%E6%97%B6%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="补充帧 B：HTTP + SSE 流式响应（长耗时工具）的直接链接" title="补充帧 B：HTTP + SSE 流式响应（长耗时工具）的直接链接" translate="no">​</a></h4>
<p>Streamable HTTP 下，客户端 POST 单个请求；服务端可能返回<strong>请求级 SSE 流</strong>，先推进度、最后一条才是最终响应：</p>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST /mcp HTTP/1.1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Accept: application/json, text/event-stream</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">MCP-Protocol-Version: 2026-07-28</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Mcp-Method: tools/call</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Mcp-Name: get_weather</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  "params": { "name": "get_weather", "arguments": { "location": "Hangzhou" },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               "io.modelcontextprotocol/clientCapabilities": {} } } }</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">HTTP/1.1 200 OK</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: text/event-stream</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">X-Accel-Buffering: no</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"p1","progress":1,"total":3}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"p1","progress":2,"total":3}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"jsonrpc":"2.0","id":3,"result":{"resultType":"complete","content":[{"type":"text","text":"Hangzhou: 26°C, sunny"}],"isError":false}}</span><br></div></code></pre></div></div>
<blockquote>
<p>最后一条 SSE 事件是<strong>最终响应</strong>，随后流关闭。请求级通知（<code>notifications/progress</code>）只在该请求的响应流上出现。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-c并行多工具调用">补充帧 C：并行多工具调用<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-c%E5%B9%B6%E8%A1%8C%E5%A4%9A%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8" class="hash-link" aria-label="补充帧 C：并行多工具调用的直接链接" title="补充帧 C：并行多工具调用的直接链接" translate="no">​</a></h4>
<p>一轮 LLM 响应可返回多个 <code>tool_call</code>；Agent 逐个（或并行）执行，再按 <code>id</code> 关联回灌多条 tool result：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← LLM 返回两个工具调用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_a"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"weather__get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"location\":\"Hangzhou\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_b"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"time__now"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → 两次 tools/call（可并行），id 分别为 3、4</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tools/call"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hangzhou"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tools/call"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"now"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← 两条 tool result，按 call id 关联</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_a"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hangzhou: 26°C, sunny"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_b"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2026-10-05T10:00:00+08:00"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-dmrtr工具需要额外输入">补充帧 D：MRTR（工具需要额外输入）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-dmrtr%E5%B7%A5%E5%85%B7%E9%9C%80%E8%A6%81%E9%A2%9D%E5%A4%96%E8%BE%93%E5%85%A5" class="hash-link" aria-label="补充帧 D：MRTR（工具需要额外输入）的直接链接" title="补充帧 D：MRTR（工具需要额外输入）的直接链接" translate="no">​</a></h4>
<p>当 Server 需要客户端补充输入时，不再主动发请求，而是返回 <code>resultType: "input_required"</code>；客户端补齐后用<strong>新 id</strong> 重放：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ← 第一次 tools/call 的中间结果</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"result"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"resultType"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"input_required"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"inputRequests"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"login"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"elicitation/create"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"GitHub username?"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"mode"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"form"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"requestedSchema"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"requestState"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"eyJsb2NhdGlvbiI6IkhhbmcgemhvdSJ9"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// → 用新 id=4 重放，并带上 inputResponses + requestState</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"jsonrpc"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"method"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tools/call"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"params"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"location"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hangzhou"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"inputResponses"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"login"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"action"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"accept"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"octocat"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"requestState"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"eyJsb2NhdGlvbiI6IkhhbmcgemhvdSJ9"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-e两类错误帧务必区分">补充帧 E：两类错误帧（务必区分）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-e%E4%B8%A4%E7%B1%BB%E9%94%99%E8%AF%AF%E5%B8%A7%E5%8A%A1%E5%BF%85%E5%8C%BA%E5%88%86" class="hash-link" aria-label="补充帧 E：两类错误帧（务必区分）的直接链接" title="补充帧 E：两类错误帧（务必区分）的直接链接" translate="no">​</a></h4>
<div class="language-jsonc codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsonc codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 工具执行错误：走 result.isError —— 回给模型自纠</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 3, "result": { "resultType": "complete",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  "content": [{ "type": "text", "text": "Invalid location: no such city." }], "isError": true } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 协议错误：走 JSON-RPC error —— 交给框架错误处理</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 3, "error": { "code": -32602, "message": "Unknown tool: get_weather_x" } }</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-f取消与超时">补充帧 F：取消与超时<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-f%E5%8F%96%E6%B6%88%E4%B8%8E%E8%B6%85%E6%97%B6" class="hash-link" aria-label="补充帧 F：取消与超时的直接链接" title="补充帧 F：取消与超时的直接链接" translate="no">​</a></h4>
<div class="language-jsonc codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsonc codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">// stdio：客户端发取消通知（引用请求 id）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "method": "notifications/cancelled", "params": { "requestId": 3, "reason": "user aborted" } }</span><br></div></code></pre></div></div>
<blockquote>
<p>HTTP 传输没有取消通知：<strong>关闭该请求的响应流</strong>即视为取消（2026-07-28 移除 SSE 可恢复性，断流即丢请求，需用新 id 重发）。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="补充帧-g分页拉取">补充帧 G：分页拉取<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E8%A1%A5%E5%85%85%E5%B8%A7-g%E5%88%86%E9%A1%B5%E6%8B%89%E5%8F%96" class="hash-link" aria-label="补充帧 G：分页拉取的直接链接" title="补充帧 G：分页拉取的直接链接" translate="no">​</a></h4>
<div class="language-jsonc codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsonc codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 第一页</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// ← result 中 nextCursor = "c1"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// 第二页</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">{ "jsonrpc": "2.0", "id": 5, "method": "tools/list", "params": { "cursor": "c1" } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">// ← result 中 nextCursor = null → 结束</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="内部机制pending-路由与超时">内部机制：<code>pending</code> 路由与超时<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%86%85%E9%83%A8%E6%9C%BA%E5%88%B6pending-%E8%B7%AF%E7%94%B1%E4%B8%8E%E8%B6%85%E6%97%B6" class="hash-link" aria-label="内部机制pending-路由与超时的直接链接" title="内部机制pending-路由与超时的直接链接" translate="no">​</a></h4>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pending: Map&lt;id, Future&gt;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  发送 tools/call(id=3)  → pending["3"] = F3</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  收到带 id=3 的响应     → pending.remove("3").complete(result)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  超时                   → F3 失败；pending.remove("3")（stdio 另发 notifications/cancelled）</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：协议是异步的，<code>id</code> 是唯一关联键；客户端必须在 <code>Future</code> 上加超时（收到 <code>notifications/progress</code> 可重置时钟，但仍需保留最大超时）。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="完整一轮协议帧流水浓缩总览">完整一轮「协议帧流水」（浓缩总览）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%AE%8C%E6%95%B4%E4%B8%80%E8%BD%AE%E5%8D%8F%E8%AE%AE%E5%B8%A7%E6%B5%81%E6%B0%B4%E6%B5%93%E7%BC%A9%E6%80%BB%E8%A7%88" class="hash-link" aria-label="完整一轮「协议帧流水」（浓缩总览）的直接链接" title="完整一轮「协议帧流水」（浓缩总览）的直接链接" translate="no">​</a></h4>
<table><thead><tr><th>序号</th><th>方向</th><th>帧 / 动作</th><th>关键字段</th></tr></thead><tbody><tr><td>1</td><td>C→S</td><td><code>server/discover</code></td><td><code>_meta.protocolVersion</code> / <code>clientCapabilities</code></td></tr><tr><td>2</td><td>S→C</td><td>discover 结果</td><td><code>supportedVersions</code> / <code>capabilities</code></td></tr><tr><td>3</td><td>C→S</td><td><code>tools/list</code></td><td><code>cursor</code></td></tr><tr><td>4</td><td>S→C</td><td>工具清单</td><td><code>tools[].inputSchema</code> / <code>nextCursor</code></td></tr><tr><td>5</td><td>本地</td><td>注册 + 映射 + 过滤/分组</td><td><code>llmName ⇄ (server, mcpName)</code></td></tr><tr><td>6</td><td>H→M</td><td>ChatRequest</td><td><code>tools[]</code>（Function Calling 格式）</td></tr><tr><td>7</td><td>M→H</td><td><code>tool_calls[]</code></td><td><code>id</code> / <code>name</code> / <code>arguments</code></td></tr><tr><td>8</td><td>C→S</td><td><code>tools/call</code></td><td><code>name</code> / <code>arguments</code> / <code>Mcp-Name</code> 头</td></tr><tr><td>9</td><td>S→C</td><td>结果</td><td><code>content</code> / <code>structuredContent</code> / <code>isError</code></td></tr><tr><td>10</td><td>H→M</td><td>tool result</td><td><code>tool_call_id</code> / <code>content</code></td></tr><tr><td>11</td><td>M→H</td><td>最终答案</td><td>无 <code>tool_calls</code> → 结束</td></tr><tr><td>12</td><td>S→C</td><td>（运行期）<code>list_changed</code></td><td>触发重拉 <code>tools/list</code></td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="611设计检查清单">6.11、设计检查清单<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#611%E8%AE%BE%E8%AE%A1%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95" class="hash-link" aria-label="6.11、设计检查清单的直接链接" title="6.11、设计检查清单的直接链接" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->传输是否匹配部署形态（本地 stdio / 远程 HTTP）？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否处理了协议版本兼容（双时代）？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->三类超时是否设置（请求 / 初始化 / 空闲）？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->工具名是否做了命名空间消歧？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否做了工具白名单 / 最小暴露？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>isError</code> 是否正确回给模型自纠？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否监听工具列表变更并热更新？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否有人类审批与审计日志？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->连接是否随 Agent 生命周期正确关闭 / 重启？</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七安全与授权设计">七、安全与授权设计<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B8%83%E5%AE%89%E5%85%A8%E4%B8%8E%E6%8E%88%E6%9D%83%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="七、安全与授权设计的直接链接" title="七、安全与授权设计的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="71授权演进规范">7.1、授权演进（<a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization" target="_blank" rel="noopener noreferrer" class="">规范</a>）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#71%E6%8E%88%E6%9D%83%E6%BC%94%E8%BF%9B%E8%A7%84%E8%8C%83" class="hash-link" aria-label="71授权演进规范的直接链接" title="71授权演进规范的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>2025-03-26</strong>：引入 <strong>OAuth 2.1</strong> 授权框架；</li>
<li class=""><strong>2025-06-18</strong>：MCP Server 定位为 <strong>OAuth Resource Server</strong>，要求 <strong>Resource Indicators（RFC 8707）</strong>，防止 Token 被复用；</li>
<li class=""><strong>2025-11-25</strong>：支持 <strong>OpenID Connect Discovery 1.0</strong>、增量授权范围同意（<code>WWW-Authenticate</code>）、推荐 <strong>Client ID Metadata Documents</strong>；</li>
<li class=""><strong>2026-07-28</strong>：进一步对齐 OAuth 2.0 / OIDC 部署实践，<strong>弃用动态客户端注册（RFC 7591）</strong>，改推 Client ID Metadata Documents；要求校验 <code>iss</code>（RFC 9207）、按 issuer 隔离凭据。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：授权仅适用于 <strong>HTTP 类传输</strong>；stdio 传输 <strong>SHOULD NOT</strong> 使用该框架，而应从环境变量获取凭据。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="72传输安全">7.2、传输安全<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#72%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8" class="hash-link" aria-label="7.2、传输安全的直接链接" title="7.2、传输安全的直接链接" translate="no">​</a></h3>
<p>Streamable HTTP 的硬性要求：</p>
<ol>
<li class="">所有入站连接 <strong>MUST</strong> 校验 <code>Origin</code>，非法则 <code>403</code>（防 DNS rebinding）；</li>
<li class="">本地运行 <strong>SHOULD</strong> 只绑定 <code>127.0.0.1</code>，不要 <code>0.0.0.0</code>；</li>
<li class=""><strong>SHOULD</strong> 实现认证；</li>
<li class="">服务端 <strong>MUST</strong> 校验镜像头与请求体一致（不一致返回 <code>400</code> + <code>HeaderMismatch</code>）。</li>
</ol>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="73工具安全">7.3、工具安全<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#73%E5%B7%A5%E5%85%B7%E5%AE%89%E5%85%A8" class="hash-link" aria-label="7.3、工具安全的直接链接" title="7.3、工具安全的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>Human-in-the-loop</strong>：工具由模型控制，<strong>SHOULD</strong> 始终保留人类拒绝调用的能力；应用应明确展示暴露了哪些工具、在调用时给出视觉提示、对敏感操作弹确认；</li>
<li class=""><strong>Tool Poisoning</strong>：工具注解/描述必须当作<strong>不可信输入</strong>；</li>
<li class=""><strong>输入/输出安全</strong>：Server <strong>MUST</strong> 校验所有输入、做访问控制、限流、清理输出；Client <strong>SHOULD</strong> 在传给 LLM 前校验结果、设置超时、记录审计；</li>
<li class=""><strong><code>x-mcp-header</code></strong>：仅用于路由，不得标记敏感字段；</li>
<li class=""><strong>图标安全</strong>：图标 URI 只允许 HTTPS 或 <code>data:</code>，拒绝 <code>javascript:</code>/<code>file:</code> 等；不经凭据抓取；校验 MIME 与内容（防 SVG 内嵌脚本）。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八扩展机制与治理">八、扩展机制与治理<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%85%AB%E6%89%A9%E5%B1%95%E6%9C%BA%E5%88%B6%E4%B8%8E%E6%B2%BB%E7%90%86" class="hash-link" aria-label="八、扩展机制与治理的直接链接" title="八、扩展机制与治理的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="81扩展框架">8.1、扩展框架<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#81%E6%89%A9%E5%B1%95%E6%A1%86%E6%9E%B6" class="hash-link" aria-label="8.1、扩展框架的直接链接" title="8.1、扩展框架的直接链接" translate="no">​</a></h3>
<p>2026-07-28 在 <code>ClientCapabilities</code> / <code>ServerCapabilities</code> 中新增 <code>extensions</code> 字段：</p>
<ul>
<li class="">标识符使用<strong>反向 DNS 前缀</strong>（如 <code>io.modelcontextprotocol/ui</code>）；</li>
<li class="">每个扩展自带设置对象与独立版本；</li>
<li class="">一方支持、另一方不支持时，支持方 <strong>MUST</strong> 回退核心行为或明确拒绝。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="82mcp-apps">8.2、MCP Apps<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#82mcp-apps" class="hash-link" aria-label="8.2、MCP Apps的直接链接" title="8.2、MCP Apps的直接链接" translate="no">​</a></h3>
<p>2026-01-26 作为<strong>首个官方扩展</strong>发布，让 Server 能在沙箱 iframe 中渲染交互式 UI（表格、表单等），标识为 <code>io.modelcontextprotocol/ui</code>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="83tasks-扩展">8.3、Tasks 扩展<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#83tasks-%E6%89%A9%E5%B1%95" class="hash-link" aria-label="8.3、Tasks 扩展的直接链接" title="8.3、Tasks 扩展的直接链接" translate="no">​</a></h3>
<p>把实验性任务从核心协议迁出为官方扩展 <code>io.modelcontextprotocol/tasks</code>，用 <code>tasks/get</code> 轮询 + <code>tasks/update</code> 补充输入，取代阻塞式 <code>tasks/result</code>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="84弃用策略">8.4、弃用策略<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#84%E5%BC%83%E7%94%A8%E7%AD%96%E7%95%A5" class="hash-link" aria-label="8.4、弃用策略的直接链接" title="8.4、弃用策略的直接链接" translate="no">​</a></h3>
<p>2026-07-28 建立正式的<strong>特性生命周期与弃用策略</strong>：Active / Deprecated / Removed 三态，弃用窗口至少 12 个月，并维护<a href="https://modelcontextprotocol.io/specification/2026-07-28/deprecated" target="_blank" rel="noopener noreferrer" class="">弃用特性登记表</a>。当前被弃用者：</p>
<ul>
<li class=""><strong>Roots、Sampling、Logging</strong>（迁移方向：工具参数/资源 URI/配置传递目录；直接用 Provider API；<code>stderr</code> 或 OpenTelemetry）；</li>
<li class=""><strong>HTTP+SSE 传输</strong>（迁移到 Streamable HTTP）；</li>
<li class=""><strong><code>includeContext</code> 的 <code>"thisServer"</code>/<code>"allServers"</code></strong>；</li>
<li class=""><strong>OAuth Dynamic Client Registration</strong>。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="九设计权衡与对比">九、设计权衡与对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B9%9D%E8%AE%BE%E8%AE%A1%E6%9D%83%E8%A1%A1%E4%B8%8E%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="九、设计权衡与对比的直接链接" title="九、设计权衡与对比的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="91传输设计权衡stdio-vs-streamable-http">9.1、传输设计权衡：stdio vs Streamable HTTP<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#91%E4%BC%A0%E8%BE%93%E8%AE%BE%E8%AE%A1%E6%9D%83%E8%A1%A1stdio-vs-streamable-http" class="hash-link" aria-label="9.1、传输设计权衡：stdio vs Streamable HTTP的直接链接" title="9.1、传输设计权衡：stdio vs Streamable HTTP的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>维度</th><th>stdio</th><th>Streamable HTTP</th></tr></thead><tbody><tr><td>部署</td><td>本地子进程</td><td>远程服务</td></tr><tr><td>分帧</td><td>换行分隔 JSON</td><td>HTTP POST，响应 JSON 或 SSE</td></tr><tr><td>状态</td><td>无（2026-07-28）</td><td>无（2026-07-28 移除 session）</td></tr><tr><td>鉴权</td><td>环境变量</td><td>OAuth 2.1 / OIDC</td></tr><tr><td>取消</td><td><code>notifications/cancelled</code></td><td>关闭响应流</td></tr><tr><td>适用</td><td>本地工具、桌面宿主</td><td>云部署、多租户、负载均衡</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="92有状态-vs-无状态">9.2、有状态 vs 无状态<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#92%E6%9C%89%E7%8A%B6%E6%80%81-vs-%E6%97%A0%E7%8A%B6%E6%80%81" class="hash-link" aria-label="9.2、有状态 vs 无状态的直接链接" title="9.2、有状态 vs 无状态的直接链接" translate="no">​</a></h3>
<p>旧版用 session 提供「连接内上下文」，实现简单但与水平扩展、负载均衡冲突；2026-07-28 选择<strong>无状态</strong>，把状态显式化（handle 作为普通参数），换取云原生友好。代价是每次请求都要携带元数据、跨调用状态需应用自管。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="93服务端反向请求-vs-mrtr">9.3、服务端反向请求 vs MRTR<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#93%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%8F%8D%E5%90%91%E8%AF%B7%E6%B1%82-vs-mrtr" class="hash-link" aria-label="9.3、服务端反向请求 vs MRTR的直接链接" title="9.3、服务端反向请求 vs MRTR的直接链接" translate="no">​</a></h3>
<p>旧版服务端可主动发请求（sampling/elicitation/roots），依赖双向流与有状态连接；MRTR 把它改成「客户端重试 + <code>inputResponses</code>」，使交互符合「客户端只发请求」的单向模型，是去状态化的关键配套设计。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="94mcp-vs-function-calling--lsp--a2a">9.4、MCP vs Function Calling / LSP / A2A<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#94mcp-vs-function-calling--lsp--a2a" class="hash-link" aria-label="9.4、MCP vs Function Calling / LSP / A2A的直接链接" title="9.4、MCP vs Function Calling / LSP / A2A的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>对比</th><th>关系</th></tr></thead><tbody><tr><td><strong>Function Calling</strong></td><td>MCP 不替代它，而是把工具<strong>声明与执行</strong>标准化；模型侧仍走各家的 function calling</td></tr><tr><td><strong>LSP</strong></td><td>架构同源（Host/Client/Server、JSON-RPC）；MCP 是 LSP 思路在 AI 能力领域的迁移</td></tr><tr><td><strong>A2A</strong></td><td>MCP 解决「AI ↔ 工具」，A2A 解决「Agent ↔ Agent」；二者互补，可组合</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="95常见设计陷阱">9.5、常见设计陷阱<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#95%E5%B8%B8%E8%A7%81%E8%AE%BE%E8%AE%A1%E9%99%B7%E9%98%B1" class="hash-link" aria-label="9.5、常见设计陷阱的直接链接" title="9.5、常见设计陷阱的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><strong>命名冲突</strong>：聚合多个 Server 时工具名可能重名，必须前缀消歧；<strong>不要</strong>依赖 <code>serverInfo.name</code>（可自报、不校验、不保证唯一）。</li>
<li class=""><strong>版本碎片化</strong>：2025 系与 2026 系并存，客户端必须双时代兼容。</li>
<li class=""><strong>SSE 不可恢复</strong>：2026-07-28 移除 <code>Last-Event-ID</code>，断流必须整请求重发。</li>
<li class=""><strong>无状态不等于无状态数据</strong>：跨调用状态要靠工具自己返回显式 handle，Server 不能依赖连接状态。</li>
<li class=""><strong>stdio 日志污染</strong>：Server <strong>绝不能</strong>往 <code>stdout</code> 写非 MCP 消息，日志走 <code>stderr</code>。</li>
<li class=""><strong>超时缺失</strong>：无状态协议下，客户端必须强制超时，否则线程可能永久挂起。</li>
<li class=""><strong>能力越界</strong>：Server <strong>MUST NOT</strong> 依赖客户端未声明的能力，否则应返回 <code>-32021</code>。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="十总结与展望">十、总结与展望<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%8D%81%E6%80%BB%E7%BB%93%E4%B8%8E%E5%B1%95%E6%9C%9B" class="hash-link" aria-label="十、总结与展望的直接链接" title="十、总结与展望的直接链接" translate="no">​</a></h2>
<ol>
<li class="">MCP 用 <strong>JSON-RPC 2.0 + 三大原语 + 标准传输</strong>，把「AI 应用 × 外部能力」的 M×N 集成成本降为 M+N，设计范式承袭 LSP。</li>
<li class="">版本演进体现了一条清晰主线：<strong>从「有状态连接」走向「无状态请求」</strong>——<code>2025-03-26</code> 换传输、<code>2025-06-18</code> 强化结构与安全、<code>2025-11-25</code> 完善授权与生态、<code>2026-07-28</code> 彻底无状态化并引入 MRTR/扩展框架/弃用策略。</li>
<li class="">当前版本 <code>2026-07-28</code> 是范式级修订：请求自描述、<code>server/discover</code>、MRTR、<code>subscriptions/listen</code>、扩展机制与正式弃用策略。</li>
<li class="">一个 MCP Client 的核心是 <strong>Transport + 协议层（id→Future 路由、超时、取消）+ 工具适配</strong>；一个 Server 的核心是 <strong>method 分发 + 能力声明 + 错误分流</strong>。</li>
<li class="">治理上，MCP 已从单一厂商开源项目演进为 Linux 基金会 AAIF 下的中立标准；后续可关注扩展生态（MCP Apps / Tasks）、注册中心（MCP Registry）与授权加固的落地。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<blockquote>
<p>官方入口：规范文档 <a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">modelcontextprotocol.io</a> ｜ 官方博客 <a href="https://blog.modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">blog.modelcontextprotocol.io</a> ｜ 官方代码 <a href="https://github.com/modelcontextprotocol" target="_blank" rel="noopener noreferrer" class="">github.com/modelcontextprotocol</a> ｜ 官方注册中心 <a href="https://registry.modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">registry.modelcontextprotocol.io</a></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一官方规范当前版本-2026-07-28">一、官方规范（当前版本 2026-07-28）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B8%80%E5%AE%98%E6%96%B9%E8%A7%84%E8%8C%83%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC-2026-07-28" class="hash-link" aria-label="一、官方规范（当前版本 2026-07-28）的直接链接" title="一、官方规范（当前版本 2026-07-28）的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://modelcontextprotocol.io/specification/2026-07-28" target="_blank" rel="noopener noreferrer" class="">MCP 官方规范（2026-07-28）</a></p>
<p>[2]. <a href="https://modelcontextprotocol.io/llms.txt" target="_blank" rel="noopener noreferrer" class="">MCP 文档索引 llms.txt</a></p>
<p>[3]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/architecture" target="_blank" rel="noopener noreferrer" class="">MCP 架构总览 Architecture</a></p>
<p>[4]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic" target="_blank" rel="noopener noreferrer" class="">MCP 基础协议与消息模型 Basic</a></p>
<p>[5]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/lifecycle" target="_blank" rel="noopener noreferrer" class="">MCP 版本与兼容性 Versioning and Compatibility</a></p>
<p>[6]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" target="_blank" rel="noopener noreferrer" class="">MCP 2026-07-28 变更日志 Key Changes</a></p>
<p>[7]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/schema" target="_blank" rel="noopener noreferrer" class="">MCP 完整 Schema（schema.json / schema.ts）</a></p>
<p>[8]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/deprecated" target="_blank" rel="noopener noreferrer" class="">MCP 弃用特性登记表 Deprecated</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="11传输transports">1.1、传输（Transports）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#11%E4%BC%A0%E8%BE%93transports" class="hash-link" aria-label="1.1、传输（Transports）的直接链接" title="1.1、传输（Transports）的直接链接" translate="no">​</a></h3>
<p>[9]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports" target="_blank" rel="noopener noreferrer" class="">MCP 传输总览 Transports</a></p>
<p>[10]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio" target="_blank" rel="noopener noreferrer" class="">MCP stdio 传输</a></p>
<p>[11]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http" target="_blank" rel="noopener noreferrer" class="">MCP Streamable HTTP 传输</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="12服务端特性server-features">1.2、服务端特性（Server Features）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#12%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%89%B9%E6%80%A7server-features" class="hash-link" aria-label="1.2、服务端特性（Server Features）的直接链接" title="1.2、服务端特性（Server Features）的直接链接" translate="no">​</a></h3>
<p>[12]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" target="_blank" rel="noopener noreferrer" class="">MCP Tools</a></p>
<p>[13]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/resources" target="_blank" rel="noopener noreferrer" class="">MCP Resources</a></p>
<p>[14]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/prompts" target="_blank" rel="noopener noreferrer" class="">MCP Prompts</a></p>
<p>[15]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/discover" target="_blank" rel="noopener noreferrer" class="">MCP Discovery（server/discover）</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="13消息模式与授权patterns--authorization">1.3、消息模式与授权（Patterns &amp; Authorization）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#13%E6%B6%88%E6%81%AF%E6%A8%A1%E5%BC%8F%E4%B8%8E%E6%8E%88%E6%9D%83patterns--authorization" class="hash-link" aria-label="1.3、消息模式与授权（Patterns &amp; Authorization）的直接链接" title="1.3、消息模式与授权（Patterns &amp; Authorization）的直接链接" translate="no">​</a></h3>
<p>[16]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns" target="_blank" rel="noopener noreferrer" class="">MCP 消息模式总览 Patterns</a></p>
<p>[17]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr" target="_blank" rel="noopener noreferrer" class="">MCP 多轮往返请求 MRTR</a></p>
<p>[18]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions" target="_blank" rel="noopener noreferrer" class="">MCP 订阅与通知 Subscriptions（subscriptions/listen）</a></p>
<p>[19]. <a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization" target="_blank" rel="noopener noreferrer" class="">MCP 授权 Authorization</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二历史版本规范">二、历史版本规范<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%BA%8C%E5%8E%86%E5%8F%B2%E7%89%88%E6%9C%AC%E8%A7%84%E8%8C%83" class="hash-link" aria-label="二、历史版本规范的直接链接" title="二、历史版本规范的直接链接" translate="no">​</a></h2>
<p>[20]. <a href="https://modelcontextprotocol.io/specification/2025-11-25" target="_blank" rel="noopener noreferrer" class="">MCP 2025-11-25 规范</a> ｜ <a href="https://modelcontextprotocol.io/specification/2025-11-25/changelog" target="_blank" rel="noopener noreferrer" class="">变更日志</a></p>
<p>[21]. <a href="https://modelcontextprotocol.io/specification/2025-06-18" target="_blank" rel="noopener noreferrer" class="">MCP 2025-06-18 规范</a> ｜ <a href="https://modelcontextprotocol.io/specification/2025-06-18/changelog" target="_blank" rel="noopener noreferrer" class="">变更日志</a> ｜ <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle" target="_blank" rel="noopener noreferrer" class="">生命周期（initialize 握手）</a></p>
<p>[22]. <a href="https://modelcontextprotocol.io/specification/2025-03-26" target="_blank" rel="noopener noreferrer" class="">MCP 2025-03-26 规范</a> ｜ <a href="https://modelcontextprotocol.io/specification/2025-03-26/changelog" target="_blank" rel="noopener noreferrer" class="">变更日志</a></p>
<p>[23]. <a href="https://modelcontextprotocol.io/specification/2024-11-05" target="_blank" rel="noopener noreferrer" class="">MCP 2024-11-05 初版规范</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三官方博客blogmodelcontextprotocolio">三、官方博客（blog.modelcontextprotocol.io）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B8%89%E5%AE%98%E6%96%B9%E5%8D%9A%E5%AE%A2blogmodelcontextprotocolio" class="hash-link" aria-label="三、官方博客（blog.modelcontextprotocol.io）的直接链接" title="三、官方博客（blog.modelcontextprotocol.io）的直接链接" translate="no">​</a></h2>
<p>[24]. <a href="https://blog.modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">MCP 官方博客首页</a></p>
<p>[25]. <a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" target="_blank" rel="noopener noreferrer" class="">The 2026-07-28 Specification（正式版发布）</a></p>
<p>[26]. <a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" target="_blank" rel="noopener noreferrer" class="">The 2026-07-28 MCP Specification Release Candidate</a></p>
<p>[27]. <a href="https://blog.modelcontextprotocol.io/posts/sdk-betas-2026-07-28/" target="_blank" rel="noopener noreferrer" class="">Beta SDKs for the 2026-07-28 MCP Spec RC</a></p>
<p>[28]. <a href="https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/" target="_blank" rel="noopener noreferrer" class="">MCP Apps - Bringing UI Capabilities To MCP Clients</a></p>
<p>[29]. <a href="https://blog.modelcontextprotocol.io/posts/2025-11-25-first-mcp-anniversary/" target="_blank" rel="noopener noreferrer" class="">One Year of Model Context Protocol（2025-11-25 一周年）</a></p>
<p>[30]. <a href="https://blog.modelcontextprotocol.io/posts/2025-09-08-mcp-registry-preview/" target="_blank" rel="noopener noreferrer" class="">Introducing the MCP Registry（2025-09-08 预览）</a></p>
<p>[31]. <a href="https://blog.modelcontextprotocol.io/posts/2025-09-05-php-sdk/" target="_blank" rel="noopener noreferrer" class="">Announcing the official PHP SDK for MCP（2025-09-05）</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四官方仓库与-sdkgithubcommodelcontextprotocol">四、官方仓库与 SDK（github.com/modelcontextprotocol）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%9B%9B%E5%AE%98%E6%96%B9%E4%BB%93%E5%BA%93%E4%B8%8E-sdkgithubcommodelcontextprotocol" class="hash-link" aria-label="四、官方仓库与 SDK（github.com/modelcontextprotocol）的直接链接" title="四、官方仓库与 SDK（github.com/modelcontextprotocol）的直接链接" translate="no">​</a></h2>
<p>[32]. <a href="https://github.com/modelcontextprotocol" target="_blank" rel="noopener noreferrer" class="">MCP GitHub 组织</a></p>
<p>[33]. <a href="https://github.com/modelcontextprotocol/modelcontextprotocol" target="_blank" rel="noopener noreferrer" class="">规范与文档仓库 modelcontextprotocol</a></p>
<p>[34]. <a href="https://github.com/modelcontextprotocol/docs" target="_blank" rel="noopener noreferrer" class="">文档仓库 docs</a></p>
<p>[35]. <a href="https://github.com/modelcontextprotocol/servers" target="_blank" rel="noopener noreferrer" class="">参考 Server 仓库 servers</a></p>
<p>[36]. <a href="https://github.com/modelcontextprotocol/inspector" target="_blank" rel="noopener noreferrer" class="">调试工具 MCP Inspector</a></p>
<p>[37]. <a href="https://github.com/modelcontextprotocol/registry" target="_blank" rel="noopener noreferrer" class="">注册中心 registry</a> ｜ <a href="https://registry.modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">官方 Registry 站点</a></p>
<p>[38]. <a href="https://github.com/modelcontextprotocol/conformance" target="_blank" rel="noopener noreferrer" class="">一致性测试 conformance</a></p>
<p>[39]. <a href="https://github.com/modelcontextprotocol/python-sdk" target="_blank" rel="noopener noreferrer" class="">官方 SDK：Python</a> ｜ <a href="https://github.com/modelcontextprotocol/typescript-sdk" target="_blank" rel="noopener noreferrer" class="">TypeScript</a> ｜ <a href="https://github.com/modelcontextprotocol/java-sdk" target="_blank" rel="noopener noreferrer" class="">Java</a> ｜ <a href="https://github.com/modelcontextprotocol/csharp-sdk" target="_blank" rel="noopener noreferrer" class="">C#</a> ｜ <a href="https://github.com/modelcontextprotocol/go-sdk" target="_blank" rel="noopener noreferrer" class="">Go</a> ｜ <a href="https://github.com/modelcontextprotocol/kotlin-sdk" target="_blank" rel="noopener noreferrer" class="">Kotlin</a> ｜ <a href="https://github.com/modelcontextprotocol/php-sdk" target="_blank" rel="noopener noreferrer" class="">PHP</a> ｜ <a href="https://github.com/modelcontextprotocol/ruby-sdk" target="_blank" rel="noopener noreferrer" class="">Ruby</a> ｜ <a href="https://github.com/modelcontextprotocol/rust-sdk" target="_blank" rel="noopener noreferrer" class="">Rust</a> ｜ <a href="https://github.com/modelcontextprotocol/swift-sdk" target="_blank" rel="noopener noreferrer" class="">Swift</a></p>
<p>[40]. <a href="https://github.com/modelcontextprotocol/ext-apps" target="_blank" rel="noopener noreferrer" class="">官方扩展：MCP Apps（ext-apps）</a> ｜ <a href="https://github.com/modelcontextprotocol/ext-tasks" target="_blank" rel="noopener noreferrer" class="">Tasks（ext-tasks）</a> ｜ <a href="https://github.com/modelcontextprotocol/ext-auth" target="_blank" rel="noopener noreferrer" class="">Auth（ext-auth）</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五anthropic-公告">五、Anthropic 公告<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%BA%94anthropic-%E5%85%AC%E5%91%8A" class="hash-link" aria-label="五、Anthropic 公告的直接链接" title="五、Anthropic 公告的直接链接" translate="no">​</a></h2>
<p>[41]. <a href="https://www.anthropic.com/news/model-context-protocol" target="_blank" rel="noopener noreferrer" class="">Introducing the Model Context Protocol（2024-11-25 开源发布）</a></p>
<p>[42]. <a href="https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation" target="_blank" rel="noopener noreferrer" class="">Donating the Model Context Protocol and establishing the Agentic AI Foundation（2025-12-09）</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六相关标准与规范">六、相关标准与规范<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%85%AD%E7%9B%B8%E5%85%B3%E6%A0%87%E5%87%86%E4%B8%8E%E8%A7%84%E8%8C%83" class="hash-link" aria-label="六、相关标准与规范的直接链接" title="六、相关标准与规范的直接链接" translate="no">​</a></h2>
<p>[43]. <a href="https://www.jsonrpc.org/specification" target="_blank" rel="noopener noreferrer" class="">JSON-RPC 2.0 规范</a></p>
<p>[44]. <a href="https://microsoft.github.io/language-server-protocol/" target="_blank" rel="noopener noreferrer" class="">Language Server Protocol（LSP，MCP 的灵感来源）</a></p>
<p>[45]. <a href="https://json-schema.org/draft/2020-12/schema" target="_blank" rel="noopener noreferrer" class="">JSON Schema 2020-12</a></p>
<p>[46]. <a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1" target="_blank" rel="noopener noreferrer" class="">OAuth 2.1（IETF draft）</a></p>
<p>[47]. <a href="https://datatracker.ietf.org/doc/html/rfc8707" target="_blank" rel="noopener noreferrer" class="">RFC 8707 Resource Indicators for OAuth 2.0</a></p>
<p>[48]. <a href="https://datatracker.ietf.org/doc/html/rfc9207" target="_blank" rel="noopener noreferrer" class="">RFC 9207 OAuth 2.0 Authorization Server Issuer Identification</a></p>
<p>[49]. <a href="https://datatracker.ietf.org/doc/html/rfc7591" target="_blank" rel="noopener noreferrer" class="">RFC 7591 OAuth 2.0 Dynamic Client Registration（已被 MCP 弃用）</a></p>
<p>[50]. <a href="https://openid.net/specs/openid-connect-discovery-1_0.html" target="_blank" rel="noopener noreferrer" class="">OpenID Connect Discovery 1.0</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七延伸阅读第三方">七、延伸阅读（第三方）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E4%B8%83%E5%BB%B6%E4%BC%B8%E9%98%85%E8%AF%BB%E7%AC%AC%E4%B8%89%E6%96%B9" class="hash-link" aria-label="七、延伸阅读（第三方）的直接链接" title="七、延伸阅读（第三方）的直接链接" translate="no">​</a></h2>
<p>[51]. <a href="https://hidekazu-konishi.com/entry/mcp_specification_version_timeline.html" target="_blank" rel="noopener noreferrer" class="">Model Context Protocol Specification Version Timeline（版本演进时间线）</a></p>
<p>[52]. <a href="https://docs.langchain4j.dev/tutorials/mcp/" target="_blank" rel="noopener noreferrer" class="">LangChain4j MCP 官方教程</a></p>
<p>[53]. <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-helpers.html" target="_blank" rel="noopener noreferrer" class="">Spring AI MCP Utilities</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八java-构建与-agent-接入参考">八、Java 构建与 Agent 接入参考<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81MCP%E5%8D%8F%E8%AE%AE%E8%AF%A6%E8%A7%A3-%E5%8E%86%E5%8F%B2%E6%BC%94%E8%BF%9B%E4%B8%8E%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86#%E5%85%ABjava-%E6%9E%84%E5%BB%BA%E4%B8%8E-agent-%E6%8E%A5%E5%85%A5%E5%8F%82%E8%80%83" class="hash-link" aria-label="八、Java 构建与 Agent 接入参考的直接链接" title="八、Java 构建与 Agent 接入参考的直接链接" translate="no">​</a></h2>
<p>[54]. <a href="https://java.sdk.modelcontextprotocol.io/latest/" target="_blank" rel="noopener noreferrer" class="">MCP Java SDK 官方文档</a></p>
<p>[55]. <a href="https://java.sdk.modelcontextprotocol.io/latest/quickstart/" target="_blank" rel="noopener noreferrer" class="">MCP Java SDK Quickstart（依赖 / BOM）</a></p>
<p>[56]. <a href="https://java.sdk.modelcontextprotocol.io/latest/client/" target="_blank" rel="noopener noreferrer" class="">MCP Java SDK Client</a></p>
<p>[57]. <a href="https://java.sdk.modelcontextprotocol.io/latest/server/" target="_blank" rel="noopener noreferrer" class="">MCP Java SDK Server</a></p>
<p>[58]. <a href="https://java.agentscope.io/v1/en/docs/task/mcp" target="_blank" rel="noopener noreferrer" class="">AgentScope Java MCP 文档</a></p>
<p>[59]. <a href="https://github.com/agentscope-ai/agentscope-java" target="_blank" rel="noopener noreferrer" class="">AgentScope Java（GitHub）</a></p>
<p>[60]. <a href="https://doc.agentscope.io/tutorial/task_mcp.html" target="_blank" rel="noopener noreferrer" class="">AgentScope Python MCP 教程</a></p>
<p>[61]. <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html" target="_blank" rel="noopener noreferrer" class="">Spring AI MCP 总览</a></p>
<p>[62]. <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html" target="_blank" rel="noopener noreferrer" class="">Spring AI MCP Client Boot Starter</a></p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>通用ReActagent层</category>
            <category>Function Calling</category>
            <category>MCP</category>
        </item>
        <item>
            <title><![CDATA[适配HTTP协议转换FunctionCall协议01、AgentForge HTTP工具模块核心设计原理]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/http/适配HTTP协议转换FunctionCall协议01、AgentForge HTTP工具模块核心设计原理</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/http/适配HTTP协议转换FunctionCall协议01、AgentForge HTTP工具模块核心设计原理</guid>
            <pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[更新日期：2026-10-05]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>更新日期：2026-10-05<br>
<!-- -->适用模块：<code>agentforge-agent-core</code>（<code>cloud.changlu.agentforge.agent.tool.http</code>）<br>
<!-- -->依赖契约：<code>agentforge-model-api</code> / <code>agentforge-model-core</code><br>
<!-- -->维护者：changlu</p>
</blockquote>
<!-- -->
<p>一句话结论：<strong>HTTP 工具模块把"一组 HTTP 接口"声明式地映射为 AgentForge 的 <code>ToolSpecification</code> + <code>ToolExecutor</code>，让模型能像调用本地方法一样调用任意 REST 接口，而无需为零依赖核心引入任何 HTTP / JSON 第三方库。</strong></p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动接入钉钉机器人时我们卡在了哪">1.1、场景驱动：接入钉钉机器人时，我们卡在了哪<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E6%8E%A5%E5%85%A5%E9%92%89%E9%92%89%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%97%B6%E6%88%91%E4%BB%AC%E5%8D%A1%E5%9C%A8%E4%BA%86%E5%93%AA" class="hash-link" aria-label="1.1、场景驱动：接入钉钉机器人时，我们卡在了哪的直接链接" title="1.1、场景驱动：接入钉钉机器人时，我们卡在了哪的直接链接" translate="no">​</a></h2>
<p>在为 AgentForge 做外部能力接入时，我们遇到一个很现实的场景：</p>
<blockquote>
<p>团队希望 Agent 能发钉钉消息、查 CRM 客户、拉工单列表。这些能力<strong>既没有 MCP Server，也不值得为每个接口写一个 Java <code>@Tool</code> 方法</strong>——它们只是普通的 REST API。</p>
</blockquote>
<p>由此带来三个具体痛点：</p>
<ol>
<li class=""><strong>接口多</strong>：一个 SaaS 动辄几十个接口，逐个写 <code>@Tool</code> 方法维护成本极高；</li>
<li class=""><strong>模型无从下手</strong>：把"一个通用 HTTP 工具"丢给模型，它会自己编 URL、乱加参数，既不可控也不安全；</li>
<li class=""><strong>核心零依赖</strong>：AgentForge 核心是 Java 8 + 零第三方库，不能为接 HTTP 引入 OkHttp / Retrofit / Jackson。</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导如何让模型说人话就能调-http">1.2、问题引导：如何让模型"说人话"就能调 HTTP？<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BC%E5%A6%82%E4%BD%95%E8%AE%A9%E6%A8%A1%E5%9E%8B%E8%AF%B4%E4%BA%BA%E8%AF%9D%E5%B0%B1%E8%83%BD%E8%B0%83-http" class="hash-link" aria-label="1.2、问题引导：如何让模型&quot;说人话&quot;就能调 HTTP？的直接链接" title="1.2、问题引导：如何让模型&quot;说人话&quot;就能调 HTTP？的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>问题</strong>：如何让模型把一句自然语言，变成一次<strong>参数正确、地址可控</strong>的 HTTP 调用？</p>
</blockquote>
<p>答案不是"让模型自己拼 HTTP"，而是：</p>
<ul>
<li class="">由开发者<strong>预先声明</strong>每个接口（地址、方法、参数）→ 生成 <code>ToolSpecification</code>；</li>
<li class="">模型只在<strong>已声明的工具</strong>里选择、只填<strong>声明的参数</strong>；</li>
<li class="">运行时由执行器把参数"填"进 URL / Header / Body 并发起请求。</li>
</ul>
<p>这正是本模块要做的事。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13设计目标与约束">1.3、设计目标与约束<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#13%E8%AE%BE%E8%AE%A1%E7%9B%AE%E6%A0%87%E4%B8%8E%E7%BA%A6%E6%9D%9F" class="hash-link" aria-label="1.3、设计目标与约束的直接链接" title="1.3、设计目标与约束的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>目标</th><th>说明</th></tr></thead><tbody><tr><td>声明式</td><td>用 <code>HttpPlugin</code> 描述一组接口，不写胶水代码</td></tr><tr><td>零依赖</td><td>复用内置 <code>Json</code> 与 <code>HttpTransport</code>（<code>JdkHttpTransport</code>）</td></tr><tr><td>可控</td><td>模型只能调用已声明工具，URL 由插件固定</td></tr><tr><td>统一</td><td>产物是 <code>Map&lt;ToolSpecification, ToolExecutor&gt;</code>，与 local / mcp 同一张工具表</td></tr><tr><td>可扩展</td><td><code>IHttpPlugin</code> SPI + curl 互转，降低接入成本</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念讲解">二、核心概念讲解<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E8%AE%B2%E8%A7%A3" class="hash-link" aria-label="二、核心概念讲解的直接链接" title="二、核心概念讲解的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21三个核心抽象">2.1、三个核心抽象<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#21%E4%B8%89%E4%B8%AA%E6%A0%B8%E5%BF%83%E6%8A%BD%E8%B1%A1" class="hash-link" aria-label="2.1、三个核心抽象的直接链接" title="2.1、三个核心抽象的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">HttpPlugin                    # 一个服务：baseUrl + 公共请求头 + 一组方法</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  └── HttpPluginMethod        # 一个工具：name / description / httpMethod / uri / parameters</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └── HttpToolParameter # 一个参数：参数名 + 映射名 + 使用位置 + 数据类型 + 默认值 + 是否必填</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22参数的四维模型重点">2.2、参数的四维模型（重点）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#22%E5%8F%82%E6%95%B0%E7%9A%84%E5%9B%9B%E7%BB%B4%E6%A8%A1%E5%9E%8B%E9%87%8D%E7%82%B9" class="hash-link" aria-label="2.2、参数的四维模型（重点）的直接链接" title="2.2、参数的四维模型（重点）的直接链接" translate="no">​</a></h2>
<p>一个参数被四个维度描述，正是它让"声明"到"真实请求"的映射成为可能：</p>
<table><thead><tr><th>维度</th><th>枚举</th><th>作用</th></tr></thead><tbody><tr><td>使用位置</td><td><code>ParameterUseType</code>：QUERY / BODY / PATH / HEADER</td><td>决定参数最终落到 URL、请求体还是请求头</td></tr><tr><td>数据类型</td><td><code>ParameterType</code>：STRING / INTEGER / NUMBER / BOOLEAN / ARRAY / OBJECT</td><td>决定 JSON Schema 类型与运行时类型转换</td></tr><tr><td>映射名</td><td><code>mappedName</code></td><td>模型参数名 ↔ 真实接口参数名（内外解耦，可改名）</td></tr><tr><td>约束</td><td><code>defaultValue</code> / <code>required</code></td><td>缺省填充与必填校验</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：模型看到的是 <code>methodParamName</code>（工具参数名），真正发给接口的是 <code>mappedName</code>——这让"给模型看的名字"和"接口要的名字"可以不同。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="23与-agent-工具抽象的关系">2.3、与 Agent 工具抽象的关系<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#23%E4%B8%8E-agent-%E5%B7%A5%E5%85%B7%E6%8A%BD%E8%B1%A1%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="2.3、与 Agent 工具抽象的关系的直接链接" title="2.3、与 Agent 工具抽象的关系的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>HTTP 模块</th><th>AgentForge 契约</th></tr></thead><tbody><tr><td><code>HttpPluginMethod</code> + 参数</td><td><code>ToolSpecification</code>（<code>parameters</code> → JSON Schema）</td></tr><tr><td><code>HttpToolExecutor</code></td><td><code>ToolExecutor</code>（<code>execute</code> / <code>executeWithResult</code>）</td></tr><tr><td><code>HttpToolFactory.buildHttpTools</code></td><td>产出 <code>Map&lt;ToolSpecification, ToolExecutor&gt;</code>，交给 <code>ToolService</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="24为什么还要支持-curl">2.4、为什么还要支持 curl<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#24%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%98%E8%A6%81%E6%94%AF%E6%8C%81-curl" class="hash-link" aria-label="2.4、为什么还要支持 curl的直接链接" title="2.4、为什么还要支持 curl的直接链接" translate="no">​</a></h2>
<p>开发者手里最常见的"接口样本"就是一段 curl。支持 <code>curl → HttpPlugin</code>，能把"复制粘贴一段 curl"直接变成可注入 Agent 的工具；反向 <code>HttpPlugin → curl</code> 则方便调试与文档化。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三实现思路">三、实现思路<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="三、实现思路的直接链接" title="三、实现思路的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31三种方案对比">3.1、三种方案对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#31%E4%B8%89%E7%A7%8D%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="3.1、三种方案对比的直接链接" title="3.1、三种方案对比的直接链接" translate="no">​</a></h2>
<p><strong>方案一：每个接口写一个 <code>@Tool</code> 方法</strong></p>
<ul>
<li class="">优点：类型安全、可复用 local 模式。</li>
<li class="">弊端说明：接口多则代码膨胀；改一个字段要改代码；维护成本高。</li>
</ul>
<p><strong>方案二：一个通用 HTTP 工具（模型传 url / method / body）</strong></p>
<ul>
<li class="">优点：实现最简单。</li>
<li class="">弊端说明：<strong>模型可任意构造 URL / 参数</strong>，不可控、不安全，且易编造接口；难以做鉴权与白名单。</li>
</ul>
<p><strong>方案三：声明式 <code>HttpPlugin</code> 映射（本模块采用）</strong></p>
<ul>
<li class="">优点：地址固定、参数受控、零依赖、可批量描述、可与 local / mcp 统一进 <code>ToolService</code>。</li>
<li class="">弊端说明：需定义插件结构；复杂鉴权（OAuth 刷新等）仍需宿主层补。</li>
</ul>
<blockquote>
<p><strong>结论</strong>：采用<strong>方案三</strong>——用声明换可控，用映射换零依赖。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32核心流程list--spec--executor">3.2、核心流程：List → Spec + Executor<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#32%E6%A0%B8%E5%BF%83%E6%B5%81%E7%A8%8Blist--spec--executor" class="hash-link" aria-label="3.2、核心流程：List → Spec + Executor的直接链接" title="3.2、核心流程：List → Spec + Executor的直接链接" translate="no">​</a></h2>
<p><code>HttpToolFactory.buildHttpTools(httpPlugin)</code> 对每个 <code>HttpPluginMethod</code> 生成一对：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">HttpPluginMethod</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├─ methodName / description ───────────► ToolSpecification.name / description</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├─ parameters[].toJsonSchema() ────────► ToolSpecification.parameters（JSON Schema）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   └─ (baseUrl+uri, httpMethod, headers, 参数配置) ─► new HttpToolExecutor(...)</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：与 MCP 的 <code>serverAlias__tool</code> 不同，HTTP 工具<strong>不加前缀</strong>——工具名直接用 <code>methodName</code>，天然由开发者控制唯一性。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33参数处理流水线核心">3.3、参数处理流水线（核心）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#33%E5%8F%82%E6%95%B0%E5%A4%84%E7%90%86%E6%B5%81%E6%B0%B4%E7%BA%BF%E6%A0%B8%E5%BF%83" class="hash-link" aria-label="3.3、参数处理流水线（核心）的直接链接" title="3.3、参数处理流水线（核心）的直接链接" translate="no">​</a></h2>
<p><code>HttpToolExecutor.execute</code> 内部按固定顺序处理：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">arguments(JSON) ──► argumentsAsMap</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">processArguments      ① 取实参 / 套默认值 / 校验必填 / 按 dataType 转换</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├─ buildUrlWithPathParams   ② PATH 参数替换 {mappedName}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├─ addHeaderParameters      ③ HEADER 参数写入请求头</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├─ shouldUseJsonBody ?</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     ├─ true  → BODY 参数 → JSON Body（Content-Type: application/json）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │     └─ false → appendQueryParameters（QUERY 参数拼到 URL）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">HttpRequest(url, method, headers, body) ──► HttpTransport.execute</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>必填缺失</strong>：抛 <code>IllegalArgumentException("缺少必填参数 'x'")</code>；</li>
<li class=""><strong>类型转换</strong>：<code>convertValueToCorrectType</code> 支持 INTEGER / NUMBER / BOOLEAN / ARRAY / OBJECT / STRING，并对 JSON 字符串做容错解析。</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="34请求体的智能判断">3.4、请求体的"智能判断"<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#34%E8%AF%B7%E6%B1%82%E4%BD%93%E7%9A%84%E6%99%BA%E8%83%BD%E5%88%A4%E6%96%AD" class="hash-link" aria-label="3.4、请求体的&quot;智能判断&quot;的直接链接" title="3.4、请求体的&quot;智能判断&quot;的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">GET / DELETE   → 强制用 Query（即使配了 BODY 也不发 Body）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST / PUT / PATCH → 只有当存在 BODY 参数（已传或有默认值）时才发 JSON Body</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：GET 即使配了 BODY 类型参数也不会发送请求体（由 <code>shouldNotSendJsonBodyForGetEvenWithBodyConfig</code> 用例保证）。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="35重试与容错">3.5、重试与容错<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#35%E9%87%8D%E8%AF%95%E4%B8%8E%E5%AE%B9%E9%94%99" class="hash-link" aria-label="3.5、重试与容错的直接链接" title="3.5、重试与容错的直接链接" translate="no">​</a></h2>
<ul>
<li class="">仅对 <strong>GET / DELETE</strong> 的 <code>connection reset</code> 做<strong>一次</strong>重试（幂等语义下安全）；</li>
<li class="">其余异常在 <code>execute</code> 中兜底为文本 <code>"HTTP请求失败: {message}"</code>。</li>
</ul>
<blockquote>
<p><strong>弊端说明</strong>：当前 <code>execute</code> 把错误降级为<strong>文本</strong>，<code>isError</code> 仍为 <code>false</code>；需要结构化错误时可在上层用 <code>ToolExecutionResult</code> 包装（对比：MCP 执行器已做 <code>isError</code> 无损映射）。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="36curl-解析与互转">3.6、curl 解析与互转<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#36curl-%E8%A7%A3%E6%9E%90%E4%B8%8E%E4%BA%92%E8%BD%AC" class="hash-link" aria-label="3.6、curl 解析与互转的直接链接" title="3.6、curl 解析与互转的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>方向</th><th>入口</th><th>说明</th></tr></thead><tbody><tr><td>curl → 插件</td><td><code>CurlParser.parse</code> + <code>CurlToHttpPluginConverter.convert</code></td><td>拆出 url / method / headers / query / body，生成参数</td></tr><tr><td>插件 → curl</td><td><code>HttpPluginToCurlConverter.convert(plugin[, methodName])</code></td><td>反向生成可复现命令；多方法插件需指定方法名</td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：一个插件含多个方法时，<code>convert(plugin)</code> 会拒绝，必须显式指定 <code>methodName</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="37能力矩阵请求方法与参数格式">3.7、能力矩阵：请求方法与参数格式<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#37%E8%83%BD%E5%8A%9B%E7%9F%A9%E9%98%B5%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95%E4%B8%8E%E5%8F%82%E6%95%B0%E6%A0%BC%E5%BC%8F" class="hash-link" aria-label="3.7、能力矩阵：请求方法与参数格式的直接链接" title="3.7、能力矩阵：请求方法与参数格式的直接链接" translate="no">​</a></h2>
<p><strong>① 支持的 HTTP 方法（<code>HttpPluginEnums.HttpMethod</code>）</strong></p>
<table><thead><tr><th>方法</th><th>Code</th><th>请求体</th><th>幂等重试</th><th>说明</th></tr></thead><tbody><tr><td>GET</td><td>1</td><td>否（<strong>强制 Query</strong>）</td><td>✅ 连接重置重试 1 次</td><td>查询</td></tr><tr><td>POST</td><td>2</td><td>有 BODY 参数时发 JSON</td><td>❌</td><td>创建</td></tr><tr><td>PUT</td><td>3</td><td>有 BODY 参数时发 JSON</td><td>❌</td><td>全量更新</td></tr><tr><td>DELETE</td><td>4</td><td>否（强制 Query）</td><td>✅ 连接重置重试 1 次</td><td>删除（幂等）</td></tr><tr><td>PATCH</td><td>5</td><td>有 BODY 参数时发 JSON</td><td>❌</td><td>局部更新</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：GET / DELETE 配置为 BODY 的参数<strong>不会</strong>进请求体，会被忽略（见 <code>shouldNotSendJsonBodyForGetEvenWithBodyConfig</code>）。</p>
</blockquote>
<br>
<p><strong>② 参数使用位置（<code>ParameterUseType</code>）</strong></p>
<table><thead><tr><th>位置</th><th>Code</th><th>落点</th><th>生成规则</th></tr></thead><tbody><tr><td>QUERY</td><td>1</td><td>URL 查询串</td><td><code>encode(mappedName)=encode(value)</code></td></tr><tr><td>BODY</td><td>2</td><td>JSON 请求体</td><td><code>{mappedName: value}</code> → <code>Json.stringify</code></td></tr><tr><td>PATH</td><td>3</td><td>URL 路径占位符</td><td>替换 <code>{mappedName}</code></td></tr><tr><td>HEADER</td><td>4</td><td>请求头</td><td><code>mappedName: value</code></td></tr></tbody></table>
<br>
<p><strong>③ 参数数据类型（<code>ParameterType</code>）</strong></p>
<table><thead><tr><th>类型</th><th>Code</th><th>JSON Schema</th><th>运行时转换</th></tr></thead><tbody><tr><td>STRING</td><td>1</td><td><code>string</code></td><td><code>value.toString()</code></td></tr><tr><td>INTEGER</td><td>2</td><td><code>integer</code></td><td><code>Integer.parseInt</code> / <code>Number.intValue()</code></td></tr><tr><td>NUMBER</td><td>3</td><td><code>number</code></td><td><code>Double.parseDouble</code> / <code>Number.doubleValue()</code></td></tr><tr><td>BOOLEAN</td><td>4</td><td><code>boolean</code></td><td><code>"true"/"1"/"yes"</code> → true，否则 false</td></tr><tr><td>ARRAY</td><td>5</td><td><code>array</code>（items: string）</td><td>List 直用；JSON 字符串解析；否则包成单元素数组</td></tr><tr><td>OBJECT</td><td>6</td><td><code>object</code></td><td>Map 直用；JSON 字符串解析；否则空 Map</td></tr></tbody></table>
<br>
<p><strong>④ 必填与默认值（<code>RequiredStatus</code> + <code>defaultValue</code>）</strong></p>
<table><thead><tr><th>项</th><th>Code</th><th>行为</th></tr></thead><tbody><tr><td>REQUIRED</td><td>1</td><td>实参与默认值都缺失 → 抛 <code>IllegalArgumentException("缺少必填参数 'x'")</code></td></tr><tr><td>NOT_REQUIRED</td><td>0</td><td>缺失即不参与本次请求</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：取值优先级为 <strong>实参 &gt; <code>defaultValue</code> &gt; 必填校验</strong>；<code>methodParamName</code> 是模型侧名字，<code>mappedName</code> 才是发给接口的名字。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四实战代码">四、实战代码<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E5%9B%9B%E5%AE%9E%E6%88%98%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="四、实战代码的直接链接" title="四、实战代码的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41最小接入">4.1、最小接入<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#41%E6%9C%80%E5%B0%8F%E6%8E%A5%E5%85%A5" class="hash-link" aria-label="4.1、最小接入的直接链接" title="4.1、最小接入的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">HttpPlugin</span><span class="token plain"> plugin </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">HttpPlugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">baseUrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://api.example.com"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">staticHeaders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singletonMap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Bearer "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">pluginMethods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singletonList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token class-name">HttpPluginMethod</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">methodName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">methodDescription</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"查询城市天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">httpMethodType</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">HttpPluginEnums</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token class-name">HttpMethod</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">GET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">uri</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/v1/weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                </span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singletonList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                        </span><span class="token class-name">HttpToolParameter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">methodParamName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">methodParamDescription</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">mappedName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"q"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">useTypeValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                        </span><span class="token class-name">HttpPluginEnums</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token class-name">ParameterUseType</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">QUERY</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">dataTypeValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                        </span><span class="token class-name">HttpPluginEnums</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token class-name">ParameterType</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">STRING</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">required</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                        </span><span class="token class-name">HttpPluginEnums</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token class-name">RequiredStatus</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">REQUIRED</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getCode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                                                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">HttpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildHttpTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">plugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ReActAgent</span><span class="token plain"> agent </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">ReActAgent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 1) 注册后模型可见的工具（tools[] 摘要）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">getWeather</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 查询城市天气</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  parameters: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> city: string </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">required</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># 模型参数名 city → 实际接口参数名 ?q</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 2) 执行 getWeather(city=杭州) 的 HTTP 往返（java.util.logging，INFO）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"> HTTP 请求详情 </span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">URL: https://api.example.com/v1/weather?q</span><span class="token operator">=</span><span class="token plain">%E6%9D%AD%E5%B7%9E</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">方法: GET</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">请求头: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">Authorization</span><span class="token operator">=</span><span class="token plain">Bearer sk-***</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">QUERY 参数: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">q</span><span class="token operator">=</span><span class="token plain">杭州</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"> HTTP 响应详情 </span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">状态码: </span><span class="token number">200</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">耗时: </span><span class="token number">143</span><span class="token plain"> 毫秒</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">响应体: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"temp"</span><span class="token plain">:26,</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"晴"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">=</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42通过-ihttpplugin-扩展">4.2、通过 <code>IHttpPlugin</code> 扩展<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#42%E9%80%9A%E8%BF%87-ihttpplugin-%E6%89%A9%E5%B1%95" class="hash-link" aria-label="42通过-ihttpplugin-扩展的直接链接" title="42通过-ihttpplugin-扩展的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">DingTalkPlugin</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">implements</span><span class="token plain"> </span><span class="token class-name">IHttpPlugin</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token class-name">Properties</span><span class="token plain"> props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">init</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Properties</span><span class="token plain"> props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">props </span><span class="token operator">=</span><span class="token plain"> props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getPluginName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"dingtalk"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">doCheckProps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getProperty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">IllegalArgumentException</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"missing token"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">HttpPlugin</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getHttpPlugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">/* 依据 props 构建 */</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> plugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">DingTalkPlugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildHttpTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="43curl-一键转换">4.3、curl 一键转换<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#43curl-%E4%B8%80%E9%94%AE%E8%BD%AC%E6%8D%A2" class="hash-link" aria-label="4.3、curl 一键转换的直接链接" title="4.3、curl 一键转换的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">CurlParseResult</span><span class="token plain"> curl </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">CurlParser</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token string" style="color:rgb(255, 121, 198)">"curl -X POST 'https://api.example.com/v1/tickets' "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"-H 'Content-Type: application/json' "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"-d '{\"title\":\"bug\"}'"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">HttpPlugin</span><span class="token plain"> plugin </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">CurlToHttpPluginConverter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">convert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">curl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">HttpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildHttpTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">plugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># CurlParser.parse(...) 解析出的关键字段</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">method  </span><span class="token operator">=</span><span class="token plain"> POST</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">url     </span><span class="token operator">=</span><span class="token plain"> https://api.example.com/v1/tickets</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">headers </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">Content-Type</span><span class="token operator">=</span><span class="token plain">application/json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data    </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"title"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"bug"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># CurlToHttpPluginConverter.convert(...) 生成的 HttpPlugin</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">baseUrl </span><span class="token operator">=</span><span class="token plain"> https://api.example.com</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">uri     </span><span class="token operator">=</span><span class="token plain"> /v1/tickets</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">method  </span><span class="token operator">=</span><span class="token plain"> POST</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">params  </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> title: string </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BODY:title, required</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># buildHttpTools(...) → 模型可见工具（方法名由 curl 自动生成 executePOST）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">executePOST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> （由 </span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> 生成）  body: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"title"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"..."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="44一次模型调用如何被闭环">4.4、一次模型调用如何被闭环<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#44%E4%B8%80%E6%AC%A1%E6%A8%A1%E5%9E%8B%E8%B0%83%E7%94%A8%E5%A6%82%E4%BD%95%E8%A2%AB%E9%97%AD%E7%8E%AF" class="hash-link" aria-label="4.4、一次模型调用如何被闭环的直接链接" title="4.4、一次模型调用如何被闭环的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>对应单测</strong>：<code>HttpToolLoopTest#shouldCloseTheWeatherLoopThroughToolService</code>——注入 <code>RecordingHttpTransport</code>，离线完整走通"声明 → 注册 → 命中 → 组装 → 发请求 → 回灌"。</p>
</blockquote>
<p>以 4.1 的 <code>getWeather</code> 为例，逐环节拆解：</p>
<p><strong>① 声明与注册（build 期）</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">HttpToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildHttpTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function" style="color:rgb(80, 250, 123)">weatherPlugin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> transport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// transport 可注入（测试用假实现）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">                                           </span><span class="token comment" style="color:rgb(98, 114, 164)">// toolExecutors["getWeather"] = executor</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>ToolSpecification.name = "getWeather"</code>，<code>parameters = { city: string(required) }</code>；</li>
<li class=""><code>HttpToolExecutor(baseUrl + "/v1/weather", GET, {Authorization=Bearer token}, {city → (QUERY, STRING, required)})</code>。</li>
</ul>
<p><strong>② 模型侧 function call</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"杭州\"}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><code>arguments</code> 是 <strong>JSON 字符串</strong>，不是对象。</p>
</blockquote>
<p><strong>③ 归一化为请求</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionRequest{ id="call_1", name="getWeather", arguments="{\"city\":\"杭州\"}" }</span><br></div></code></pre></div></div>
<p><strong>④ ToolService 定位执行器</strong></p>
<p><code>toolExecutors.get("getWeather")</code> → 命中 <code>HttpToolExecutor</code>（HTTP 工具<strong>不加前缀</strong>，名字即 <code>methodName</code>）。</p>
<p><strong>⑤ 参数处理（processArguments）</strong></p>
<table><thead><tr><th>步骤</th><th>输入</th><th>结果</th></tr></thead><tbody><tr><td><code>argumentsAsMap</code></td><td><code>"{\"city\":\"杭州\"}"</code></td><td><code>{city: 杭州}</code></td></tr><tr><td>取实参</td><td>key = <code>city</code></td><td><code>杭州</code></td></tr><tr><td>必填校验</td><td>REQUIRED</td><td>通过</td></tr><tr><td>类型转换</td><td>STRING</td><td><code>"杭州"</code></td></tr><tr><td>位置判定</td><td>QUERY，映射名 <code>q</code></td><td>待拼到 URL</td></tr></tbody></table>
<p><strong>⑥ 组装 <code>HttpRequest</code></strong></p>
<ul>
<li class="">PATH：无；HEADER：无（静态 <code>Authorization</code> 已在 <code>staticHeaders</code>）；</li>
<li class=""><code>shouldUseJsonBody</code>：GET → <code>false</code>（不发请求体）；</li>
<li class=""><code>appendQueryParameters</code> → <code>?q=%E6%9D%AD%E5%B7%9E</code>；</li>
<li class="">最终：<code>GET https://api.example.com/v1/weather?q=%E6%9D%AD%E5%B7%9E</code>，<code>body=""</code>。</li>
</ul>
<p><strong>⑦ 发起并取回响应</strong></p>
<p><code>JdkHttpTransport.execute(request)</code> → <code>200</code>，响应体 <code>{"city":"杭州","temp":26,"text":"晴"}</code>；<code>HttpToolExecutor.execute</code> 直接返回<strong>响应体文本</strong>。</p>
<p><strong>⑧ 回灌模型</strong></p>
<p>响应体文本作为工具结果回灌 → 模型据此给出最终答复。</p>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  查一下杭州天气</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">think</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 需要调用工具 getWeather</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">act</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">   getWeather </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">http</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  GET https://api.example.com/v1/weather?q</span><span class="token operator">=</span><span class="token plain">%E6%9D%AD%E5%B7%9E → </span><span class="token number">200</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"temp"</span><span class="token plain">:26,</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"晴"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">think</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 杭州当前 </span><span class="token number">26</span><span class="token plain">℃，天气晴。</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">final</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 杭州当前 </span><span class="token number">26</span><span class="token plain">℃，天气晴。</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五验证测试">五、验证测试<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%BA%94%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="五、验证测试的直接链接" title="五、验证测试的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51单元测试概览">5.1、单元测试概览<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#51%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%A6%82%E8%A7%88" class="hash-link" aria-label="5.1、单元测试概览的直接链接" title="5.1、单元测试概览的直接链接" translate="no">​</a></h2>
<p>用 <code>RecordingHttpTransport</code> 记录收到的 <code>HttpRequest</code>，断言 URL / 请求头 / 请求体，无需真实网络。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52测试清单当前实现全部通过">5.2、测试清单（当前实现，全部通过）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#52%E6%B5%8B%E8%AF%95%E6%B8%85%E5%8D%95%E5%BD%93%E5%89%8D%E5%AE%9E%E7%8E%B0%E5%85%A8%E9%83%A8%E9%80%9A%E8%BF%87" class="hash-link" aria-label="5.2、测试清单（当前实现，全部通过）的直接链接" title="5.2、测试清单（当前实现，全部通过）的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>用例</th><th>覆盖点</th></tr></thead><tbody><tr><td><code>shouldSendGetQueryParameters</code></td><td>QUERY 参数拼接</td></tr><tr><td><code>shouldSubstitutePathParameters</code></td><td>PATH 占位符替换</td></tr><tr><td><code>shouldSendJsonBodyForPost</code></td><td>BODY → JSON Body</td></tr><tr><td><code>shouldConvertArrayAndObjectBodyValues</code></td><td>ARRAY / OBJECT 类型转换</td></tr><tr><td><code>shouldParseJsonEncodedArrayString</code></td><td>字符串数组容错</td></tr><tr><td><code>shouldAddStaticAndDynamicHeaders</code></td><td>静态 + HEADER 参数</td></tr><tr><td><code>shouldApplyDefaultValueWhenArgumentMissing</code></td><td>默认值</td></tr><tr><td><code>shouldThrowWhenRequiredArgumentMissing</code></td><td>必填校验</td></tr><tr><td><code>shouldRetryOnceOnConnectionResetForGet</code></td><td>幂等重试</td></tr><tr><td><code>shouldReturnFailureTextWhenPostFails</code></td><td>失败降级文本</td></tr><tr><td><code>shouldNotSendJsonBodyForGetEvenWithBodyConfig</code></td><td>GET 不发 Body</td></tr><tr><td><code>HttpToolLoopTest#shouldCloseTheWeatherLoopThroughToolService</code></td><td><strong>端到端闭环</strong>：插件构建 → ToolService → function call → GET 请求 → 回灌（对齐 4.4）</td></tr><tr><td><code>HttpToolFactoryTest</code></td><td>Spec / Executor 构建、<code>IHttpPlugin</code>、null 拒绝</td></tr><tr><td><code>CurlParserConverterTest</code></td><td>curl 解析、双向转换、多方法拒绝</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="53边界与风险">5.3、边界与风险<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#53%E8%BE%B9%E7%95%8C%E4%B8%8E%E9%A3%8E%E9%99%A9" class="hash-link" aria-label="5.3、边界与风险的直接链接" title="5.3、边界与风险的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>错误语义</strong>：<code>execute</code> 把异常降级为文本，<code>isError=false</code>，需要结构化错误要自行包装。</li>
<li class=""><strong>鉴权复杂度</strong>：静态头 / 参数够用；OAuth 刷新、签名等需宿主层实现。</li>
<li class=""><strong>超时</strong>：默认连接 10 分钟、读取 15 分钟（构造时可调），偏宽松，按场景收紧。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六总结与展望">六、总结与展望<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E5%85%AD%E6%80%BB%E7%BB%93%E4%B8%8E%E5%B1%95%E6%9C%9B" class="hash-link" aria-label="六、总结与展望的直接链接" title="六、总结与展望的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>本质</strong>：把"一组 HTTP 接口"声明式映射成模型可调用的 <code>ToolSpecification</code> + <code>ToolExecutor</code>。</li>
<li class=""><strong>核心</strong>：四维参数模型（位置 × 类型 × 映射名 × 约束）+ 固定顺序的参数流水线。</li>
<li class=""><strong>零依赖</strong>：复用内置 <code>Json</code> 与 <code>JdkHttpTransport</code>，与 Java 8 / 零依赖约束一致。</li>
<li class=""><strong>统一</strong>：产物与其他模式一样是 <code>Map&lt;ToolSpecification, ToolExecutor&gt;</code>，可同表共存。</li>
<li class=""><strong>展望</strong>：结构化错误（<code>isError</code>）、OpenAPI 导入、更细的超时 / 重试策略。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/http/%E9%80%82%E9%85%8DHTTP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20HTTP%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods" target="_blank" rel="noopener noreferrer" class="">MDN HTTP 请求方法</a></p>
<p>[2]. <a href="https://json-schema.org/" target="_blank" rel="noopener noreferrer" class="">JSON Schema 官方站点</a></p>
<p>[3]. <a href="https://curl.se/docs/manpage.html" target="_blank" rel="noopener noreferrer" class="">curl 命令手册</a></p>
<p>[4]. <a href="https://www.rfc-editor.org/rfc/rfc7231" target="_blank" rel="noopener noreferrer" class="">RFC 7231：HTTP/1.1 语义与方法</a></p>
<p>[5]. <a href="https://spec.openapis.org/oas/latest.html" target="_blank" rel="noopener noreferrer" class="">OpenAPI 规范</a></p>
<p>[6]. 相关内部文档：[Local 工具模块核心设计原理](/blog/02、通用ReActagent层/functioncall核心扩展业务tool/local/Java代码转换FunctionCall协议01、AgentForge Local工具模块核心设计原理)、<a class="" href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0">MCP 框架调研与 AgentForge 接入实现</a>、[ChatModel 核心协议层设计](/blog/01、model模型协议层/chatmodel/统一ChatModel协议层01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现)</p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>通用ReActagent层</category>
            <category>Function Calling</category>
            <category>HTTP</category>
        </item>
        <item>
            <title><![CDATA[Java代码转换FunctionCall协议01、AgentForge Local工具模块核心设计原理]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/local/Java代码转换FunctionCall协议01、AgentForge Local工具模块核心设计原理</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/02、通用ReActagent层/functioncall核心扩展业务tool/local/Java代码转换FunctionCall协议01、AgentForge Local工具模块核心设计原理</guid>
            <pubDate>Wed, 07 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[更新日期：2026-10-05]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>更新日期：2026-10-05<br>
<!-- -->适用模块：<code>agentforge-agent-core</code>（<code>cloud.changlu.agentforge.agent.tool.local</code>）<br>
<!-- -->依赖契约：<code>agentforge-model-api</code> / <code>agentforge-model-core</code><br>
<!-- -->维护者：changlu</p>
</blockquote>
<!-- -->
<p>一句话结论：<strong>Local 工具模块把 Java 对象上标注 <code>@Tool</code> 的方法自动扫描为 <code>ToolSpecification</code>，并用反射把模型给的 JSON 参数绑定到方法入参——零依赖地实现"让模型直接调用你的 Java 方法"。</strong></p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动把内部方法顺手暴露给模型">1.1、场景驱动：把内部方法"顺手"暴露给模型<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E6%8A%8A%E5%86%85%E9%83%A8%E6%96%B9%E6%B3%95%E9%A1%BA%E6%89%8B%E6%9A%B4%E9%9C%B2%E7%BB%99%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="1.1、场景驱动：把内部方法&quot;顺手&quot;暴露给模型的直接链接" title="1.1、场景驱动：把内部方法&quot;顺手&quot;暴露给模型的直接链接" translate="no">​</a></h2>
<p>在把 AgentForge 接入内部 CRM、工单系统时，我们遇到一个很现实的场景：</p>
<blockquote>
<p>团队已经有 <code>CrmService.queryCustomer(...)</code>、<code>TicketService.createTicket(...)</code> 这样一批现成的 Java 方法，希望<strong>几乎零改动</strong>地让 Agent 调用它们。</p>
</blockquote>
<p>由此带来三个具体痛点：</p>
<ol>
<li class=""><strong>手工登记</strong>：为每个方法手写工具描述与 JSON Schema，重复且易错；</li>
<li class=""><strong>参数难绑</strong>：模型给的是 JSON，要手动解析、做类型转换（枚举、数组、数值边界……）；</li>
<li class=""><strong>不能引重依赖</strong>：核心是 Java 8 + 零第三方库，不能用 Jackson / Gson 做反序列化。</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导如何让普通-java-方法零成本变工具">1.2、问题引导：如何让"普通 Java 方法"零成本变工具？<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BC%E5%A6%82%E4%BD%95%E8%AE%A9%E6%99%AE%E9%80%9A-java-%E6%96%B9%E6%B3%95%E9%9B%B6%E6%88%90%E6%9C%AC%E5%8F%98%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="1.2、问题引导：如何让&quot;普通 Java 方法&quot;零成本变工具？的直接链接" title="1.2、问题引导：如何让&quot;普通 Java 方法&quot;零成本变工具？的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>问题</strong>：如何让一个已经写好的 Java 方法，被打个注解就变成模型可调用的工具？</p>
</blockquote>
<p>核心要解决三件事：</p>
<ol>
<li class=""><strong>声明</strong>：从方法签名自动生成 <code>ToolSpecification</code>（名字、描述、参数 Schema）；</li>
<li class=""><strong>绑定</strong>：把模型返回的 <code>arguments</code>（JSON 字符串）绑定到方法入参；</li>
<li class=""><strong>转换</strong>：把 JSON 里的 <code>Long</code> / <code>String</code> 等，安全地转成方法期望的 <code>int</code> / <code>Enum</code> / <code>List</code> / <code>BigDecimal</code>。</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13设计目标与约束">1.3、设计目标与约束<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#13%E8%AE%BE%E8%AE%A1%E7%9B%AE%E6%A0%87%E4%B8%8E%E7%BA%A6%E6%9D%9F" class="hash-link" aria-label="1.3、设计目标与约束的直接链接" title="1.3、设计目标与约束的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>目标</th><th>说明</th></tr></thead><tbody><tr><td>注解驱动</td><td>只加 <code>@Tool</code> / <code>@P</code>，不改业务逻辑</td></tr><tr><td>强类型绑定</td><td>支持枚举、数值边界、BigDecimal / BigInteger、UUID、集合 / Map</td></tr><tr><td>零依赖</td><td>复用内置 <code>Json</code>，不引 JSON 库</td></tr><tr><td>统一</td><td>产物是 <code>Map&lt;ToolSpecification, ToolExecutor&gt;</code>，与 http / mcp 同一张工具表</td></tr><tr><td>可观测</td><td>可选的执行日志装饰器</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念讲解">二、核心概念讲解<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E8%AE%B2%E8%A7%A3" class="hash-link" aria-label="二、核心概念讲解的直接链接" title="二、核心概念讲解的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21两个注解tool-与-p">2.1、两个注解：<code>@Tool</code> 与 <code>@P</code><a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#21%E4%B8%A4%E4%B8%AA%E6%B3%A8%E8%A7%A3tool-%E4%B8%8E-p" class="hash-link" aria-label="21两个注解tool-与-p的直接链接" title="21两个注解tool-与-p的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询城市天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getWeather</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> city</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> weatherApi</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">city</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>@Tool</code>：标记方法为工具；<code>name</code> 缺省用方法名，<code>value</code> 为描述；</li>
<li class=""><code>@P</code>：覆盖参数名与描述（当 javac 未开 <code>-parameters</code> 时，反射拿到的是 <code>arg0</code>，靠它兜底）。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：参数<strong>名称</strong>决定模型 JSON 的键；若未加 <code>@P</code> 且未开 <code>-parameters</code>，键会变成 <code>arg0</code>，所以生产环境务必显式命名。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22反射生成-schema">2.2、反射生成 Schema<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#22%E5%8F%8D%E5%B0%84%E7%94%9F%E6%88%90-schema" class="hash-link" aria-label="2.2、反射生成 Schema的直接链接" title="2.2、反射生成 Schema的直接链接" translate="no">​</a></h2>
<p><code>ToolSpecifications.toolSpecificationFrom(method)</code> 从方法签名推导：</p>
<table><thead><tr><th>方法要素</th><th>映射到</th></tr></thead><tbody><tr><td><code>@Tool.name</code> / 方法名</td><td><code>ToolSpecification.name</code></td></tr><tr><td><code>@Tool.value</code></td><td><code>ToolSpecification.description</code></td></tr><tr><td>每个参数名（<code>@P.name</code> / 反射名）</td><td>JSON Schema <code>properties</code> 键</td></tr><tr><td>参数类型</td><td>JSON Schema <code>type</code>（string / integer / number / boolean / array / object）</td></tr><tr><td>参数</td><td><strong>一律 required</strong>（当前实现固定 <code>true</code>）</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="23localtoolexecutor反射调用器">2.3、<code>LocalToolExecutor</code>：反射调用器<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#23localtoolexecutor%E5%8F%8D%E5%B0%84%E8%B0%83%E7%94%A8%E5%99%A8" class="hash-link" aria-label="23localtoolexecutor反射调用器的直接链接" title="23localtoolexecutor反射调用器的直接链接" translate="no">​</a></h2>
<p>它持有 <strong>对象实例 + 方法</strong>，<code>execute</code> 时把参数绑定后反射调用：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">arguments(JSON) ──► LocalToolExecutionRequestUtil.argumentsAsMap</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               prepareArguments(originalMethod, map)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          每个参数：@P 名 → 取值 → coerceArgument 类型转换</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               methodToInvoke.invoke(object, args)   （代理场景用原始方法取参数名）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                          ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">               返回值规范化 → String（回灌模型）</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="24返回值约定重点">2.4、返回值约定（重点）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#24%E8%BF%94%E5%9B%9E%E5%80%BC%E7%BA%A6%E5%AE%9A%E9%87%8D%E7%82%B9" class="hash-link" aria-label="2.4、返回值约定（重点）的直接链接" title="2.4、返回值约定（重点）的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>方法返回类型</th><th>回给模型的内容</th></tr></thead><tbody><tr><td><code>void</code></td><td>字面量 <code>"Success"</code></td></tr><tr><td><code>String</code></td><td>原样返回</td></tr><tr><td>其它类型</td><td><code>Json.stringify(result)</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="25异常策略">2.5、异常策略<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#25%E5%BC%82%E5%B8%B8%E7%AD%96%E7%95%A5" class="hash-link" aria-label="2.5、异常策略的直接链接" title="2.5、异常策略的直接链接" translate="no">​</a></h2>
<p><code>LocalToolExecutor</code> 提供两个开关（默认均 <code>false</code>）：</p>
<table><thead><tr><th>开关</th><th>关闭（默认）</th><th>打开</th></tr></thead><tbody><tr><td><code>wrapToolArgumentsExceptions</code></td><td>参数错误按原样抛出</td><td>包装为 <code>ToolArgumentsException</code></td></tr><tr><td><code>propagateToolExecutionExceptions</code></td><td>方法内部异常 → 返回异常消息文本</td><td>抛出 <code>ToolExecutionException(cause)</code></td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：默认"方法抛异常 → 把消息当结果回给模型"，利于模型自纠；需要强失败语义时打开 <code>propagateToolExecutionExceptions</code>。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三实现思路">三、实现思路<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="三、实现思路的直接链接" title="三、实现思路的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31三种方案对比">3.1、三种方案对比<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#31%E4%B8%89%E7%A7%8D%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="3.1、三种方案对比的直接链接" title="3.1、三种方案对比的直接链接" translate="no">​</a></h2>
<p><strong>方案一：为每个方法手写 <code>ToolExecutor</code></strong></p>
<ul>
<li class="">优点：完全可控。</li>
<li class="">弊端说明：大量重复的 JSON 解析与类型转换代码，维护成本高。</li>
</ul>
<p><strong>方案二：引入 Jackson / Gson 直接反序列化到 DTO</strong></p>
<ul>
<li class="">优点：开发体验好。</li>
<li class="">弊端说明：<strong>破坏核心零依赖</strong>（Java 8、无第三方库），且与 AgentForge 内置 <code>Json</code> 体系割裂。</li>
</ul>
<p><strong>方案三：注解 + 反射 + 自研类型转换（本模块采用）</strong></p>
<ul>
<li class="">优点：零依赖、注解驱动、参数绑定与转换内聚、代理/日志可插拔。</li>
<li class="">弊端说明：需自行维护类型转换矩阵；复杂泛型（如 <code>List&lt;CustomDto&gt;</code>）需自行兜底。</li>
</ul>
<blockquote>
<p><strong>结论</strong>：采用<strong>方案三</strong>——复用内置 <code>Json</code>，用一层可测的类型转换矩阵换取零依赖。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32扫描与建表流程">3.2、扫描与建表流程<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#32%E6%89%AB%E6%8F%8F%E4%B8%8E%E5%BB%BA%E8%A1%A8%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="3.2、扫描与建表流程的直接链接" title="3.2、扫描与建表流程的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">LocalToolFactory.buildLocalTools(objects)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   for each object:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       拒绝 Class（必须是实例）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       for each declaredMethod with @Tool:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">           spec = ToolSpecifications.toolSpecificationFrom(method)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">           重名 → 抛 IllegalArgumentException("Duplicated definition for tool: x")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">           executor = new LocalToolExecutor(object, method)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                       （hasLogging=true 时再包一层 LoggingToolExecutor）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">           结果放入 Map&lt;ToolSpecification, ToolExecutor&gt;</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   → 交给 ToolService 按 spec.name() 建表</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33参数绑定流水线">3.3、参数绑定流水线<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#33%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A%E6%B5%81%E6%B0%B4%E7%BA%BF" class="hash-link" aria-label="3.3、参数绑定流水线的直接链接" title="3.3、参数绑定流水线的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">参数为空? → 基本类型给默认值(0/false/'\0')，对象类型给 null</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">参数非空? → coerceArgument(值, 参数名, 目标Class, 泛型Type)</span><br></div></code></pre></div></div>
<p>参数名解析优先级：<code>@P.name()</code> → <code>parameter.getName()</code>。<br>
<strong>代理兼容</strong>：AOP 代理方法会丢失参数名，故构造器支持 <code>(object, originalMethod, methodToInvoke)</code>——用原始方法取参数名，用代理方法实际调用。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="34类型转换矩阵核心">3.4、类型转换矩阵（核心）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#34%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E7%9F%A9%E9%98%B5%E6%A0%B8%E5%BF%83" class="hash-link" aria-label="3.4、类型转换矩阵（核心）的直接链接" title="3.4、类型转换矩阵（核心）的直接链接" translate="no">​</a></h2>
<p><code>coerceArgument</code> 按目标类型逐一处理，越界 / 非法值直接报错：</p>
<table><thead><tr><th>目标类型</th><th>转换策略</th></tr></thead><tbody><tr><td><code>String</code></td><td><code>argument.toString()</code></td></tr><tr><td>枚举</td><td><code>Enum.valueOf</code>，失败再试 <code>toUpperCase()</code></td></tr><tr><td><code>Boolean</code> / <code>boolean</code></td><td>仅接受 <code>Boolean</code>，否则报错</td></tr><tr><td><code>Double</code> / <code>Float</code></td><td>字符串或数字 → double，Float 做边界检查</td></tr><tr><td><code>BigDecimal</code></td><td><code>BigDecimal.valueOf(double)</code></td></tr><tr><td><code>Integer</code> / <code>Long</code> / <code>Short</code> / <code>Byte</code></td><td>走 <code>getBoundedLongValue</code>（非小数 + 边界校验）</td></tr><tr><td><code>BigInteger</code></td><td>非小数 double → <code>BigDecimal.toBigInteger()</code></td></tr><tr><td><code>UUID</code></td><td><code>UUID.fromString</code></td></tr><tr><td><code>Collection</code> / <code>Map</code></td><td>交给 <code>LocalToolArgumentConverter</code> 解析 JSON</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：所有数值转换都走 <strong>double 中间态 + 边界检查</strong>，避免静默溢出；非整数赋给整型会明确报错。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="35复杂参数与双重编码容错">3.5、复杂参数与"双重编码"容错<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#35%E5%A4%8D%E6%9D%82%E5%8F%82%E6%95%B0%E4%B8%8E%E5%8F%8C%E9%87%8D%E7%BC%96%E7%A0%81%E5%AE%B9%E9%94%99" class="hash-link" aria-label="3.5、复杂参数与&quot;双重编码&quot;容错的直接链接" title="3.5、复杂参数与&quot;双重编码&quot;容错的直接链接" translate="no">​</a></h2>
<p>模型有时会把数组 / 对象<strong>再编码成字符串</strong>传进来（例如 <code>"[1,2,3]"</code> 或 <code>"\"{...}\""</code>）。<code>LocalToolArgumentConverter</code> 的策略：</p>
<ol>
<li class="">参数本就是对象 → 原样返回；</li>
<li class="">是字符串 → <code>Json.parse</code>，类型匹配则返回；</li>
<li class="">不匹配 → <code>cleanJsonString</code>（去外层引号、还原转义）后再试；</li>
<li class="">仍失败 → 抛 <code>IllegalArgumentException</code>。</li>
</ol>
<p><code>LocalToolExecutionRequestUtil.argumentsAsMap</code> 还额外容忍：<strong>尾逗号</strong>、<strong>整体被引号包裹的 JSON</strong>。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="36日志装饰">3.6、日志装饰<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#36%E6%97%A5%E5%BF%97%E8%A3%85%E9%A5%B0" class="hash-link" aria-label="3.6、日志装饰的直接链接" title="3.6、日志装饰的直接链接" translate="no">​</a></h2>
<p><code>LoggingToolExecutor</code> 是 <code>ToolExecutor</code> 的装饰器，在执行前后用 <code>java.util.logging</code> 打印工具名、入参、出参与耗时。<code>buildLocalToolsWithLogging(objects, true)</code> 即启用。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四实战代码">四、实战代码<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E5%9B%9B%E5%AE%9E%E6%88%98%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="四、实战代码的直接链接" title="四、实战代码的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41定义工具">4.1、定义工具<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#41%E5%AE%9A%E4%B9%89%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="4.1、定义工具的直接链接" title="4.1、定义工具的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">CrmTools</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"queryCustomer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"按客户名查询客户信息"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">queryCustomer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"客户名称"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"limit"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"返回条数"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> crmService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42注册到-toolservice">4.2、注册到 ToolService<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#42%E6%B3%A8%E5%86%8C%E5%88%B0-toolservice" class="hash-link" aria-label="4.2、注册到 ToolService的直接链接" title="4.2、注册到 ToolService的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">LocalToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildLocalToolsWithLogging</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singletonList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">CrmTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ReActAgent</span><span class="token plain"> agent </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">ReActAgent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 1) 注册后模型可见的工具（tools[] 摘要）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">queryCustomer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 按客户名查询客户信息</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  parameters: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> name: string </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">required</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">, limit: integer </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">required</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 2) 打开 FINE 日志后，一次工具执行（LoggingToolExecutor 装饰）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"> 工具执行请求 </span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">工具名称: queryCustomer</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">参数: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"长路"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"limit"</span><span class="token plain">:5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">记忆ID: session-1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"> 工具执行响应 </span><span class="token operator">==</span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">结果: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"长路"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"level"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"VIP"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">耗时: </span><span class="token number">3</span><span class="token plain"> 毫秒</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">==</span><span class="token operator">=</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="43一次模型调用如何被闭环">4.3、一次模型调用如何被闭环<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#43%E4%B8%80%E6%AC%A1%E6%A8%A1%E5%9E%8B%E8%B0%83%E7%94%A8%E5%A6%82%E4%BD%95%E8%A2%AB%E9%97%AD%E7%8E%AF" class="hash-link" aria-label="4.3、一次模型调用如何被闭环的直接链接" title="4.3、一次模型调用如何被闭环的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>对应单测</strong>：<code>LocalToolLoopTest#shouldCloseTheCrmLoopThroughToolService</code>——离线完整走通"扫描 → 注册 → 命中 → 绑定 → 反射调用 → 回灌"。</p>
</blockquote>
<p>以 4.1/4.2 的 <code>queryCustomer</code> 为例，逐环节拆解：</p>
<p><strong>① 扫描与注册（build 期）</strong></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">ToolExecutor</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> tools </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">LocalToolFactory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">buildLocalTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Collections</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">singletonList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">CrmTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// toolExecutors["queryCustomer"] = LocalToolExecutor(crmTools, method)</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>ToolSpecifications.toolSpecificationFrom(method)</code> 生成 <code>ToolSpecification{ name=queryCustomer, parameters={ name:string(required), limit:integer(required) } }</code>；</li>
<li class=""><code>LocalToolExecutor</code> 绑定<strong>对象实例 + 方法</strong>（AOP 代理时：用原方法取参数名、用代理方法实际调用）。</li>
</ul>
<p><strong>② 模型侧 function call</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"queryCustomer"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"name\":\"长路\",\"limit\":5}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><strong>③ 归一化为请求</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionRequest{ id="call_1", name="queryCustomer", arguments="{\"name\":\"长路\",\"limit\":5}" }</span><br></div></code></pre></div></div>
<p><strong>④ ToolService 定位执行器</strong></p>
<p><code>toolExecutors.get("queryCustomer")</code> → 命中 <code>LocalToolExecutor</code>。</p>
<p><strong>⑤ 参数绑定（prepareArguments）</strong></p>
<table><thead><tr><th>方法参数</th><th>参数名来源</th><th>取值</th><th><code>coerceArgument</code></th></tr></thead><tbody><tr><td><code>String name</code></td><td><code>@P.name() = "name"</code></td><td><code>"长路"</code></td><td>STRING → <code>"长路"</code></td></tr><tr><td><code>int limit</code></td><td><code>@P.name() = "limit"</code></td><td><code>5</code>（Json 解析为数值）</td><td>double 中间态 + 非小数 + 边界校验 → <code>(int) 5</code></td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：参数缺失时，基本类型给默认值（<code>int → 0</code>、<code>boolean → false</code>），对象类型给 <code>null</code>；参数名优先取 <code>@P.name()</code>，否则取反射参数名。</p>
</blockquote>
<p><strong>⑥ 反射调用与返回规范化</strong></p>
<p><code>methodToInvoke.invoke(crmTools, ["长路", 5])</code>，返回值按约定处理：<code>String</code> 原样、<code>void</code> → <code>"Success"</code>、其它 → <code>Json.stringify</code>。</p>
<p><strong>⑦ 回灌模型</strong></p>
<p>工具返回值作为结果回灌 → 模型据此回答。</p>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  查一下客户</span><span class="token string" style="color:rgb(255, 121, 198)">"长路"</span><span class="token plain">的信息</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">think</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 调用 queryCustomer</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">act</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">   queryCustomer </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"长路"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"limit"</span><span class="token plain">:5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">reflect</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> CrmTools.queryCustomer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"长路"</span><span class="token plain">, </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> → customer:长路,limit:5</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">think</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 已查询到客户长路。</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">final</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> 已查询到客户长路。</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五验证测试">五、验证测试<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E4%BA%94%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="五、验证测试的直接链接" title="五、验证测试的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51单元测试概览">5.1、单元测试概览<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#51%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%A6%82%E8%A7%88" class="hash-link" aria-label="5.1、单元测试概览的直接链接" title="5.1、单元测试概览的直接链接" translate="no">​</a></h2>
<p><code>LocalToolFactoryTest</code> 覆盖工厂扫描、日志装饰、参数工具容错；<code>LocalToolExecutorTest</code> 覆盖参数绑定与类型转换矩阵，全部离线执行。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52测试清单当前实现全部通过">5.2、测试清单（当前实现，全部通过）<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#52%E6%B5%8B%E8%AF%95%E6%B8%85%E5%8D%95%E5%BD%93%E5%89%8D%E5%AE%9E%E7%8E%B0%E5%85%A8%E9%83%A8%E9%80%9A%E8%BF%87" class="hash-link" aria-label="5.2、测试清单（当前实现，全部通过）的直接链接" title="5.2、测试清单（当前实现，全部通过）的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>用例</th><th>覆盖点</th></tr></thead><tbody><tr><td><code>shouldBuildToolsFromAnnotatedObject</code></td><td>扫描 <code>@Tool</code> 构建映射</td></tr><tr><td><code>shouldDecorateExecutorWithLogging</code></td><td>日志装饰器</td></tr><tr><td><code>shouldReturnEmptyMapForNullOrEmptyInput</code></td><td>空输入边界</td></tr><tr><td><code>shouldBindStringArgumentUsingPAnnotation</code></td><td><code>@P</code> 参数名绑定</td></tr><tr><td><code>shouldCoerceNumericArguments</code></td><td>数值类型转换</td></tr><tr><td><code>shouldUsePrimitiveDefaultWhenArgumentMissing</code></td><td>基本类型默认值</td></tr><tr><td><code>shouldCoerceEnumNameIgnoringCase</code></td><td>枚举忽略大小写</td></tr><tr><td><code>shouldConvertJsonStringIntoListCollection</code></td><td>字符串 → List</td></tr><tr><td><code>shouldPassThroughNativeList</code></td><td>原生 List 透传</td></tr><tr><td><code>shouldConvertJsonStringIntoMap</code></td><td>字符串 → Map</td></tr><tr><td><code>shouldReturnSuccessForVoidMethod</code></td><td>void → <code>"Success"</code></td></tr><tr><td><code>shouldSerializeNonStringReturnValueAsJson</code></td><td>非 String 返回 → JSON</td></tr><tr><td><code>shouldReturnThrownMessageByDefault</code></td><td>默认异常降级文本</td></tr><tr><td><code>shouldThrowWrappedExecutionExceptionWhenPropagating</code></td><td>传播异常开关</td></tr><tr><td><code>shouldWrapArgumentsExceptionWhenConfigured</code></td><td>参数异常包装开关</td></tr><tr><td><code>shouldTolerateTrailingCommaWhenParsingArguments</code></td><td>尾逗号容错</td></tr><tr><td><code>shouldConvertDoubleEncodedJsonString</code></td><td>双重编码 JSON 容错</td></tr><tr><td><code>LocalToolLoopTest#shouldCloseTheCrmLoopThroughToolService</code></td><td><strong>端到端闭环</strong>：<code>@Tool</code> 扫描 → ToolService → function call → 参数绑定 → 反射调用 → 回灌（对齐 4.3）</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="53边界与风险">5.3、边界与风险<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#53%E8%BE%B9%E7%95%8C%E4%B8%8E%E9%A3%8E%E9%99%A9" class="hash-link" aria-label="5.3、边界与风险的直接链接" title="5.3、边界与风险的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><strong>参数名依赖</strong>：未开 <code>-parameters</code> 且未用 <code>@P</code> 时，模型看到 <code>arg0</code>，务必显式命名。</li>
<li class=""><strong>required 固定为 true</strong>：当前所有参数都必填，可选参数需上层兜底或默认值。</li>
<li class=""><strong>泛型擦除</strong>：<code>List&lt;CustomDto&gt;</code> 只能解析为 <code>Map</code> / <code>List</code> 结构，无法自动实例化为具体类型。</li>
<li class=""><strong>布尔严格</strong>：<code>Boolean</code> 字段只接受布尔值，字符串 <code>"true"</code> 不做隐式转换。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六总结与展望">六、总结与展望<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E5%85%AD%E6%80%BB%E7%BB%93%E4%B8%8E%E5%B1%95%E6%9C%9B" class="hash-link" aria-label="六、总结与展望的直接链接" title="六、总结与展望的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>本质</strong>：<code>@Tool</code> 注解 + 反射 + 类型转换，把普通 Java 方法变成模型可调用的工具。</li>
<li class=""><strong>核心</strong>：<code>ToolSpecifications</code> 生成 Schema；<code>LocalToolExecutor</code> 绑定参数并反射调用。</li>
<li class=""><strong>零依赖</strong>：复用内置 <code>Json</code>，与 Java 8 / 零第三方库约束一致。</li>
<li class=""><strong>工程性</strong>：异常策略可配、日志可装饰、代理可兼容。</li>
<li class=""><strong>展望</strong>：可选参数（required 可控）、泛型 DTO 反序列化、SpEL / 表达式默认值。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/local/Java%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE01%E3%80%81AgentForge%20Local%E5%B7%A5%E5%85%B7%E6%A8%A1%E5%9D%97%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://docs.oracle.com/javase/tutorial/reflect/" target="_blank" rel="noopener noreferrer" class="">Java 反射教程（Oracle）</a></p>
<p>[2]. <a href="https://json-schema.org/" target="_blank" rel="noopener noreferrer" class="">JSON Schema 官方站点</a></p>
<p>[3]. <a href="https://docs.oracle.com/javase/tutorial/java/annotations/" target="_blank" rel="noopener noreferrer" class="">Java 注解教程（Oracle）</a></p>
<p>[4]. 相关内部文档：[HTTP 工具模块核心设计原理](/blog/02、通用ReActagent层/functioncall核心扩展业务tool/http/适配HTTP协议转换FunctionCall协议01、AgentForge HTTP工具模块核心设计原理)、<a class="" href="https://agentforge-tech.github.io/AgentForge/blog/02%E3%80%81%E9%80%9A%E7%94%A8ReActagent%E5%B1%82/functioncall%E6%A0%B8%E5%BF%83%E6%89%A9%E5%B1%95%E4%B8%9A%E5%8A%A1tool/mcp/MCP%E5%8D%8F%E8%AE%AE%E8%BD%AC%E6%8D%A2FunctionCall%E5%8D%8F%E8%AE%AE02%E3%80%81MCP%E6%A1%86%E6%9E%B6%E8%B0%83%E7%A0%94%E4%B8%8EAgentForge%E6%8E%A5%E5%85%A5%E5%AE%9E%E7%8E%B0">MCP 框架调研与 AgentForge 接入实现</a></p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>通用ReActagent层</category>
            <category>Function Calling</category>
            <category>Local</category>
        </item>
        <item>
            <title><![CDATA[Anthropic协议02、AgentForge 适配Anthropic接入核心实践]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议02、AgentForge 适配Anthropic接入核心实践</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议02、AgentForge 适配Anthropic接入核心实践</guid>
            <pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[更新日期：2026-10-05]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>更新日期：2026-10-05<br>
<!-- -->适用模块：<code>agentforge-model-anthropic</code><br>
<!-- -->当前实现：<code>AnthropicChatModel</code>（Blocking）、<code>AnthropicStreamingChatModel</code>（SSE）、<code>AnthropicProtocol</code>（共享 wire mapping）<br>
<!-- -->Wire API：Anthropic Messages API<br>
<!-- -->维护者：changlu</p>
</blockquote>
<!-- -->
<p>一句话结论：Anthropic Provider 把 AgentForge 统一的 <code>ChatRequest</code> / <code>ChatResponse</code> 与 <strong>Messages API</strong> 双向映射，并处理了 Anthropic 特有的"system 顶层字段 + content block"两大差异；协议本身见《Anthropic协议01、Anthropic底层协议快速理解》。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动一套-core适配-claude-的-block-协议">1.1、场景驱动：一套 Core，适配 Claude 的 block 协议<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E4%B8%80%E5%A5%97-core%E9%80%82%E9%85%8D-claude-%E7%9A%84-block-%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="1.1、场景驱动：一套 Core，适配 Claude 的 block 协议的直接链接" title="1.1、场景驱动：一套 Core，适配 Claude 的 block 协议的直接链接" translate="no">​</a></h2>
<blockquote>
<p>AgentForge 上层只依赖 <code>ChatMessage</code> / <code>ChatRequest</code> / <code>ChatResponse</code>。接入 Claude 时，不能让"system 顶层字段""content block""tool_use/tool_result"这些 wire 细节泄漏到 Agent Runtime。</p>
</blockquote>
<p>因此 Anthropic Provider 的职责是<strong>协议翻译层</strong>，并让 Blocking 与 Streaming 共用同一套映射逻辑。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导block-协议如何落到统一消息">1.2、问题引导：block 协议如何落到统一消息？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BCblock-%E5%8D%8F%E8%AE%AE%E5%A6%82%E4%BD%95%E8%90%BD%E5%88%B0%E7%BB%9F%E4%B8%80%E6%B6%88%E6%81%AF" class="hash-link" aria-label="1.2、问题引导：block 协议如何落到统一消息？的直接链接" title="1.2、问题引导：block 协议如何落到统一消息？的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>问题</strong>：<code>SystemMessage</code> 怎么落到顶层 <code>system</code>？<code>AiMessage</code> 的 text 与工具调用怎么落到 <code>content[]</code>？<code>ToolExecutionResultMessage</code> 怎么落到 <code>tool_result</code>？流式 <code>input_json_delta</code> 怎么聚合成完整工具参数？</p>
</blockquote>
<p>本文逐项给出映射与实现位置。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13builder-参数">1.3、Builder 参数<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#13builder-%E5%8F%82%E6%95%B0" class="hash-link" aria-label="1.3、Builder 参数的直接链接" title="1.3、Builder 参数的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>Builder 参数</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>baseUrl</code></td><td><code>https://api.anthropic.com</code></td><td>API 根地址</td></tr><tr><td><code>apiKey</code></td><td><code>null</code></td><td>必填，写入 <code>x-api-key</code></td></tr><tr><td><code>anthropicVersion</code></td><td><code>2023-06-01</code></td><td>写入 <code>anthropic-version</code> Header</td></tr><tr><td><code>modelName</code></td><td><code>null</code></td><td>请求前必须有值</td></tr><tr><td><code>temperature</code></td><td><code>null</code></td><td>非空映射 <code>temperature</code>（兼容性见 5.4）</td></tr><tr><td><code>maxTokens</code></td><td><code>1024</code></td><td>映射必填的 <code>max_tokens</code></td></tr><tr><td><code>topP</code></td><td><code>null</code></td><td>非空映射 <code>top_p</code>（兼容性见 5.4）</td></tr><tr><td><code>stopSequences</code></td><td><code>null</code></td><td>映射 <code>stop_sequences</code></td></tr><tr><td><code>customParameter</code></td><td>空</td><td>Provider 顶层扩展字段</td></tr><tr><td><code>customHeader</code></td><td>空</td><td>自定义 Header</td></tr><tr><td><code>httpTransport</code></td><td><code>JdkHttpTransport</code></td><td>HTTP SPI</td></tr><tr><td><code>connectTimeoutMillis</code></td><td><code>10000</code></td><td>连接超时</td></tr><tr><td><code>readTimeoutMillis</code></td><td><code>60000</code></td><td>读取超时</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：<code>max_tokens</code> 是 Anthropic 的必填字段，AgentForge 默认 1024，保证最简单调用可直接执行。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念">二、核心概念<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="二、核心概念的直接链接" title="二、核心概念的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatRequest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token class-name">AiMessage</span><span class="token plain"> aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token class-name">TokenUsage</span><span class="token plain"> tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token class-name">FinishReason</span><span class="token plain"> finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>Provider 负责把 Core 的 <code>ChatRequest</code> 翻译为 Anthropic wire，再把响应归一化为 <code>ChatResponse</code>。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三实现思路与映射">三、实现思路与映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF%E4%B8%8E%E6%98%A0%E5%B0%84" class="hash-link" aria-label="三、实现思路与映射的直接链接" title="三、实现思路与映射的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31http-headers">3.1、HTTP Headers<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#31http-headers" class="hash-link" aria-label="3.1、HTTP Headers的直接链接" title="3.1、HTTP Headers的直接链接" translate="no">​</a></h2>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Accept: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">x-api-key: ${ANTHROPIC_API_KEY}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">anthropic-version: 2023-06-01</span><br></div></code></pre></div></div>
<p>之后追加 <code>customHeaders</code>。</p>
<blockquote>
<p><strong>注意</strong>：不是 <code>Authorization: Bearer</code>，且必须显式发送 <code>anthropic-version</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32systemmessage-映射">3.2、SystemMessage 映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#32systemmessage-%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.2、SystemMessage 映射的直接链接" title="3.2、SystemMessage 映射的直接链接" translate="no">​</a></h2>
<p>Anthropic 的 system prompt 使用<strong>顶层 <code>system</code> 字段</strong>。AgentForge 遍历所有 <code>SystemMessage</code>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">SystemMessage A + SystemMessage B → "A\n\nB" → {"system":"A\n\nB"}</span><br></div></code></pre></div></div>
<p>拼接后的 system <strong>不再进入 <code>messages</code> 数组</strong>。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33user--ai-消息映射">3.3、User / AI 消息映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#33user--ai-%E6%B6%88%E6%81%AF%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.3、User / AI 消息映射的直接链接" title="3.3、User / AI 消息映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>AgentForge</th><th>Anthropic role</th><th>content</th></tr></thead><tbody><tr><td><code>UserMessage</code>（单文本）</td><td><code>user</code></td><td><code>message.text()</code>（字符串）</td></tr><tr><td><code>UserMessage</code>（多 <code>Content</code>）</td><td><code>user</code></td><td><code>content[]</code>，逐条 <code>TextContent</code> → <code>{"type":"text","text":...}</code></td></tr><tr><td><code>AiMessage</code>（纯文本）</td><td><code>assistant</code></td><td><code>message.text()</code>（字符串）</td></tr><tr><td><code>AiMessage</code>（含工具调用）</td><td><code>assistant</code></td><td><code>content[]</code>：可选 text 块 + <code>tool_use</code> 块</td></tr><tr><td><code>ToolExecutionResultMessage</code></td><td><code>user</code></td><td><code>content[]</code>：<code>tool_result</code> 块</td></tr><tr><td><code>SystemMessage</code></td><td>不进入 messages</td><td>聚合到顶层 <code>system</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="34工具调用映射">3.4、工具调用映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#34%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.4、工具调用映射的直接链接" title="3.4、工具调用映射的直接链接" translate="no">​</a></h2>
<p><code>AiMessage.hasToolExecutionRequests()</code> 为真时，映射为 <code>assistant</code> 的 <code>content[]</code>：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"I will check."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_use"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"input"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"city"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hangzhou"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><code>ToolExecutionResultMessage</code> 映射为 <code>user</code> 的 <code>content[]</code>：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_result"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_use_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"temperature\":22}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<table><thead><tr><th>Core 字段</th><th>Anthropic 字段</th></tr></thead><tbody><tr><td><code>ToolExecutionRequest.id</code></td><td><code>tool_use.id</code>（回填对应 <code>tool_result.tool_use_id</code>）</td></tr><tr><td><code>ToolExecutionRequest.name</code></td><td><code>tool_use.name</code></td></tr><tr><td><code>ToolExecutionRequest.arguments</code>（JSON 字符串）</td><td><code>tool_use.input</code>（<strong>解析为对象</strong>后下发）</td></tr><tr><td><code>AiMessage.text</code></td><td>可选的前置 <code>text</code> 块</td></tr><tr><td><code>ToolExecutionResultMessage.isError == true</code></td><td><code>tool_result.is_error = true</code></td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：<code>CustomMessage</code> 没有 <code>text()</code>，当前仍会抛 <code>UnsupportedOperationException</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="35参数映射">3.5、参数映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#35%E5%8F%82%E6%95%B0%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.5、参数映射的直接链接" title="3.5、参数映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>AgentForge 参数</th><th>Anthropic 字段</th><th>当前行为</th></tr></thead><tbody><tr><td><code>modelName</code></td><td><code>model</code></td><td>必填；为空本地失败</td></tr><tr><td><code>maxTokens</code></td><td><code>max_tokens</code></td><td>必填；默认 1024</td></tr><tr><td><code>temperature</code></td><td><code>temperature</code></td><td>非空发送；新模型兼容性需注意</td></tr><tr><td><code>topP</code></td><td><code>top_p</code></td><td>非空发送；新模型兼容性需注意</td></tr><tr><td><code>stopSequences</code></td><td><code>stop_sequences</code></td><td>非空发送</td></tr><tr><td><code>tools</code></td><td><code>tools[]</code></td><td><code>{"name","description","input_schema"}</code>；无参数时下发空 object schema</td></tr><tr><td><code>toolChoice</code></td><td><code>tool_choice</code></td><td><code>AUTO→{"type":"auto"}</code>、<code>NONE→{"type":"none"}</code>、<code>REQUIRED→{"type":"any"}</code>、<code>SPECIFIC→{"type":"tool","name":X}</code></td></tr><tr><td><code>SystemMessage</code></td><td><code>system</code></td><td>多条以双换行拼接</td></tr><tr><td><code>customParameters</code></td><td>顶层透传</td><td>标准字段最终覆盖同名 custom 值</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="36响应提取规则">3.6、响应提取规则<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#36%E5%93%8D%E5%BA%94%E6%8F%90%E5%8F%96%E8%A7%84%E5%88%99" class="hash-link" aria-label="3.6、响应提取规则的直接链接" title="3.6、响应提取规则的直接链接" translate="no">​</a></h2>
<p>遍历 <code>content</code> 数组：</p>
<ul>
<li class="">只提取 <code>type == "text"</code> 的块，按顺序拼接为 <code>AiMessage.text()</code>；</li>
<li class="">提取 <code>type == "tool_use"</code> 的块，映射为 <code>ToolExecutionRequest</code>：</li>
</ul>
<table><thead><tr><th>Anthropic <code>tool_use</code></th><th>AgentForge</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>id</code></td></tr><tr><td><code>name</code></td><td><code>name</code></td></tr><tr><td><code>input</code>（对象）</td><td><code>arguments</code>（<code>Json.stringify(input)</code>）</td></tr></tbody></table>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">content = [text("I will check."), tool_use(...)]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↓</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.text()                     = "I will check."</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.hasToolExecutionRequests() = true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.toolExecutionRequests()    = [ToolExecutionRequest(toolu_1, get_weather, {"city":"hangzhou"})]</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：只有 <code>tool_use</code> 没有 text 块时，<code>AiMessage.text()</code> 为 <code>null</code>；<code>thinking</code> / <code>image</code> / <code>document</code> / server tool block 当前不进入 <code>AiMessage</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="37chatresponse-映射">3.7、ChatResponse 映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#37chatresponse-%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.7、ChatResponse 映射的直接链接" title="3.7、ChatResponse 映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>Anthropic 字段</th><th>AgentForge 字段</th></tr></thead><tbody><tr><td><code>content[*].text</code></td><td><code>ChatResponse.aiMessage().text()</code></td></tr><tr><td><code>content[*].tool_use</code></td><td><code>ChatResponse.aiMessage().toolExecutionRequests()</code></td></tr><tr><td><code>usage.input_tokens</code></td><td><code>TokenUsage.inputTokens()</code></td></tr><tr><td><code>usage.output_tokens</code></td><td><code>TokenUsage.outputTokens()</code></td></tr><tr><td><code>input + output</code></td><td><code>TokenUsage.totalTokens()</code></td></tr><tr><td><code>stop_reason</code></td><td><code>ChatResponse.finishReason()</code></td></tr><tr><td><code>id</code> / <code>model</code> / <code>type</code></td><td><code>metadata["id"] / ["model"] / ["type"]</code></td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="38stop_reason-映射">3.8、stop_reason 映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#38stop_reason-%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.8、stop_reason 映射的直接链接" title="3.8、stop_reason 映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>Anthropic</th><th>AgentForge <code>FinishReason</code></th></tr></thead><tbody><tr><td><code>end_turn</code></td><td><code>STOP</code></td></tr><tr><td><code>stop_sequence</code></td><td><code>STOP</code></td></tr><tr><td><code>max_tokens</code></td><td><code>LENGTH</code></td></tr><tr><td><code>tool_use</code></td><td><code>TOOL_EXECUTION</code></td></tr><tr><td>其他非空</td><td><code>OTHER</code></td></tr><tr><td><code>null</code></td><td><code>null</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="39流式实现">3.9、流式实现<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#39%E6%B5%81%E5%BC%8F%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="3.9、流式实现的直接链接" title="3.9、流式实现的直接链接" translate="no">​</a></h2>
<p><code>AnthropicStreamingChatModel</code> 使用同一 Endpoint，强制 <code>stream=true</code>，<code>Accept: text/event-stream</code>；请求体映射与 Blocking 完全一致，统一由 <code>AnthropicProtocol</code> 生成。</p>
<p><code>JdkHttpTransport</code> 只按行回调，不解析 SSE 语义；<code>AnthropicStreamState</code> 自行按 <code>event:</code> / <code>data:</code> 累积一帧，遇空行（或单行 <code>data:</code> 帧）时 dispatch：</p>
<table><thead><tr><th>事件</th><th>AgentForge 行为</th></tr></thead><tbody><tr><td><code>message_start</code></td><td>记录 <code>metadata["id"]/["model"]</code>，读 <code>usage.input_tokens</code></td></tr><tr><td><code>content_block_start</code></td><td><code>tool_use</code> 时按 <code>index</code> 建累加器，记 <code>id</code> / <code>name</code> / 初始 <code>input</code></td></tr><tr><td><code>content_block_delta</code></td><td><code>text_delta</code> 立即 <code>onPartialResponse(...)</code>；<code>input_json_delta</code> 追加到同 <code>index</code> 工具参数</td></tr><tr><td><code>content_block_stop</code></td><td>不处理，等整块结束</td></tr><tr><td><code>message_delta</code></td><td>读 <code>delta.stop_reason</code> 与 <code>usage.output_tokens</code></td></tr><tr><td><code>message_stop</code></td><td>由 EOF / 完成回调触发最终响应</td></tr><tr><td><code>error</code></td><td>包装为 <code>ModelException</code> 并 <code>onError(...)</code></td></tr></tbody></table>
<p>工具调用聚合规则：</p>
<ul>
<li class="">文本块与工具块可交错出现，互不影响；</li>
<li class=""><code>arguments</code> 按到达顺序拼接原始 JSON 文本，不重新格式化；</li>
<li class="">若只有 <code>content_block_start</code> 没有 <code>input_json_delta</code>（<code>input</code> 直接给对象），使用 start 帧的 <code>input</code>；</li>
<li class="">无任何 <code>partial_json</code> 时，<code>arguments</code> 归一化为 <code>{}</code>；</li>
<li class="">与 LangChain4j 一致：流式中<strong>不</strong>暴露半成品工具调用，只在最终 <code>ChatResponse</code> 给出完整结果。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：<code>temperature</code> / <code>top_p</code> 已被 Anthropic 官方标记为对较新模型 deprecated/受限；推荐默认不显式设置，按目标模型能力决定是否传入。Provider 后续可加入 model capability 校验，提前在客户端阻止无效参数。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四实战代码">四、实战代码<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E5%9B%9B%E5%AE%9E%E6%88%98%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="四、实战代码的直接链接" title="四、实战代码的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41非流式">4.1、非流式<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#41%E9%9D%9E%E6%B5%81%E5%BC%8F" class="hash-link" aria-label="4.1、非流式的直接链接" title="4.1、非流式的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">AnthropicChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">AnthropicChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">baseUrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://api.anthropic.com"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ANTHROPIC_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"your-claude-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1024</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatRequest</span><span class="token plain"> request </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">SystemMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"You are a concise Java assistant."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Explain volatile."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">$ </span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> https://api.anthropic.com/v1/messages </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x-api-key: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ANTHROPIC_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"anthropic-version: 2023-06-01"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{"model":"your-claude-model","max_tokens":1024,"system":"You are a concise Java assistant.","messages":[{"role":"user","content":"Explain volatile."}]}'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"id"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"msg_xxx"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"type"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"role"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"content"</span><span class="token plain">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"type"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"volatile provides visibility guarantees..."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stop_reason"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"end_turn"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"usage"</span><span class="token plain">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"input_tokens"</span><span class="token plain">:24,</span><span class="token string" style="color:rgb(255, 121, 198)">"output_tokens"</span><span class="token plain">:40</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42工具调用闭环wire-示例">4.2、工具调用闭环（wire 示例）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#42%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E9%97%AD%E7%8E%AFwire-%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="4.2、工具调用闭环（wire 示例）的直接链接" title="4.2、工具调用闭环（wire 示例）的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// assistant 发起</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"role"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"content"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"text"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"I will check."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"tool_use"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"id"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"name"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"input"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"city"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"hangzhou"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 工具结果回填</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"role"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"content"</span><span class="token operator">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">"type"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"tool_result"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"tool_use_id"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token property">"content"</span><span class="token operator">:</span><span class="token string" style="color:rgb(255, 121, 198)">"{\"temperature\":22}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五错误处理限制与演进">五、错误处理、限制与演进<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%BA%94%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86%E9%99%90%E5%88%B6%E4%B8%8E%E6%BC%94%E8%BF%9B" class="hash-link" aria-label="五、错误处理、限制与演进的直接链接" title="五、错误处理、限制与演进的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51本地参数校验">5.1、本地参数校验<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#51%E6%9C%AC%E5%9C%B0%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C" class="hash-link" aria-label="5.1、本地参数校验的直接链接" title="5.1、本地参数校验的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">modelName 必须非空</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">apiKey 必须非空</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">messages 中至少有一条非 system 的 user/assistant 类消息</span><br></div></code></pre></div></div>
<p>只有 <code>SystemMessage</code> 时抛：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">IllegalArgumentException: Anthropic request requires at least one user/assistant message</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52http-错误">5.2、HTTP 错误<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#52http-%E9%94%99%E8%AF%AF" class="hash-link" aria-label="5.2、HTTP 错误的直接链接" title="5.2、HTTP 错误的直接链接" translate="no">​</a></h2>
<p>非 2xx → <code>new ModelException("Anthropic request failed with HTTP " + statusCode, statusCode, responseBody)</code>；IOException → <code>ModelException("Anthropic request failed", cause)</code>。上层可统一读取 <code>exception.statusCode()</code> / <code>exception.responseBody()</code>。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="53当前未实现能力">5.3、当前未实现能力<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#53%E5%BD%93%E5%89%8D%E6%9C%AA%E5%AE%9E%E7%8E%B0%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="5.3、当前未实现能力的直接链接" title="5.3、当前未实现能力的直接链接" translate="no">​</a></h2>
<p>已落地：Streaming/SSE、<code>tool_use</code> 强类型响应（含流式 <code>input_json_delta</code> 聚合）、<code>tool_result</code> 强类型请求、<code>tools/tool_choice</code> 请求映射、<code>UserMessage</code> 多 <code>Content</code> → <code>content[]</code>。</p>
<p>尚未完整支持：thinking / redacted thinking、image / document content blocks、Prompt Caching 强类型配置与 usage、Structured Output、server tools、extended usage 字段（cache token 等）、<code>stop_sequence</code> 写入 metadata、provider refusal / stop details 标准化。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="54类结构与演进">5.4、类结构与演进<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#54%E7%B1%BB%E7%BB%93%E6%9E%84%E4%B8%8E%E6%BC%94%E8%BF%9B" class="hash-link" aria-label="5.4、类结构与演进的直接链接" title="5.4、类结构与演进的直接链接" translate="no">​</a></h2>
<p>Blocking 与 Streaming 共用同一套 wire mapping：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">AnthropicProtocol（package-private）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── collectSystemMessages / serializeMessages</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── serializeTools / toolChoice</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── extractText / extractToolUses</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── AnthropicChatModel          -&gt; blocking Messages API</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └── AnthropicStreamingChatModel -&gt; Messages API SSE</span><br></div></code></pre></div></div>
<p>后续引入 Thinking / Multimodal 时，建议在 Core 扩展统一 Content 层级（<code>TextContent</code> 已存在）：<code>ImageContent</code> / <code>AudioContent</code> / <code>VideoContent</code> / <code>PdfFileContent</code>，使 Anthropic block protocol 与 OpenAI 多模态汇聚到同一上层模型。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六总结">六、总结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E5%85%AD%E6%80%BB%E7%BB%93" class="hash-link" aria-label="六、总结的直接链接" title="六、总结的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>两大差异</strong>：<code>system</code> 顶层字段、<code>content</code> block 数组——都被 Provider 层吸收，Core 不变。</li>
<li class=""><strong>工具调用</strong>：<code>tool_use</code>（<code>input</code> 对象）→ <code>tool_result</code>（<code>user</code> 角色回填），强类型双向映射。</li>
<li class=""><strong>流式</strong>：<code>AnthropicStreamState</code> 自行解析带事件名的 SSE，<code>input_json_delta</code> 按 block <code>index</code> 聚合。</li>
<li class=""><strong>必填约束</strong>：<code>max_tokens</code> 默认 1024，保证直接可调用。</li>
<li class=""><strong>风险提示</strong>：新 Claude 模型对 <code>temperature</code> / <code>top_p</code> 收紧，默认不设置更安全。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DAnthropic%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://platform.claude.com/docs/en/api/messages/create" target="_blank" rel="noopener noreferrer" class="">Anthropic Messages API（英文）</a></p>
<p>[2]. <a href="https://platform.claude.com/docs/zh-CN/api/messages/create" target="_blank" rel="noopener noreferrer" class="">Anthropic Messages API（中文）</a></p>
<p>[3]. 相关内部文档：<a class="" href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3">Anthropic 底层协议快速理解</a>、[ChatModel 核心协议层设计](/blog/01、model模型协议层/chatmodel/统一ChatModel协议层01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现)</p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>模型协议层</category>
            <category>Anthropic</category>
        </item>
        <item>
            <title><![CDATA[Anthropic协议01、Anthropic底层协议快速理解]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议01、Anthropic底层协议快速理解</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议01、Anthropic底层协议快速理解</guid>
            <pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[更新日期：2026-10-05]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>更新日期：2026-10-05<br>
<!-- -->适用场景：理解 <code>agentforge-model-anthropic</code> 所对接的 Anthropic Messages API<br>
<!-- -->维护者：changlu</p>
</blockquote>
<!-- -->
<p>一句话结论：Anthropic 对接的是 <strong>Messages API</strong>（<code>POST {baseUrl}/v1/messages</code>），它有两大差异点——<strong>system 是顶层字段</strong>、<strong>content 是 block 数组</strong>。本文只讲"协议本身长什么样"；AgentForge 的映射见《Anthropic协议02、AgentForge Anthropic接入核心实践》。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动claude-的-tool_use-与-content-block">1.1、场景驱动：Claude 的 tool_use 与 content block<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8claude-%E7%9A%84-tool_use-%E4%B8%8E-content-block" class="hash-link" aria-label="1.1、场景驱动：Claude 的 tool_use 与 content block的直接链接" title="1.1、场景驱动：Claude 的 tool_use 与 content block的直接链接" translate="no">​</a></h2>
<blockquote>
<p>在为 AgentForge 接入 Claude 时，我们发现 Anthropic 与 OpenAI 的 wire 差异不小：鉴权头不同、system prompt 位置不同、content 不是字符串而是 block 数组，工具调用用 <code>tool_use</code> / <code>tool_result</code> 表达。</p>
</blockquote>
<p>如果不先理解这些 wire 细节，就无法把响应正确归一化为 AgentForge 的统一消息。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导claude-的一次调用长什么样">1.2、问题引导：Claude 的一次调用长什么样？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BCclaude-%E7%9A%84%E4%B8%80%E6%AC%A1%E8%B0%83%E7%94%A8%E9%95%BF%E4%BB%80%E4%B9%88%E6%A0%B7" class="hash-link" aria-label="1.2、问题引导：Claude 的一次调用长什么样？的直接链接" title="1.2、问题引导：Claude 的一次调用长什么样？的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>问题</strong>：一次 Messages API 调用的请求头、<code>system</code>、<code>messages</code>、<code>content</code> block、流式 SSE 事件分别是什么？工具调用如何双向表达？</p>
</blockquote>
<p>本文按"请求 → 响应 → 流式 → 工具调用"的顺序讲清。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13协议定位">1.3、协议定位<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#13%E5%8D%8F%E8%AE%AE%E5%AE%9A%E4%BD%8D" class="hash-link" aria-label="1.3、协议定位的直接链接" title="1.3、协议定位的直接链接" translate="no">​</a></h2>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST {baseUrl}/v1/messages</span><br></div></code></pre></div></div>
<p>默认：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">baseUrl = https://api.anthropic.com</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">anthropic-version = 2023-06-01</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">→ 实际地址 https://api.anthropic.com/v1/messages</span><br></div></code></pre></div></div>
<p>Messages API 是<strong>无状态</strong>消息协议：客户端提交当前会话历史，模型生成下一条 assistant message。</p>
<p>官方参考：</p>
<ul>
<li class=""><a href="https://platform.claude.com/docs/en/api/messages/create" target="_blank" rel="noopener noreferrer" class="">https://platform.claude.com/docs/en/api/messages/create</a></li>
<li class=""><a href="https://platform.claude.com/docs/zh-CN/api/messages/create" target="_blank" rel="noopener noreferrer" class="">https://platform.claude.com/docs/zh-CN/api/messages/create</a></li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二请求协议">二、请求协议<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%BA%8C%E8%AF%B7%E6%B1%82%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="二、请求协议的直接链接" title="二、请求协议的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21headers">2.1、Headers<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#21headers" class="hash-link" aria-label="2.1、Headers的直接链接" title="2.1、Headers的直接链接" translate="no">​</a></h2>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Accept: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">x-api-key: ${ANTHROPIC_API_KEY}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">anthropic-version: 2023-06-01</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：鉴权不是 <code>Authorization: Bearer</code>，而是 <code>x-api-key</code>；并且必须显式发送 API version Header。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22system-是顶层字段关键差异">2.2、system 是顶层字段（关键差异）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#22system-%E6%98%AF%E9%A1%B6%E5%B1%82%E5%AD%97%E6%AE%B5%E5%85%B3%E9%94%AE%E5%B7%AE%E5%BC%82" class="hash-link" aria-label="2.2、system 是顶层字段（关键差异）的直接链接" title="2.2、system 是顶层字段（关键差异）的直接链接" translate="no">​</a></h2>
<p>Anthropic 的 system prompt <strong>不使用 <code>system</code> role message</strong>，而是顶层 <code>system</code> 字段：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"your-claude-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"max_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1024</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"system"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"You are a concise Java assistant."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"messages"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Explain volatile."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：多条 system 提示需要由客户端拼接（AgentForge 以双换行 <code>\n\n</code> 拼接）。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="23messagesrole-与-content">2.3、messages：role 与 content<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#23messagesrole-%E4%B8%8E-content" class="hash-link" aria-label="2.3、messages：role 与 content的直接链接" title="2.3、messages：role 与 content的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>role</th><th>content</th><th>说明</th></tr></thead><tbody><tr><td><code>user</code></td><td>字符串或块数组</td><td>用户输入；工具结果也用 <code>user</code> 承载</td></tr><tr><td><code>assistant</code></td><td>字符串或块数组</td><td>模型回复；工具调用用 <code>tool_use</code> 块</td></tr></tbody></table>
<p>对话历史通常是交替的 <code>user</code> / <code>assistant</code> turns；连续同 role 消息可能被服务端合并。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="24content-block-类型">2.4、content block 类型<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#24content-block-%E7%B1%BB%E5%9E%8B" class="hash-link" aria-label="2.4、content block 类型的直接链接" title="2.4、content block 类型的直接链接" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">text</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">tool_use</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">tool_result</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">thinking / redacted_thinking</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">image / document</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">server tool blocks</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">...</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="25请求参数">2.5、请求参数<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#25%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0" class="hash-link" aria-label="2.5、请求参数的直接链接" title="2.5、请求参数的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>字段</th><th>说明</th></tr></thead><tbody><tr><td><code>model</code></td><td>模型 ID</td></tr><tr><td><code>max_tokens</code></td><td><strong>必填</strong>，最大生成 token</td></tr><tr><td><code>temperature</code> / <code>top_p</code></td><td>采样参数（新模型已 deprecated/受限，见下）</td></tr><tr><td><code>stop_sequences</code></td><td>停止序列</td></tr><tr><td><code>tools[]</code></td><td><code>{"name","description","input_schema"}</code></td></tr><tr><td><code>tool_choice</code></td><td><code>{"type":"auto"}</code> / <code>{"type":"none"}</code> / <code>{"type":"any"}</code> / <code>{"type":"tool","name":X}</code></td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：Anthropic 官方已将 <code>temperature</code>、<code>top_p</code> 标注为面向新模型的 deprecated/受限参数；新模型可能只接受兼容值，否则返回 400。是否传入需按目标模型能力决定。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三响应协议">三、响应协议<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%B8%89%E5%93%8D%E5%BA%94%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="三、响应协议的直接链接" title="三、响应协议的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31message-结构">3.1、message 结构<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#31message-%E7%BB%93%E6%9E%84" class="hash-link" aria-label="3.1、message 结构的直接链接" title="3.1、message 结构的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"msg_xxx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"your-claude-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"volatile provides visibility guarantees..."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"end_turn"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_sequence"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"input_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">24</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"output_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">40</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>content</code> 是 <strong>block 数组</strong>，不是单一字符串；一个响应里可能有多个 text 块，也可能有 <code>tool_use</code> 块。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32stop_reason">3.2、stop_reason<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#32stop_reason" class="hash-link" aria-label="3.2、stop_reason的直接链接" title="3.2、stop_reason的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>值</th><th>含义</th></tr></thead><tbody><tr><td><code>end_turn</code></td><td>自然结束</td></tr><tr><td><code>stop_sequence</code></td><td>命中自定义停止序列</td></tr><tr><td><code>max_tokens</code></td><td>达到 max_tokens</td></tr><tr><td><code>tool_use</code></td><td>需要执行工具</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33usage">3.3、usage<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#33usage" class="hash-link" aria-label="3.3、usage的直接链接" title="3.3、usage的直接链接" translate="no">​</a></h2>
<p><code>input_tokens</code> / <code>output_tokens</code>；官方还可能返回 cache token、server tool usage 等扩展字段。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四流式协议sse">四、流式协议（SSE）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E5%9B%9B%E6%B5%81%E5%BC%8F%E5%8D%8F%E8%AE%AEsse" class="hash-link" aria-label="四、流式协议（SSE）的直接链接" title="四、流式协议（SSE）的直接链接" translate="no">​</a></h2>
<p>请求增加：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"stream"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>响应为<strong>带事件名的 SSE</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_start","message":{...}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hel"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_stop"}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41事件一览">4.1、事件一览<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#41%E4%BA%8B%E4%BB%B6%E4%B8%80%E8%A7%88" class="hash-link" aria-label="4.1、事件一览的直接链接" title="4.1、事件一览的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>事件</th><th>含义</th></tr></thead><tbody><tr><td><code>message_start</code></td><td>开始，携带 message 元信息与 <code>usage.input_tokens</code></td></tr><tr><td><code>content_block_start</code></td><td>开始一个 content block（含 <code>tool_use</code> 的 <code>id</code> / <code>name</code> / 初始 <code>input</code>）</td></tr><tr><td><code>content_block_delta</code></td><td>增量：<code>text_delta</code> 或 <code>input_json_delta</code></td></tr><tr><td><code>content_block_stop</code></td><td>块结束</td></tr><tr><td><code>message_delta</code></td><td>携带 <code>delta.stop_reason</code> 与 <code>usage.output_tokens</code></td></tr><tr><td><code>message_stop</code></td><td>消息结束</td></tr><tr><td><code>error</code></td><td>错误事件</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42工具参数的流式聚合">4.2、工具参数的流式聚合<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#42%E5%B7%A5%E5%85%B7%E5%8F%82%E6%95%B0%E7%9A%84%E6%B5%81%E5%BC%8F%E8%81%9A%E5%90%88" class="hash-link" aria-label="4.2、工具参数的流式聚合的直接链接" title="4.2、工具参数的流式聚合的直接链接" translate="no">​</a></h2>
<p><code>tool_use</code> 的参数以 <code>input_json_delta</code> 增量下发，按 content block <code>index</code> 归属：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">content_block_start (index=1, type=tool_use, id=toolu_1, name=get_weather, input={})</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">content_block_delta (index=1, input_json_delta: "{\"city\":")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">content_block_delta (index=1, input_json_delta: "\"hangzhou\"}")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼ accumulate by content block index</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolRequest(id=toolu_1, name=get_weather, arguments={"city":"hangzhou"})</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：文本块与工具块可在同一条流里交错出现；<code>partial_json</code> 按到达顺序拼接原始 JSON 文本，不重新格式化。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五工具调用tool-usewire-细节">五、工具调用（Tool Use）wire 细节<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%BA%94%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8tool-usewire-%E7%BB%86%E8%8A%82" class="hash-link" aria-label="五、工具调用（Tool Use）wire 细节的直接链接" title="五、工具调用（Tool Use）wire 细节的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51assistant-发起调用tool_use-块">5.1、assistant 发起调用（tool_use 块）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#51assistant-%E5%8F%91%E8%B5%B7%E8%B0%83%E7%94%A8tool_use-%E5%9D%97" class="hash-link" aria-label="5.1、assistant 发起调用（tool_use 块）的直接链接" title="5.1、assistant 发起调用（tool_use 块）的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"I will check."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_use"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"input"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"city"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hangzhou"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：与 OpenAI 的 <code>arguments</code> 字符串不同，Anthropic 的 <code>input</code> 是<strong>对象</strong>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52工具结果回填tool_result-块roleuser">5.2、工具结果回填（tool_result 块，role=user）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#52%E5%B7%A5%E5%85%B7%E7%BB%93%E6%9E%9C%E5%9B%9E%E5%A1%ABtool_result-%E5%9D%97roleuser" class="hash-link" aria-label="5.2、工具结果回填（tool_result 块，role=user）的直接链接" title="5.2、工具结果回填（tool_result 块，role=user）的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_result"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_use_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"temperature\":22}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><code>tool_use_id</code> 与发起时的 <code>tool_use.id</code> 对应。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六完整调用演练user-prompt--tool-usecurl">六、完整调用演练：user prompt + tool use（curl）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E5%85%AD%E5%AE%8C%E6%95%B4%E8%B0%83%E7%94%A8%E6%BC%94%E7%BB%83user-prompt--tool-usecurl" class="hash-link" aria-label="六、完整调用演练：user prompt + tool use（curl）的直接链接" title="六、完整调用演练：user prompt + tool use（curl）的直接链接" translate="no">​</a></h2>
<p>场景：用户问 <strong>"杭州今天天气怎么样？"</strong>，并声明一个 <code>get_weather</code> 工具。下面从 curl 构造到返回，非流式 / 流式都演示一遍（含工具结果回填的第二轮）。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="61声明-tools">6.1、声明 tools<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#61%E5%A3%B0%E6%98%8E-tools" class="hash-link" aria-label="6.1、声明 tools的直接链接" title="6.1、声明 tools的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询指定城市的当前天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"input_schema"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"city"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：Anthropic 工具用 <code>input_schema</code>（不是 OpenAI 的 <code>parameters</code>）。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="62非流式--第一轮模型决定调用工具">6.2、非流式 · 第一轮：模型决定调用工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#62%E9%9D%9E%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%B8%80%E8%BD%AE%E6%A8%A1%E5%9E%8B%E5%86%B3%E5%AE%9A%E8%B0%83%E7%94%A8%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="6.2、非流式 · 第一轮：模型决定调用工具的直接链接" title="6.2、非流式 · 第一轮：模型决定调用工具的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.anthropic.com/v1/messages </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x-api-key: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ANTHROPIC_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"anthropic-version: 2023-06-01"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "claude-3-5-sonnet-latest",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "max_tokens": 1024,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "system": "你是一个简洁的助手。",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "user", "content": "杭州今天天气怎么样？" }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "input_schema": { "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "properties": { "city": { "type": "string", "description": "城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "required": ["city"] } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": { "type": "auto" }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p><strong>返回（注意 <code>stop_reason=tool_use</code>、<code>content</code> 含 <code>tool_use</code> 块）：</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"msg_abc"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"claude-3-5-sonnet-latest"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"我来查询一下杭州的天气。"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_use"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"input"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"city"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_use"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_sequence"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"input_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">88</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"output_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">35</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>tool_use.input</code> 是<strong>对象</strong> <code>{"city":"杭州"}</code>（与 OpenAI 的 <code>arguments</code> 字符串不同）。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="63非流式--第二轮回填工具结果得到最终答案">6.3、非流式 · 第二轮：回填工具结果，得到最终答案<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#63%E9%9D%9E%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%BA%8C%E8%BD%AE%E5%9B%9E%E5%A1%AB%E5%B7%A5%E5%85%B7%E7%BB%93%E6%9E%9C%E5%BE%97%E5%88%B0%E6%9C%80%E7%BB%88%E7%AD%94%E6%A1%88" class="hash-link" aria-label="6.3、非流式 · 第二轮：回填工具结果，得到最终答案的直接链接" title="6.3、非流式 · 第二轮：回填工具结果，得到最终答案的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.anthropic.com/v1/messages </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x-api-key: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ANTHROPIC_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"anthropic-version: 2023-06-01"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "claude-3-5-sonnet-latest",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "max_tokens": 1024,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "system": "你是一个简洁的助手。",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "user", "content": "杭州今天天气怎么样？" },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "assistant", "content": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          { "type": "text", "text": "我来查询一下杭州的天气。" },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          { "type": "tool_use", "id": "toolu_1", "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "input": { "city": "杭州" } } ] },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "user", "content": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          { "type": "tool_result", "tool_use_id": "toolu_1",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "content": "{\"temperature\":26,\"text\":\"晴\"}" } ] }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "name": "get_weather", "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "input_schema": { "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "properties": { "city": { "type": "string" } }, "required": ["city"] } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p><strong>返回（<code>stop_reason=end_turn</code>）：</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"msg_def"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 26℃，天气晴。"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"end_turn"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"input_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">140</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"output_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">16</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="64流式--第一轮input_json_delta-增量streamtrue">6.4、流式 · 第一轮：<code>input_json_delta</code> 增量（<code>stream:true</code>）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#64%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%B8%80%E8%BD%AEinput_json_delta-%E5%A2%9E%E9%87%8Fstreamtrue" class="hash-link" aria-label="64流式--第一轮input_json_delta-增量streamtrue的直接链接" title="64流式--第一轮input_json_delta-增量streamtrue的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-N</span><span class="token plain"> https://api.anthropic.com/v1/messages </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x-api-key: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ANTHROPIC_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"anthropic-version: 2023-06-01"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "claude-3-5-sonnet-latest",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "max_tokens": 1024,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream": true,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [ { "role": "user", "content": "杭州今天天气怎么样？" } ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "name": "get_weather", "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "input_schema": { "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "properties": { "city": { "type": "string" } }, "required": ["city"] } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": { "type": "auto" }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p><strong>SSE（按到达顺序，注意 <code>event:</code> + <code>data:</code>）：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_start","message":{"id":"msg_abc","role":"assistant","content":[],"usage":{"input_tokens":88,"output_tokens":1}}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"我来查询一下杭州的天气。"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_stop","index":0}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_1","name":"get_weather","input":{}}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"city\":"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\"杭州\"}"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_stop","index":1}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"output_tokens":35}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_stop"}</span><br></div></code></pre></div></div>
<p><strong>聚合</strong>（按 content block <code>index</code> 归属；<code>partial_json</code> 拼接）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">index=1 (tool_use) → id=toolu_1, name=get_weather, input={"city":"杭州"}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="65流式--第二轮回填后生成最终文本">6.5、流式 · 第二轮：回填后生成最终文本<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#65%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%BA%8C%E8%BD%AE%E5%9B%9E%E5%A1%AB%E5%90%8E%E7%94%9F%E6%88%90%E6%9C%80%E7%BB%88%E6%96%87%E6%9C%AC" class="hash-link" aria-label="6.5、流式 · 第二轮：回填后生成最终文本的直接链接" title="6.5、流式 · 第二轮：回填后生成最终文本的直接链接" translate="no">​</a></h2>
<p>请求体同 6.3，追加 <code>"stream": true</code>。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_start","message":{"id":"msg_def","role":"assistant","content":[],"usage":{"input_tokens":140,"output_tokens":1}}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"杭州今天 26℃"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"，天气晴。"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_stop","index":0}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":16}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_stop"}</span><br></div></code></pre></div></div>
<p><strong>聚合</strong>：<code>text_delta</code> 顺序拼接 → <code>"杭州今天 26℃，天气晴。"</code>。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="66演练要点小结">6.6、演练要点小结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#66%E6%BC%94%E7%BB%83%E8%A6%81%E7%82%B9%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="6.6、演练要点小结的直接链接" title="6.6、演练要点小结的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>观测点</th><th>非流式</th><th>流式</th></tr></thead><tbody><tr><td>工具参数</td><td><code>content[].tool_use.input</code>（对象）</td><td><code>content_block_delta.input_json_delta.partial_json</code> 分片，按 block <code>index</code> 聚合</td></tr><tr><td>是否发工具</td><td><code>stop_reason == "tool_use"</code></td><td><code>message_delta.delta.stop_reason == "tool_use"</code></td></tr><tr><td>文本</td><td><code>content[].text</code></td><td><code>content_block_delta.text_delta.text</code> 逐段回调</td></tr><tr><td>usage</td><td>响应体 <code>usage</code></td><td><code>message_start</code>（input）+ <code>message_delta</code>（output）</td></tr><tr><td>结束</td><td>—</td><td><code>event: message_stop</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="67真实测试验证">6.7、真实测试验证<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#67%E7%9C%9F%E5%AE%9E%E6%B5%8B%E8%AF%95%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="6.7、真实测试验证的直接链接" title="6.7、真实测试验证的直接链接" translate="no">​</a></h2>
<p>可直接运行仓库内脚本（需设置 API Key）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">ANTHROPIC_API_KEY</span><span class="token operator">=</span><span class="token plain">sk-ant-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> docs/dev/backend/AgentForge核心模块设计原理/01、model模型协议层/chatmodel/anthropic/verify-anthropic-chat.sh</span><br></div></code></pre></div></div>
<p>脚本依次执行：① 非流式工具调用请求；② 用 <code>jq</code> 打印 <code>tool_use</code> 块；③ 流式请求打印 SSE。可选环境变量 <code>ANTHROPIC_BASE_URL</code> / <code>ANTHROPIC_MODEL</code> 覆盖默认值。</p>
<blockquote>
<p><strong>注意</strong>：真实调用会产生费用；请勿把 Key 写入脚本或提交到仓库。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七总结">七、总结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%B8%83%E6%80%BB%E7%BB%93" class="hash-link" aria-label="七、总结的直接链接" title="七、总结的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>Endpoint</strong>：<code>POST {baseUrl}/v1/messages</code>；<code>x-api-key</code> + <code>anthropic-version</code> 鉴权。</li>
<li class=""><strong>system</strong>：顶层字段，不是 role message。</li>
<li class=""><strong>content</strong>：block 数组（<code>text</code> / <code>tool_use</code> / <code>tool_result</code> / …）。</li>
<li class=""><strong>请求</strong>：<code>model</code> + 必填 <code>max_tokens</code> + 采样/工具参数。</li>
<li class=""><strong>响应</strong>：<code>content[]</code> + <code>stop_reason</code> + <code>usage</code>。</li>
<li class=""><strong>流式</strong>：带事件名的 SSE；工具参数按 block <code>index</code> 聚合 <code>input_json_delta</code>。</li>
<li class=""><strong>工具</strong>：<code>tool_use</code>（<code>input</code> 为对象）→ <code>tool_result</code>（以 <code>user</code> 角色回填）。</li>
</ol>
<blockquote>
<p>AgentForge 如何映射为统一类型，见《Anthropic协议02、AgentForge Anthropic接入核心实践》。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://platform.claude.com/docs/en/api/messages/create" target="_blank" rel="noopener noreferrer" class="">Anthropic Messages API（英文）</a></p>
<p>[2]. <a href="https://platform.claude.com/docs/zh-CN/api/messages/create" target="_blank" rel="noopener noreferrer" class="">Anthropic Messages API（中文）</a></p>
<p>[3]. <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" target="_blank" rel="noopener noreferrer" class="">Server-Sent Events（MDN）</a></p>
<p>[4]. 相关内部文档：[AgentForge Anthropic 接入核心实践](/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议02、AgentForge 适配Anthropic接入核心实践)</p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>模型协议层</category>
            <category>Anthropic</category>
        </item>
        <item>
            <title><![CDATA[OpenAI协议02、AgentForge 适配OpenAI接入核心实践]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/openai/OpenAI协议02、AgentForge 适配OpenAI接入核心实践</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/openai/OpenAI协议02、AgentForge 适配OpenAI接入核心实践</guid>
            <pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[更新日期：2026-10-05]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>更新日期：2026-10-05<br>
<!-- -->适用模块：<code>agentforge-model-openai</code><br>
<!-- -->当前实现：<code>OpenAiChatModel</code>（Blocking）、<code>OpenAiStreamingChatModel</code>（SSE）<br>
<!-- -->Wire API：OpenAI Chat Completions<br>
<!-- -->维护者：changlu</p>
</blockquote>
<!-- -->
<p>一句话结论：OpenAI Provider 把 AgentForge 统一的 <code>ChatRequest</code> / <code>ChatResponse</code> 与 <strong>Chat Completions</strong> wire 格式做双向映射；协议本身见《OpenAI协议01、OpenAI底层协议快速理解》，本文讲 AgentForge 的实现与边界。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动一套-core适配多家模型">1.1、场景驱动：一套 Core，适配多家模型<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E4%B8%80%E5%A5%97-core%E9%80%82%E9%85%8D%E5%A4%9A%E5%AE%B6%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="1.1、场景驱动：一套 Core，适配多家模型的直接链接" title="1.1、场景驱动：一套 Core，适配多家模型的直接链接" translate="no">​</a></h2>
<blockquote>
<p>AgentForge 的 Agent Runtime 只依赖 <code>ChatModel</code> / <code>StreamingChatModel</code> 接口。我们希望"换模型"只换一个 Provider 依赖，而不是改 Agent 代码。</p>
</blockquote>
<p>于是 OpenAI Provider 的职责被限定为<strong>协议翻译层</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Agent Runtime</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     │  ChatRequest / ChatResponse（Provider 无关）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAiChatModel / OpenAiStreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     │  Chat Completions wire</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI / OpenAI-compatible 服务</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导统一类型如何落到-openai-wire">1.2、问题引导：统一类型如何落到 OpenAI wire？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BC%E7%BB%9F%E4%B8%80%E7%B1%BB%E5%9E%8B%E5%A6%82%E4%BD%95%E8%90%BD%E5%88%B0-openai-wire" class="hash-link" aria-label="1.2、问题引导：统一类型如何落到 OpenAI wire？的直接链接" title="1.2、问题引导：统一类型如何落到 OpenAI wire？的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>问题</strong>：AgentForge 的 <code>ChatMessage</code>、<code>ChatRequestParameters</code>、<code>ChatResponse</code> 分别怎么落到 OpenAI 的 <code>messages</code> / 请求字段 / 响应字段？工具调用如何双向映射？流式如何聚合？</p>
</blockquote>
<p>本文逐项给出映射表与实现位置。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13实现边界">1.3、实现边界<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#13%E5%AE%9E%E7%8E%B0%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="1.3、实现边界的直接链接" title="1.3、实现边界的直接链接" translate="no">​</a></h2>
<ul>
<li class="">Wire API：<code>POST {baseUrl}/chat/completions</code>（默认 <code>https://api.openai.com/v1</code>）；</li>
<li class="">release_1.x 以 Chat Completions 为第一版统一协议，Responses API 采用"并存 Adapter"策略（见第六章）。</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="14builder-参数">1.4、Builder 参数<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#14builder-%E5%8F%82%E6%95%B0" class="hash-link" aria-label="1.4、Builder 参数的直接链接" title="1.4、Builder 参数的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>Builder 参数</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>baseUrl</code></td><td><code>https://api.openai.com/v1</code></td><td>也支持 OpenAI-compatible 服务</td></tr><tr><td><code>apiKey</code></td><td><code>null</code></td><td>非空时发送 <code>Authorization: Bearer ...</code></td></tr><tr><td><code>modelName</code></td><td><code>null</code></td><td>请求前必须有值</td></tr><tr><td><code>temperature</code></td><td><code>null</code></td><td>非空才发送</td></tr><tr><td><code>maxTokens</code></td><td><code>null</code></td><td>映射 <code>max_tokens</code></td></tr><tr><td><code>topP</code></td><td><code>null</code></td><td>映射 <code>top_p</code></td></tr><tr><td><code>stopSequences</code></td><td><code>null</code></td><td>映射 <code>stop</code></td></tr><tr><td><code>customParameter</code></td><td>空</td><td>透传 Provider 扩展顶层字段</td></tr><tr><td><code>customHeader</code></td><td>空</td><td>追加自定义 HTTP Header</td></tr><tr><td><code>httpTransport</code></td><td><code>JdkHttpTransport</code></td><td>HTTP SPI</td></tr><tr><td><code>connectTimeoutMillis</code></td><td><code>10000</code></td><td>连接超时</td></tr><tr><td><code>readTimeoutMillis</code></td><td><code>60000</code></td><td>读取超时</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：<code>OpenAiStreamingChatModel</code> 复用同一套配置，并强制写入 <code>stream=true</code> 与 <code>stream_options.include_usage=true</code>。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念">二、核心概念<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="二、核心概念的直接链接" title="二、核心概念的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21统一入参">2.1、统一入参<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#21%E7%BB%9F%E4%B8%80%E5%85%A5%E5%8F%82" class="hash-link" aria-label="2.1、统一入参的直接链接" title="2.1、统一入参的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatRequest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Double</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Integer</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Double</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">topP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">stopSequences</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">customParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22统一出参">2.2、统一出参<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#22%E7%BB%9F%E4%B8%80%E5%87%BA%E5%8F%82" class="hash-link" aria-label="2.2、统一出参的直接链接" title="2.2、统一出参的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">AiMessage</span><span class="token plain"> aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)">// text + toolExecutionRequests</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">TokenUsage</span><span class="token plain"> tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">FinishReason</span><span class="token plain"> finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：Provider 的职责就是把 wire 字段无损地落到这两个统一对象上。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三实现思路与映射">三、实现思路与映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF%E4%B8%8E%E6%98%A0%E5%B0%84" class="hash-link" aria-label="三、实现思路与映射的直接链接" title="三、实现思路与映射的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31http-headers">3.1、HTTP Headers<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#31http-headers" class="hash-link" aria-label="3.1、HTTP Headers的直接链接" title="3.1、HTTP Headers的直接链接" translate="no">​</a></h2>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Accept: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Authorization: Bearer ${apiKey}   # apiKey 非空时</span><br></div></code></pre></div></div>
<p>之后追加 <code>customHeaders</code>；企业内部网关可用 <code>customHeader(...)</code> 增加租户、路由、trace 等。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32消息映射">3.2、消息映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#32%E6%B6%88%E6%81%AF%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.2、消息映射的直接链接" title="3.2、消息映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>AgentForge</th><th>OpenAI role</th><th>content / 关键字段</th></tr></thead><tbody><tr><td><code>SystemMessage</code></td><td><code>system</code></td><td><code>message.text()</code></td></tr><tr><td><code>UserMessage</code>（单文本）</td><td><code>user</code></td><td><code>message.text()</code></td></tr><tr><td><code>UserMessage</code>（多 <code>Content</code>）</td><td><code>user</code></td><td><code>content[]</code>，逐条 <code>TextContent</code> → <code>{"type":"text","text":...}</code></td></tr><tr><td><code>AiMessage</code>（纯文本）</td><td><code>assistant</code></td><td><code>message.text()</code></td></tr><tr><td><code>AiMessage</code>（含工具调用）</td><td><code>assistant</code></td><td><code>content</code>（可为 <code>null</code>）+ <code>tool_calls[]</code></td></tr><tr><td><code>ToolExecutionResultMessage</code></td><td><code>tool</code></td><td><code>tool_call_id = message.id()</code>，<code>content = message.text()</code></td></tr></tbody></table>
<p>工具调用 + 结果回填的 wire 形态：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"messages"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"hangzhou\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"temperature\":22}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>CustomMessage</code> 当前未实现 wire mapping，遇到会抛 <code>IllegalArgumentException</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="33参数映射">3.3、参数映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#33%E5%8F%82%E6%95%B0%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.3、参数映射的直接链接" title="3.3、参数映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>AgentForge 参数</th><th>Chat Completions 字段</th><th>当前行为</th></tr></thead><tbody><tr><td><code>modelName</code></td><td><code>model</code></td><td>必填；为空本地失败</td></tr><tr><td><code>temperature</code></td><td><code>temperature</code></td><td>非空才发送</td></tr><tr><td><code>maxTokens</code></td><td><code>max_tokens</code></td><td>非空才发送</td></tr><tr><td><code>topP</code></td><td><code>top_p</code></td><td>非空才发送</td></tr><tr><td><code>stopSequences</code></td><td><code>stop</code></td><td>非空才发送</td></tr><tr><td><code>tools</code></td><td><code>tools[]</code></td><td><code>{"type":"function","function":{name,description,parameters,strict}}</code></td></tr><tr><td><code>toolChoice</code></td><td><code>tool_choice</code></td><td><code>AUTO→"auto"</code>、<code>NONE→"none"</code>、<code>REQUIRED→"required"</code>、<code>SPECIFIC→{"type":"function","function":{"name":X}}</code></td></tr><tr><td><code>customParameters</code></td><td>顶层原样写入</td><td>通用字段写入后覆盖同名 custom 字段</td></tr></tbody></table>
<p>构建顺序（标准字段拥有最终优先级）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">1. payload.putAll(customParameters)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">2. 写入 model / messages</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">3. 写入 temperature / max_tokens / top_p / stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">4. 写入 tools / tool_choice</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：即使 <code>customParameters</code> 写了另一个 <code>model</code>，最终仍会被 <code>modelName</code> 覆盖。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="34响应映射">3.4、响应映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#34%E5%93%8D%E5%BA%94%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.4、响应映射的直接链接" title="3.4、响应映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>OpenAI 字段</th><th>AgentForge 字段</th></tr></thead><tbody><tr><td><code>choices[0].message.content</code></td><td><code>ChatResponse.aiMessage().text()</code></td></tr><tr><td><code>choices[0].message.tool_calls[]</code></td><td><code>ChatResponse.aiMessage().toolExecutionRequests()</code></td></tr><tr><td><code>usage.prompt_tokens</code></td><td><code>TokenUsage.inputTokens()</code></td></tr><tr><td><code>usage.completion_tokens</code></td><td><code>TokenUsage.outputTokens()</code></td></tr><tr><td><code>usage.total_tokens</code></td><td><code>TokenUsage.totalTokens()</code></td></tr><tr><td><code>choices[0].finish_reason</code></td><td><code>ChatResponse.finishReason()</code></td></tr><tr><td><code>id</code> / <code>model</code> / <code>created</code></td><td><code>metadata["id"] / ["model"] / ["created"]</code></td></tr></tbody></table>
<p><code>tool_calls[]</code> 映射：<code>id → ToolExecutionRequest.id</code>、<code>function.name → name</code>、<code>function.arguments → arguments</code>（保留原始 JSON 字符串，不重新格式化）。</p>
<blockquote>
<p><strong>注意</strong>：当 <code>tool_calls</code> 非空且 <code>content</code> 为空时，<code>AiMessage.text()</code> 返回 <code>null</code>；两者同时存在时，两者都会保留。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="35finish_reason-映射">3.5、finish_reason 映射<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#35finish_reason-%E6%98%A0%E5%B0%84" class="hash-link" aria-label="3.5、finish_reason 映射的直接链接" title="3.5、finish_reason 映射的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>OpenAI</th><th>AgentForge <code>FinishReason</code></th></tr></thead><tbody><tr><td><code>stop</code></td><td><code>STOP</code></td></tr><tr><td><code>length</code></td><td><code>LENGTH</code></td></tr><tr><td><code>tool_calls</code></td><td><code>TOOL_EXECUTION</code></td></tr><tr><td><code>function_call</code></td><td><code>TOOL_EXECUTION</code></td></tr><tr><td><code>content_filter</code></td><td><code>CONTENT_FILTER</code></td></tr><tr><td>其他非空</td><td><code>OTHER</code></td></tr><tr><td><code>null</code></td><td><code>null</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="36响应结构异常">3.6、响应结构异常<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#36%E5%93%8D%E5%BA%94%E7%BB%93%E6%9E%84%E5%BC%82%E5%B8%B8" class="hash-link" aria-label="3.6、响应结构异常的直接链接" title="3.6、响应结构异常的直接链接" translate="no">​</a></h2>
<p>以下情况直接抛 <code>ModelException</code>（不静默返回空）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">choices 不存在 / 为空 / choices[0].message 不存在</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="37流式实现">3.7、流式实现<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#37%E6%B5%81%E5%BC%8F%E5%AE%9E%E7%8E%B0" class="hash-link" aria-label="3.7、流式实现的直接链接" title="3.7、流式实现的直接链接" translate="no">​</a></h2>
<p><strong>请求</strong>：同 Endpoint，强制 <code>stream=true</code> + <code>stream_options.include_usage=true</code>。</p>
<p><strong>SSE 解析</strong>：忽略空行、<code>:</code> 注释、非 <code>data:</code> 行；每个 <code>data:</code> chunk 读取 <code>choices[0].delta.content</code>、<code>delta.tool_calls[]</code>、<code>finish_reason</code>。</p>
<ul>
<li class=""><code>delta.content</code> → 立即 <code>handler.onPartialResponse(...)</code>，同时本地 <code>StringBuilder</code> 聚合；</li>
<li class=""><code>delta.tool_calls[]</code> → <strong>按 <code>index</code> 分桶累加</strong>（规则见协议篇 4.1），不回调半成品；</li>
<li class="">结束 → <code>handler.onCompleteResponse(response)</code>。</li>
</ul>
<p><strong>最终响应</strong>：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">AiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">fullText</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> toolExecutionRequests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：最终 usage chunk（<code>choices=[]</code>）由"先读 usage、再判断 choices 是否为空"处理；流中断时最终 usage 可能缺失，<code>ChatResponse.tokenUsage()</code> 不保证一定存在。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四实战代码">四、实战代码<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E5%9B%9B%E5%AE%9E%E6%88%98%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="四、实战代码的直接链接" title="四、实战代码的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41非流式">4.1、非流式<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#41%E9%9D%9E%E6%B5%81%E5%BC%8F" class="hash-link" aria-label="4.1、非流式的直接链接" title="4.1、非流式的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">OpenAiChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">OpenAiChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">baseUrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://api.openai.com/v1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"your-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1024</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatRequest</span><span class="token plain"> request </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">SystemMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"You are a concise Java assistant."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"What is CAS?"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">$ </span><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{"model":"your-model","messages":[{"role":"system","content":"..."},{"role":"user","content":"What is CAS?"}]}'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"choices"</span><span class="token plain">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"index"</span><span class="token plain">:0,</span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token plain">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"role"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"content"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"CAS means Compare-And-Swap."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">,</span><span class="token string" style="color:rgb(255, 121, 198)">"finish_reason"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token string" style="color:rgb(255, 121, 198)">"stop"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"usage"</span><span class="token plain">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"prompt_tokens"</span><span class="token plain">:20,</span><span class="token string" style="color:rgb(255, 121, 198)">"completion_tokens"</span><span class="token plain">:10,</span><span class="token string" style="color:rgb(255, 121, 198)">"total_tokens"</span><span class="token plain">:30</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="42流式">4.2、流式<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#42%E6%B5%81%E5%BC%8F" class="hash-link" aria-label="4.2、流式的直接链接" title="4.2、流式的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">OpenAiStreamingChatModel</span><span class="token plain"> streaming </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">OpenAiStreamingChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">baseUrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://api.openai.com/v1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"your-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">streaming</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">StreamingChatResponseHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onPartialResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> partial</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">partial</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onCompleteResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatResponse</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Throwable</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">printStackTrace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><strong>运行输出（模拟终端）</strong></p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">CAS means Compare-And-Swap.</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="43openai-compatible-服务">4.3、OpenAI-compatible 服务<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#43openai-compatible-%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="4.3、OpenAI-compatible 服务的直接链接" title="4.3、OpenAI-compatible 服务的直接链接" translate="no">​</a></h2>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">OpenAiChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">OpenAiChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">baseUrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://example.com/v1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"..."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"provider-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五兼容性错误与边界">五、兼容性、错误与边界<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E4%BA%94%E5%85%BC%E5%AE%B9%E6%80%A7%E9%94%99%E8%AF%AF%E4%B8%8E%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="五、兼容性、错误与边界的直接链接" title="五、兼容性、错误与边界的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51http-错误">5.1、HTTP 错误<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#51http-%E9%94%99%E8%AF%AF" class="hash-link" aria-label="5.1、HTTP 错误的直接链接" title="5.1、HTTP 错误的直接链接" translate="no">​</a></h2>
<p>非 2xx → <code>new ModelException("OpenAI request failed with HTTP " + statusCode, statusCode, responseBody)</code>；网络层 IOException → <code>ModelException("OpenAI request failed", cause)</code>。流式非 2xx 会累积 body 并通过 <code>handler.onError(...)</code> 返回。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52协议差距release_1x-未映射">5.2、协议差距（release_1.x 未映射）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#52%E5%8D%8F%E8%AE%AE%E5%B7%AE%E8%B7%9Drelease_1x-%E6%9C%AA%E6%98%A0%E5%B0%84" class="hash-link" aria-label="5.2、协议差距（release_1.x 未映射）的直接链接" title="5.2、协议差距（release_1.x 未映射）的直接链接" translate="no">​</a></h2>
<p><code>developer</code> role、图片 / 音频多模态 <code>content</code>、流式 <code>onPartialToolCall</code>、refusal、logprobs、structured outputs、audio、provider reasoning（<code>AiMessage.thinking()</code> 字段位已留未接线）、多 <code>choices</code>。</p>
<blockquote>
<p><code>customParameters</code> 可临时透传请求字段；但若返回结构需要框架理解，仍须正式扩展 Core / Provider 类型。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六总结与演进">六、总结与演进<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E5%85%AD%E6%80%BB%E7%BB%93%E4%B8%8E%E6%BC%94%E8%BF%9B" class="hash-link" aria-label="六、总结与演进的直接链接" title="六、总结与演进的直接链接" translate="no">​</a></h2>
<p>已落地：<code>tool</code> role 的 <code>tool_call_id</code> 请求映射、<code>tools/tool_choice</code>（含 <code>SPECIFIC</code>）、<code>tool_calls</code> 非流式解析与流式按 <code>index</code> 聚合、<code>UserMessage</code> 多 <code>Content</code> → <code>content[]</code>。</p>
<p>OpenAI 官方建议新应用优先 Responses API，因此后续采用<strong>并存 Adapter</strong>：<code>OpenAiChatModel → /chat/completions</code>、<code>OpenAiStreamingChatModel → /chat/completions + SSE</code>，未来新增 <code>OpenAiResponsesModel → /responses</code>。上层仍只依赖 Core 接口，协议迁移不侵入 Agent Runtime。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE02%E3%80%81AgentForge%20%E9%80%82%E9%85%8DOpenAI%E6%8E%A5%E5%85%A5%E6%A0%B8%E5%BF%83%E5%AE%9E%E8%B7%B5#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create" target="_blank" rel="noopener noreferrer" class="">OpenAI Chat Completions API（官方参考）</a></p>
<p>[2]. <a href="https://developers.openai.com/api/docs/guides/text" target="_blank" rel="noopener noreferrer" class="">OpenAI 文本生成指南</a></p>
<p>[3]. 相关内部文档：<a class="" href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3">OpenAI 底层协议快速理解</a>、[ChatModel 核心协议层设计](/blog/01、model模型协议层/chatmodel/统一ChatModel协议层01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现)</p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>模型协议层</category>
            <category>OpenAI</category>
        </item>
        <item>
            <title><![CDATA[OpenAI协议01、OpenAI底层协议快速理解]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/openai/OpenAI协议01、OpenAI底层协议快速理解</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/openai/OpenAI协议01、OpenAI底层协议快速理解</guid>
            <pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[更新日期：2026-10-05]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>更新日期：2026-10-05<br>
<!-- -->适用场景：理解 <code>agentforge-model-openai</code> 所对接的 OpenAI Chat Completions wire protocol<br>
<!-- -->维护者：changlu</p>
</blockquote>
<!-- -->
<p>一句话结论：AgentForge 的 OpenAI Provider 对接的是 <strong>Chat Completions</strong>（<code>POST {baseUrl}/chat/completions</code>）。本文只讲"协议本身长什么样"；AgentForge 如何把它映射成统一 <code>ChatRequest</code> / <code>ChatResponse</code>，见《OpenAI协议02、AgentForge OpenAI接入核心实践》。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景与问题引入">一、背景与问题引入<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%8E%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5" class="hash-link" aria-label="一、背景与问题引入的直接链接" title="一、背景与问题引入的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="11场景驱动为什么第一版选-chat-completions">1.1、场景驱动：为什么第一版选 Chat Completions<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#11%E5%9C%BA%E6%99%AF%E9%A9%B1%E5%8A%A8%E4%B8%BA%E4%BB%80%E4%B9%88%E7%AC%AC%E4%B8%80%E7%89%88%E9%80%89-chat-completions" class="hash-link" aria-label="1.1、场景驱动：为什么第一版选 Chat Completions的直接链接" title="1.1、场景驱动：为什么第一版选 Chat Completions的直接链接" translate="no">​</a></h2>
<blockquote>
<p>在为 AgentForge 统一接入多家大模型时，我们遇到一个现实问题：OpenAI 官方一边推荐新的 <strong>Responses API</strong>，一边又有大量 OpenAI-compatible 服务（自建网关、国产模型）只兼容 <code>/chat/completions</code>。</p>
</blockquote>
<p>因此 AgentForge release_1.x 选择 <strong>Chat Completions</strong> 作为第一版统一接入协议：它仍是明确存在的 API，且兼容面最广。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="12问题引导一次对话调用到底发了什么">1.2、问题引导：一次对话调用到底发了什么？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#12%E9%97%AE%E9%A2%98%E5%BC%95%E5%AF%BC%E4%B8%80%E6%AC%A1%E5%AF%B9%E8%AF%9D%E8%B0%83%E7%94%A8%E5%88%B0%E5%BA%95%E5%8F%91%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="1.2、问题引导：一次对话调用到底发了什么？的直接链接" title="1.2、问题引导：一次对话调用到底发了什么？的直接链接" translate="no">​</a></h2>
<blockquote>
<p><strong>问题</strong>：当 Agent 调用一次 OpenAI 模型，请求体、响应体、流式 SSE 分别长什么样？工具调用（function calling）在 wire 上如何表达与回填？</p>
</blockquote>
<p>本文按"请求 → 响应 → 流式 → 工具调用"的顺序把协议讲清。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="13协议定位">1.3、协议定位<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#13%E5%8D%8F%E8%AE%AE%E5%AE%9A%E4%BD%8D" class="hash-link" aria-label="1.3、协议定位的直接链接" title="1.3、协议定位的直接链接" translate="no">​</a></h2>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">POST {baseUrl}/chat/completions</span><br></div></code></pre></div></div>
<p>默认：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">baseUrl = https://api.openai.com/v1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">→ 实际地址 https://api.openai.com/v1/chat/completions</span><br></div></code></pre></div></div>
<p>官方参考：</p>
<ul>
<li class=""><a href="https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create" target="_blank" rel="noopener noreferrer" class="">https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create</a></li>
<li class=""><a href="https://developers.openai.com/api/docs/guides/text" target="_blank" rel="noopener noreferrer" class="">https://developers.openai.com/api/docs/guides/text</a></li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二请求协议">二、请求协议<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%BA%8C%E8%AF%B7%E6%B1%82%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="二、请求协议的直接链接" title="二、请求协议的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="21headers">2.1、Headers<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#21headers" class="hash-link" aria-label="2.1、Headers的直接链接" title="2.1、Headers的直接链接" translate="no">​</a></h2>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Content-Type: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Accept: application/json</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Authorization: Bearer ${apiKey}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：鉴权用标准 <code>Authorization: Bearer</code>；企业网关可追加自定义 Header（trace / 租户 / 路由）。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="22请求体messages核心">2.2、请求体：messages（核心）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#22%E8%AF%B7%E6%B1%82%E4%BD%93messages%E6%A0%B8%E5%BF%83" class="hash-link" aria-label="2.2、请求体：messages（核心）的直接链接" title="2.2、请求体：messages（核心）的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>role</th><th>关键字段</th><th>含义</th></tr></thead><tbody><tr><td><code>system</code></td><td><code>content</code></td><td>系统提示</td></tr><tr><td><code>user</code></td><td><code>content</code> 或 <code>content[]</code></td><td>用户输入（多模态时为数组）</td></tr><tr><td><code>assistant</code></td><td><code>content</code> / <code>tool_calls[]</code></td><td>模型回复，可携带工具调用</td></tr><tr><td><code>tool</code></td><td><code>tool_call_id</code> + <code>content</code></td><td>工具执行结果回填</td></tr></tbody></table>
<p>纯文本示例：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"your-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"messages"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"system"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"You are a concise Java assistant."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"What is CAS?"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="23请求体采样与工具参数">2.3、请求体：采样与工具参数<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#23%E8%AF%B7%E6%B1%82%E4%BD%93%E9%87%87%E6%A0%B7%E4%B8%8E%E5%B7%A5%E5%85%B7%E5%8F%82%E6%95%B0" class="hash-link" aria-label="2.3、请求体：采样与工具参数的直接链接" title="2.3、请求体：采样与工具参数的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>字段</th><th>说明</th></tr></thead><tbody><tr><td><code>temperature</code></td><td>采样温度</td></tr><tr><td><code>max_tokens</code></td><td>最大生成 token</td></tr><tr><td><code>top_p</code></td><td>核采样</td></tr><tr><td><code>stop</code></td><td>停止序列</td></tr><tr><td><code>tools[]</code></td><td>工具声明：<code>{"type":"function","function":{name,description,parameters,strict}}</code></td></tr><tr><td><code>tool_choice</code></td><td><code>"auto"</code> / <code>"none"</code> / <code>"required"</code> / <code>{"type":"function","function":{"name":X}}</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="24完整非流式请求示例">2.4、完整非流式请求示例<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#24%E5%AE%8C%E6%95%B4%E9%9D%9E%E6%B5%81%E5%BC%8F%E8%AF%B7%E6%B1%82%E7%A4%BA%E4%BE%8B" class="hash-link" aria-label="2.4、完整非流式请求示例的直接链接" title="2.4、完整非流式请求示例的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"your-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"messages"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"system"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"You are a concise Java assistant."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"What is CAS?"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"temperature"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"max_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1024</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"top_p"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0.9</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三响应协议">三、响应协议<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%B8%89%E5%93%8D%E5%BA%94%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="三、响应协议的直接链接" title="三、响应协议的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="31非流式-chatcompletion">3.1、非流式 chat.completion<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#31%E9%9D%9E%E6%B5%81%E5%BC%8F-chatcompletion" class="hash-link" aria-label="3.1、非流式 chat.completion的直接链接" title="3.1、非流式 chat.completion的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl_xxx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"object"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chat.completion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"created"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1780000000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"your-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"CAS means Compare-And-Swap."</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stop"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>choices</code> 是数组，消费方通常只取 <code>choices[0]</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="32finish_reason">3.2、finish_reason<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#32finish_reason" class="hash-link" aria-label="3.2、finish_reason的直接链接" title="3.2、finish_reason的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>值</th><th>含义</th></tr></thead><tbody><tr><td><code>stop</code></td><td>正常结束</td></tr><tr><td><code>length</code></td><td>达到 max_tokens</td></tr><tr><td><code>tool_calls</code></td><td>需要执行工具</td></tr><tr><td><code>function_call</code></td><td>旧字段（deprecated）</td></tr><tr><td><code>content_filter</code></td><td>内容被过滤</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四流式协议sse">四、流式协议（SSE）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E5%9B%9B%E6%B5%81%E5%BC%8F%E5%8D%8F%E8%AE%AEsse" class="hash-link" aria-label="四、流式协议（SSE）的直接链接" title="四、流式协议（SSE）的直接链接" translate="no">​</a></h2>
<p>请求时增加：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"stream"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"stream_options"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"include_usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>响应为 SSE：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"content":"Hel"},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"content":"lo"}}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[],"usage":{"prompt_tokens":20,"completion_tokens":2,"total_tokens":22}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: [DONE]</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>stream_options.include_usage=true</code> 时，<code>[DONE]</code> 之前会出现一个 <code>choices=[]</code> 且携带整体 <code>usage</code> 的额外 chunk；解析时应先读 usage，再判断 choices 是否为空。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="41工具调用的流式增量按-index-聚合">4.1、工具调用的流式增量（按 index 聚合）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#41%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E7%9A%84%E6%B5%81%E5%BC%8F%E5%A2%9E%E9%87%8F%E6%8C%89-index-%E8%81%9A%E5%90%88" class="hash-link" aria-label="4.1、工具调用的流式增量（按 index 聚合）的直接链接" title="4.1、工具调用的流式增量（按 index 聚合）的直接链接" translate="no">​</a></h2>
<p><code>delta.tool_calls[]</code> 是分片下发的，必须<strong>按 <code>index</code> 聚合</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">chunk: {"index":0,"id":"call_1","function":{"name":"getWeather","arguments":""}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">chunk: {"index":0,"function":{"arguments":"{\"city\":"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">chunk: {"index":0,"function":{"arguments":"\"hangzhou\"}"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼ merge by index</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolRequest(id=call_1, name=getWeather, arguments={"city":"hangzhou"})</span><br></div></code></pre></div></div>
<table><thead><tr><th>delta 字段</th><th>聚合方式</th></tr></thead><tbody><tr><td><code>index</code></td><td>分桶键</td></tr><tr><td><code>id</code></td><td>覆盖式写入当前桶（首个 chunk 携带完整 id）</td></tr><tr><td><code>function.name</code></td><td>追加</td></tr><tr><td><code>function.arguments</code></td><td>追加原文，保留 JSON 文本</td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：部分兼容网关不下发 <code>index</code>，此时以"出现一个新的非空 <code>id</code>"作为新一次调用的起点（fallback 计数器分桶），避免把同一次调用的参数增量错拆成多次调用。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五工具调用function-callingwire-细节">五、工具调用（Function Calling）wire 细节<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%BA%94%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8function-callingwire-%E7%BB%86%E8%8A%82" class="hash-link" aria-label="五、工具调用（Function Calling）wire 细节的直接链接" title="五、工具调用（Function Calling）wire 细节的直接链接" translate="no">​</a></h2>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="51assistant-发起调用">5.1、assistant 发起调用<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#51assistant-%E5%8F%91%E8%B5%B7%E8%B0%83%E7%94%A8" class="hash-link" aria-label="5.1、assistant 发起调用的直接链接" title="5.1、assistant 发起调用的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"hangzhou\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>arguments</code> 是<strong>字符串</strong>（JSON 文本），不是对象——这是后续 AgentForge 归一化与再解析的关键。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="52tool-结果回填">5.2、tool 结果回填<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#52tool-%E7%BB%93%E6%9E%9C%E5%9B%9E%E5%A1%AB" class="hash-link" aria-label="5.2、tool 结果回填的直接链接" title="5.2、tool 结果回填的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_call_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"temperature\":22}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><code>tool_call_id</code> 必须与发起时的 <code>id</code> 一一对应。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="53多工具调用">5.3、多工具调用<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#53%E5%A4%9A%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8" class="hash-link" aria-label="5.3、多工具调用的直接链接" title="5.3、多工具调用的直接链接" translate="no">​</a></h2>
<p>一次响应可返回多个 <code>tool_calls</code>，需逐个执行并分别以 <code>tool</code> role 回填，<code>id</code> 一一对应。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六完整调用演练user-prompt--function-callscurl">六、完整调用演练：user prompt + function calls（curl）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E5%85%AD%E5%AE%8C%E6%95%B4%E8%B0%83%E7%94%A8%E6%BC%94%E7%BB%83user-prompt--function-callscurl" class="hash-link" aria-label="六、完整调用演练：user prompt + function calls（curl）的直接链接" title="六、完整调用演练：user prompt + function calls（curl）的直接链接" translate="no">​</a></h2>
<p>场景：用户问 <strong>"杭州今天天气怎么样？"</strong>，并声明一个 <code>get_weather</code> 工具。下面从 curl 构造到返回，非流式 / 流式都演示一遍（含工具结果回填的第二轮）。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="61声明-tools">6.1、声明 tools<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#61%E5%A3%B0%E6%98%8E-tools" class="hash-link" aria-label="6.1、声明 tools的直接链接" title="6.1、声明 tools的直接链接" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token property">"tools"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询指定城市的当前天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"parameters"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"city"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="62非流式--第一轮模型决定调用工具">6.2、非流式 · 第一轮：模型决定调用工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#62%E9%9D%9E%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%B8%80%E8%BD%AE%E6%A8%A1%E5%9E%8B%E5%86%B3%E5%AE%9A%E8%B0%83%E7%94%A8%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="6.2、非流式 · 第一轮：模型决定调用工具的直接链接" title="6.2、非流式 · 第一轮：模型决定调用工具的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "user", "content": "杭州今天天气怎么样？" }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "type": "function", "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": { "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type": "string", "description": "城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"] } } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p><strong>返回（注意 <code>finish_reason=tool_calls</code>、<code>content=null</code>）：</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl_abc"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"object"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chat.completion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"created"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1780000000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"杭州\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_calls"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">62</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">15</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">77</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>arguments</code> 是字符串 <code>"{\"city\":\"杭州\"}"</code>，本地解析后执行 <code>get_weather("杭州")</code>。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="63非流式--第二轮回填工具结果得到最终答案">6.3、非流式 · 第二轮：回填工具结果，得到最终答案<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#63%E9%9D%9E%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%BA%8C%E8%BD%AE%E5%9B%9E%E5%A1%AB%E5%B7%A5%E5%85%B7%E7%BB%93%E6%9E%9C%E5%BE%97%E5%88%B0%E6%9C%80%E7%BB%88%E7%AD%94%E6%A1%88" class="hash-link" aria-label="6.3、非流式 · 第二轮：回填工具结果，得到最终答案的直接链接" title="6.3、非流式 · 第二轮：回填工具结果，得到最终答案的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "user", "content": "杭州今天天气怎么样？" },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "assistant", "content": null,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "tool_calls": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          { "id": "call_1", "type": "function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "function": { "name": "get_weather", "arguments": "{\"city\":\"杭州\"}" } } ] },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "role": "tool", "tool_call_id": "call_1",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "content": "{\"temperature\":26,\"text\":\"晴\"}" }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p><strong>返回（<code>finish_reason=stop</code>）：</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl_def"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 26℃，天气晴。"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stop"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">85</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">12</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">97</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="64流式--第一轮tool_calls-增量streamtrue">6.4、流式 · 第一轮：tool_calls 增量（<code>stream:true</code>）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#64%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%B8%80%E8%BD%AEtool_calls-%E5%A2%9E%E9%87%8Fstreamtrue" class="hash-link" aria-label="64流式--第一轮tool_calls-增量streamtrue的直接链接" title="64流式--第一轮tool_calls-增量streamtrue的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream": true,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream_options": { "include_usage": true },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [ { "role": "user", "content": "杭州今天天气怎么样？" } ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "type": "function", "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name": "get_weather", "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": { "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type": "string" } }, "required": ["city"] } } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p><strong>SSE（按到达顺序）：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"杭州\"}"}}]},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[],"usage":{"prompt_tokens":62,"completion_tokens":15,"total_tokens":77}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: [DONE]</span><br></div></code></pre></div></div>
<p><strong>聚合</strong>（按 <code>tool_calls[].index</code> 分桶；<code>id</code> 覆盖、<code>name</code>/<code>arguments</code> 追加）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">index=0 → id=call_1, name=get_weather, arguments={"city":"杭州"}</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="65流式--第二轮回填后生成最终文本">6.5、流式 · 第二轮：回填后生成最终文本<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#65%E6%B5%81%E5%BC%8F--%E7%AC%AC%E4%BA%8C%E8%BD%AE%E5%9B%9E%E5%A1%AB%E5%90%8E%E7%94%9F%E6%88%90%E6%9C%80%E7%BB%88%E6%96%87%E6%9C%AC" class="hash-link" aria-label="6.5、流式 · 第二轮：回填后生成最终文本的直接链接" title="6.5、流式 · 第二轮：回填后生成最终文本的直接链接" translate="no">​</a></h2>
<p>请求体同 6.3，追加 <code>"stream": true, "stream_options": {"include_usage": true}</code>。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"role":"assistant","content":"杭州"},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"content":"今天 26℃"},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{"content":"，天气晴。"},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"choices":[],"usage":{"prompt_tokens":85,"completion_tokens":12,"total_tokens":97}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: [DONE]</span><br></div></code></pre></div></div>
<p><strong>聚合</strong>：<code>delta.content</code> 顺序拼接 → <code>"杭州今天 26℃，天气晴。"</code>。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="66演练要点小结">6.6、演练要点小结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#66%E6%BC%94%E7%BB%83%E8%A6%81%E7%82%B9%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="6.6、演练要点小结的直接链接" title="6.6、演练要点小结的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>观测点</th><th>非流式</th><th>流式</th></tr></thead><tbody><tr><td>工具参数</td><td><code>message.tool_calls[].function.arguments</code>（字符串）</td><td><code>delta.tool_calls[].function.arguments</code> 分片，按 <code>index</code> 聚合</td></tr><tr><td>是否发工具</td><td><code>finish_reason == "tool_calls"</code></td><td>最后一个 chunk 的 <code>finish_reason == "tool_calls"</code></td></tr><tr><td>文本</td><td><code>message.content</code></td><td><code>delta.content</code> 逐段回调</td></tr><tr><td>usage</td><td>响应体 <code>usage</code></td><td>末尾 <code>choices=[]</code> 的 usage chunk</td></tr><tr><td>结束</td><td>—</td><td><code>data: [DONE]</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="67真实测试验证">6.7、真实测试验证<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#67%E7%9C%9F%E5%AE%9E%E6%B5%8B%E8%AF%95%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="6.7、真实测试验证的直接链接" title="6.7、真实测试验证的直接链接" translate="no">​</a></h2>
<p>可直接运行仓库内脚本（需设置 API Key）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">OPENAI_API_KEY</span><span class="token operator">=</span><span class="token plain">sk-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> docs/dev/backend/AgentForge核心模块设计原理/01、model模型协议层/chatmodel/openai/verify-openai-chat.sh</span><br></div></code></pre></div></div>
<p>脚本依次执行：① 非流式工具调用请求；② 用 <code>jq</code> 打印 <code>tool_calls</code>；③ 流式请求打印 SSE。可选环境变量 <code>OPENAI_BASE_URL</code> / <code>OPENAI_MODEL</code> 覆盖默认值。</p>
<blockquote>
<p><strong>注意</strong>：真实调用会产生费用；请勿把 Key 写入脚本或提交到仓库。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七总结">七、总结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E4%B8%83%E6%80%BB%E7%BB%93" class="hash-link" aria-label="七、总结的直接链接" title="七、总结的直接链接" translate="no">​</a></h2>
<ol>
<li class=""><strong>Endpoint</strong>：<code>POST {baseUrl}/chat/completions</code>，<code>Authorization: Bearer</code> 鉴权。</li>
<li class=""><strong>请求</strong>：<code>messages</code>（system / user / assistant / tool）+ 采样参数 + <code>tools</code> / <code>tool_choice</code>。</li>
<li class=""><strong>响应</strong>：<code>choices[0].message</code>（<code>content</code> 或 <code>tool_calls</code>）+ <code>usage</code> + <code>finish_reason</code>。</li>
<li class=""><strong>流式</strong>：SSE <code>data:</code> chunk；<code>delta.content</code> 文本、<code>delta.tool_calls</code> 按 <code>index</code> 聚合；<code>[DONE]</code> 结束。</li>
<li class=""><strong>工具</strong>：<code>assistant.tool_calls</code>（<code>arguments</code> 为字符串）→ <code>tool</code> role 按 <code>tool_call_id</code> 回填。</li>
</ol>
<blockquote>
<p>AgentForge 如何把这些字段映射为统一类型，见《OpenAI协议02、AgentForge OpenAI接入核心实践》。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create" target="_blank" rel="noopener noreferrer" class="">OpenAI Chat Completions API（官方参考）</a></p>
<p>[2]. <a href="https://developers.openai.com/api/docs/guides/text" target="_blank" rel="noopener noreferrer" class="">OpenAI 文本生成指南</a></p>
<p>[3]. <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" target="_blank" rel="noopener noreferrer" class="">Server-Sent Events（MDN）</a></p>
<p>[4]. 相关内部文档：[AgentForge OpenAI 接入核心实践](/blog/01、model模型协议层/chatmodel/openai/OpenAI协议02、AgentForge 适配OpenAI接入核心实践)</p>
<br>
<p>整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>模型协议层</category>
            <category>OpenAI</category>
        </item>
        <item>
            <title><![CDATA[AgentForge模型协议层 ChatModel 原理02、Function Calling 工具调用转换与执行扩展实现：Tool 契约 + ToolService 执行循环]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/统一ChatModel协议层02、Function Calling 工具调用转换与执行扩展实现：Tool 契约 + ToolService 执行循环</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/统一ChatModel协议层02、Function Calling 工具调用转换与执行扩展实现：Tool 契约 + ToolService 执行循环</guid>
            <pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[适用版本：release_1.x]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>适用版本：release_1.x<br>
<!-- -->适用模块：<code>agentforge-model-api</code>（<code>cloud.changlu.agentforge.model.tool</code>）+ <code>agentforge-model-core</code><br>
<!-- -->包前缀：<code>cloud.changlu.agentforge.model.tool</code><br>
<!-- -->维护者：长路</p>
</blockquote>
<!-- -->
<p>系列定位：本文是 ChatModel 类别原理课第 02 篇。第 01 篇《ChatModel 核心协议层设计：统一 API 契约封装与
多协议扩展实现》讲了「模型怎么说话」——统一出入参与多协议扩展；本篇讲「模型怎么动手」——Function Calling
从<strong>工具声明</strong>、<strong>协议转换</strong>到<strong>执行回调</strong>的完整闭环。</p>
<blockquote>
<p>全文聚焦 <code>agentforge-model-api</code> 的 <code>tool</code> 包（顶层契约 + <code>spec</code> / <code>execution</code> / <code>error</code> 子包）以及
<code>agentforge-model-core</code> 的默认实现（<code>ToolSpecifications</code> / <code>DefaultToolExecutor</code> / <code>ToolService</code>）。
Provider 侧「<code>tool_calls</code> / <code>tool_use</code> 如何映射进 <code>AiMessage</code>」请见第 01 篇第四章。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="文档导航">文档导航<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E6%96%87%E6%A1%A3%E5%AF%BC%E8%88%AA" class="hash-link" aria-label="文档导航的直接链接" title="文档导航的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>文档</th><th>说明</th></tr></thead><tbody><tr><td>当前文章（Function Calling / Tool）</td><td><code>@Tool</code> 声明、<code>ToolSpecification</code>、反射执行、<code>ToolService</code> 执行循环与错误处理</td></tr><tr><td>[ChatModel原理01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现.md](/blog/01、model模型协议层/chatmodel/统一ChatModel协议层01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现)</td><td>第 01 篇：统一 API 契约、标准 ChatModel 协议、多协议扩展</td></tr><tr><td>[openai/OpenAI协议02、AgentForge OpenAI接入核心实践.md](/blog/01、model模型协议层/chatmodel/openai/OpenAI协议02、AgentForge 适配OpenAI接入核心实践)</td><td>OpenAI Provider 的 <code>tool_calls</code> wire 映射与流式聚合</td></tr><tr><td>[anthropic/Anthropic协议02、AgentForge Anthropic接入核心实践.md](/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议02、AgentForge 适配Anthropic接入核心实践)</td><td>Anthropic Provider 的 <code>tool_use</code> / <code>tool_result</code> wire 映射</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="目录">目录<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E7%9B%AE%E5%BD%95" class="hash-link" aria-label="目录的直接链接" title="目录的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%B8%80%E8%83%8C%E6%99%AF%E6%A8%A1%E5%9E%8B%E8%AF%B4%E6%88%91%E8%A6%81%E8%B0%83%E7%94%A8%E5%B7%A5%E5%85%B7%E4%B9%8B%E5%90%8E%E8%B0%81%E6%9D%A5%E6%89%A7%E8%A1%8C" class="">一、背景：模型说「我要调用工具」之后，谁来执行？</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5tool-%E5%8C%85%E7%94%B1%E5%93%AA%E4%BA%9B%E5%A5%91%E7%BA%A6%E7%BB%84%E6%88%90" class="">二、核心概念：tool 包由哪些契约组成</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF%E4%BB%8E%E6%96%B9%E6%B3%95%E5%A3%B0%E6%98%8E%E5%88%B0%E5%B7%A5%E5%85%B7%E8%A7%84%E8%8C%83" class="">三、实现思路：从方法声明到工具规范</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%9B%9Btoolservice%E6%8E%A8%E7%90%86-%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF" class="">四、ToolService：推理-执行循环</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%BA%94%E5%AE%9E%E6%88%98%E7%94%A8%E4%BE%8B%E7%9C%9F%E5%AE%9E-curl-%E4%B8%8E%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C" class="">五、实战用例：真实 curl 与返回结果</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%85%AD%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95" class="">六、验证测试</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%B8%83%E8%BE%B9%E7%95%8C%E5%85%BC%E5%AE%B9%E4%B8%8E%E6%BC%94%E8%BF%9B" class="">七、边界、兼容与演进</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%85%AB%E6%80%BB%E7%BB%93" class="">八、总结</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="">参考资料</a></li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景模型说我要调用工具之后谁来执行">一、背景：模型说「我要调用工具」之后，谁来执行？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%B8%80%E8%83%8C%E6%99%AF%E6%A8%A1%E5%9E%8B%E8%AF%B4%E6%88%91%E8%A6%81%E8%B0%83%E7%94%A8%E5%B7%A5%E5%85%B7%E4%B9%8B%E5%90%8E%E8%B0%81%E6%9D%A5%E6%89%A7%E8%A1%8C" class="hash-link" aria-label="一、背景：模型说「我要调用工具」之后，谁来执行？的直接链接" title="一、背景：模型说「我要调用工具」之后，谁来执行？的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="11什么是-function-calling核心概念">1.1、什么是 Function Calling（核心概念）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#11%E4%BB%80%E4%B9%88%E6%98%AF-function-calling%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="1.1、什么是 Function Calling（核心概念）的直接链接" title="1.1、什么是 Function Calling（核心概念）的直接链接" translate="no">​</a></h3>
<p>用一句话说：<strong>Function Calling（也叫 Tool Calling）让模型在生成回答时，可以「提出」调用某个外部函数，
由应用程序去执行，再把结果交还模型继续生成。</strong></p>
<p>OpenAI 官方<a href="https://developers.openai.com/api/docs/guides/function-calling" target="_blank" rel="noopener noreferrer" class="">《Function calling》指南</a>对它的
定义是：Function Calling 提供了一种强大而灵活的方式，让模型对接外部系统、访问训练数据之外的数据与动作。</p>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="111三个核心术语">1.1.1、三个核心术语<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#111%E4%B8%89%E4%B8%AA%E6%A0%B8%E5%BF%83%E6%9C%AF%E8%AF%AD" class="hash-link" aria-label="1.1.1、三个核心术语的直接链接" title="1.1.1、三个核心术语的直接链接" translate="no">​</a></h4>
<table><thead><tr><th>术语</th><th>英文</th><th>含义</th></tr></thead><tbody><tr><td>工具 / 函数</td><td>Function / Tool</td><td>我们告诉模型「你可以调用」的一段能力，通常带 JSON-Schema 参数定义</td></tr><tr><td>工具调用</td><td>Function Call / Tool Call</td><td>模型判断需要调用某个工具时返回的结构化响应（含工具名与参数）</td></tr><tr><td>工具结果</td><td>Function Call Output / Tool Call Output</td><td>应用执行工具后产出的结果，可为 JSON / 文本 / 图片，并引用对应的调用 ID</td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：模型本身<strong>不执行</strong>任何工具，它只负责「提出调用」。真正的执行永远发生在应用侧（client tool）
或厂商侧（server tool，见 1.1.3）。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="112五步调用流程">1.1.2、五步调用流程<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#112%E4%BA%94%E6%AD%A5%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="1.1.2、五步调用流程的直接链接" title="1.1.2、五步调用流程的直接链接" translate="no">​</a></h4>
<p>参考 OpenAI <a href="https://developers.openai.com/api/docs/guides/function-calling" target="_blank" rel="noopener noreferrer" class="">《Function calling》指南</a>，Tool
Calling 是应用与模型之间的一段多步对话：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">1. 携带「模型可调用的工具列表」发起第一次请求</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">2. 从模型收到一次 tool call（工具名 + 参数）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">3. 在应用侧执行代码（使用 tool call 的输入）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">4. 把执行结果（tool output）连同历史消息再发给模型</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">5. 收到模型的最终回答（或又一次 tool call）</span><br></div></code></pre></div></div>
<p>这段流程可以循环多次：只要模型还在请求工具，就继续「执行 → 回填 → 再调用」，直到模型给出不再需要工具的
最终回答。这正是 <code>ToolService</code> 要封装的循环。</p>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="113client-tool-vs-server-tool">1.1.3、Client Tool vs Server Tool<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#113client-tool-vs-server-tool" class="hash-link" aria-label="1.1.3、Client Tool vs Server Tool的直接链接" title="1.1.3、Client Tool vs Server Tool的直接链接" translate="no">​</a></h4>
<p>不同厂商对「谁来执行工具」的划分略有不同，Anthropic 在
<a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview" target="_blank" rel="noopener noreferrer" class="">《Tool use with Claude》</a>中明确
区分了两类：</p>
<table><thead><tr><th>类型</th><th>执行方</th><th>典型例子</th><th>协议表现</th></tr></thead><tbody><tr><td>Client Tool</td><td>你的应用</td><td>自定义 <code>get_weather</code></td><td>模型返回 <code>stop_reason: "tool_use"</code> + <code>tool_use</code> 块，应用执行后回 <code>tool_result</code></td></tr><tr><td>Server Tool</td><td>厂商基础设施</td><td>Web Search、Code Execution</td><td>厂商直接执行，结果随同一次响应返回</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：AgentForge 的 <code>tool</code> 包聚焦 <strong>Client Tool</strong>——即「模型提出、应用执行」这条最常见的链路；
Server Tool 对上层而言等同于「模型直接给了结果」，不经过 <code>ToolService</code>。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="114模型为什么会调用工具">1.1.4、模型为什么会「调用工具」<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#114%E6%A8%A1%E5%9E%8B%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E8%B0%83%E7%94%A8%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="1.1.4、模型为什么会「调用工具」的直接链接" title="1.1.4、模型为什么会「调用工具」的直接链接" translate="no">​</a></h4>
<p>模型并不是真的在运行代码，而是在<strong>预测一段结构化输出</strong>。当请求里提供了工具（名称 + 描述 + JSON-Schema
参数）后，模型会根据用户意图决定：</p>
<ul>
<li class="">要不要调用工具（<code>tool_choice</code> 可控制 <code>auto</code> / <code>required</code> / 指定工具）；</li>
<li class="">调用哪一个工具（工具名与描述是关键线索）；</li>
<li class="">用什么参数（按 Schema 生成 JSON）。</li>
</ul>
<p>一篇<a href="https://arxiv.org/abs/2409.18807" target="_blank" rel="noopener noreferrer" class="">工具学习综述（LLM With Tools: A Survey）</a>把这类能力概括为
「将用户指令映射为可执行计划并执行」，其核心挑战正是：<strong>工具调用时机、工具选择的准确性、以及稳健的推理
过程</strong>。这也是 AgentForge 要把「工具声明」规范化为 Schema、把「执行」做成可替换引擎的根本原因。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="12从一个具体场景说起">1.2、从一个具体场景说起<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#12%E4%BB%8E%E4%B8%80%E4%B8%AA%E5%85%B7%E4%BD%93%E5%9C%BA%E6%99%AF%E8%AF%B4%E8%B5%B7" class="hash-link" aria-label="1.2、从一个具体场景说起的直接链接" title="1.2、从一个具体场景说起的直接链接" translate="no">​</a></h3>
<p>在写一个「查天气」的 Agent 时，我们希望用户问「杭州今天天气怎么样？」，模型不要瞎编，而是输出一个
工具调用，让我们去查真实数据，再让模型基于结果回答。第一轮模型返回的内容大致是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">assistant: tool_calls = [{ id: "call_abc123", name: "get_weather", arguments: "{\"city\":\"杭州\"}" }]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">finish_reason = tool_calls</span><br></div></code></pre></div></div>
<p>问题来了：<strong>接下来这几步由谁来做？</strong></p>
<ol>
<li class="">把 <code>arguments</code> 的 JSON 解析成参数，反射调用 <code>getWeather("杭州")</code>；</li>
<li class="">把返回值转成文本，包成一条 <code>tool</code> 结果消息；</li>
<li class="">把「用户问题 + assistant 工具调用 + tool 结果」一起发回模型；</li>
<li class="">反复执行，直到模型不再请求工具。</li>
</ol>
<p>如果每个 Agent 业务都手写这四步，最终会得到一堆重复、易错、难测试的循环代码。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="13如果不封这一层会怎样">1.3、如果不封这一层会怎样？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#13%E5%A6%82%E6%9E%9C%E4%B8%8D%E5%B0%81%E8%BF%99%E4%B8%80%E5%B1%82%E4%BC%9A%E6%80%8E%E6%A0%B7" class="hash-link" aria-label="1.3、如果不封这一层会怎样？的直接链接" title="1.3、如果不封这一层会怎样？的直接链接" translate="no">​</a></h3>
<p>我们在项目里对比过「手写循环」和「给一个执行引擎」两种做法：</p>
<table><thead><tr><th>方案</th><th>优点</th><th>弊端说明</th></tr></thead><tbody><tr><td>业务里手写 4 步循环</td><td>直观、无额外抽象</td><td>解析/反射/回填重复；错误处理各写各的；单测很难</td></tr><tr><td>只提供 <code>@Tool</code> 注解</td><td>声明简单</td><td>仍要业务自己扫描、建 Specification、绑定参数</td></tr><tr><td><strong>Tool 契约 + ToolService（当前选型）</strong></td><td>声明、执行、循环、错误处理全部收敛</td><td>需要一个稳定的工具契约与执行引擎</td></tr></tbody></table>
<p><code>agentforge-model-api</code> 的 <code>tool</code> 包（顶层契约 + <code>spec</code> / <code>execution</code> / <code>error</code> 三个子包）就是为了把这四步标准化：</p>
<blockquote>
<p><strong>重点</strong>：Provider 层解决「模型协议里如何表达工具调用」（wire 映射）；<code>tool</code> 包解决「上层 Agent 如何把
普通 Java 方法变成可被 LLM 调用的工具，并自动驱动多轮执行」。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="14这一层要解决什么">1.4、这一层要解决什么<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#14%E8%BF%99%E4%B8%80%E5%B1%82%E8%A6%81%E8%A7%A3%E5%86%B3%E4%BB%80%E4%B9%88" class="hash-link" aria-label="1.4、这一层要解决什么的直接链接" title="1.4、这一层要解决什么的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><strong>声明简单</strong>：一个 <code>@Tool</code> 注解 + 一个 <code>@P</code> 注解，让普通 Java 方法变成 LLM 可调用工具；</li>
<li class=""><strong>规范可移植</strong>：把方法签名转成 JSON-Schema 风格的 <code>ToolSpecification</code>，与 OpenAI / Anthropic 的
<code>tools[]</code> 天然对接；</li>
<li class=""><strong>执行可替换</strong>：默认反射执行，也允许自定义 <code>ToolExecutor</code>（Lambda 即可）；</li>
<li class=""><strong>循环可控</strong>：内置推理-执行循环、最大轮次、即时返回、幻觉工具与错误处理策略；</li>
<li class=""><strong>厂商无关</strong>：无论底层是 OpenAI 还是 Anthropic，上层只面向 <code>ToolExecutionRequest</code> / <code>ToolExecutor</code> 编程。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="15各厂商对-function-calling-的支持现状">1.5、各厂商对 Function Calling 的支持现状<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#15%E5%90%84%E5%8E%82%E5%95%86%E5%AF%B9-function-calling-%E7%9A%84%E6%94%AF%E6%8C%81%E7%8E%B0%E7%8A%B6" class="hash-link" aria-label="1.5、各厂商对 Function Calling 的支持现状的直接链接" title="1.5、各厂商对 Function Calling 的支持现状的直接链接" translate="no">​</a></h3>
<p><strong>问题引导</strong>：是不是所有大模型厂商都支持「标准」的 Function Calling？</p>
<p>先给结论：<strong>没有跨厂商的唯一标准，但形成了两类事实标准；而且「协议支持」不等于「模型支持」。</strong></p>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="151事实标准openai-兼容阵营">1.5.1、事实标准：OpenAI 兼容阵营<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#151%E4%BA%8B%E5%AE%9E%E6%A0%87%E5%87%86openai-%E5%85%BC%E5%AE%B9%E9%98%B5%E8%90%A5" class="hash-link" aria-label="1.5.1、事实标准：OpenAI 兼容阵营的直接链接" title="1.5.1、事实标准：OpenAI 兼容阵营的直接链接" translate="no">​</a></h4>
<p>OpenAI Chat Completions 的 <code>tools[]</code> / <code>tool_choice</code> / <code>tool_calls[]</code> / <code>role=tool</code> 已成为行业事实标准，
大量厂商直接「兼容」这一格式：</p>
<ul>
<li class="">大模型厂商：DeepSeek、Qwen / DashScope、Moonshot / Kimi、MiniMax 等；</li>
<li class="">推理托管：Groq、Together、Mistral 等；</li>
<li class="">本地 / 网关：<a href="https://docs.ollama.com/capabilities/tool-calling" target="_blank" rel="noopener noreferrer" class="">Ollama</a>、
<a href="https://docs.vllm.ai/en/stable/features/tool_calling/" target="_blank" rel="noopener noreferrer" class="">vLLM</a>、Xinference、LiteLLM 等。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：这也是 AgentForge <code>OpenAiChatModel</code> 可复用的原因——只要实现方遵循 Chat Completions 协议，
换一个 <code>baseUrl</code> 即可接入，无需新增 Provider（见第 01 篇第四章）。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="152各自原生协议对照">1.5.2、各自原生协议对照<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#152%E5%90%84%E8%87%AA%E5%8E%9F%E7%94%9F%E5%8D%8F%E8%AE%AE%E5%AF%B9%E7%85%A7" class="hash-link" aria-label="1.5.2、各自原生协议对照的直接链接" title="1.5.2、各自原生协议对照的直接链接" translate="no">​</a></h4>
<p>并非所有厂商都走 OpenAI 格式，Anthropic 与 Google 都有自己的原生协议，语义等价但 wire 不同：</p>
<table><thead><tr><th>厂商</th><th>工具声明</th><th>工具调用返回</th><th>结果回填</th></tr></thead><tbody><tr><td><a href="https://developers.openai.com/api/docs/guides/function-calling" target="_blank" rel="noopener noreferrer" class="">OpenAI（Chat Completions）</a></td><td><code>tools[].function.parameters</code></td><td><code>tool_calls[]</code></td><td><code>role=tool</code> + <code>tool_call_id</code></td></tr><tr><td><a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview" target="_blank" rel="noopener noreferrer" class="">Anthropic（Messages）</a></td><td><code>tools[].input_schema</code></td><td><code>stop_reason=tool_use</code> + <code>tool_use</code> 块</td><td><code>tool_result</code>（<code>role=user</code> 下）</td></tr><tr><td><a href="https://ai.google.dev/gemini-api/docs/function-calling" target="_blank" rel="noopener noreferrer" class="">Google Gemini</a></td><td><code>functionDeclarations</code>（OpenAPI schema 子集）</td><td><code>functionCall</code></td><td><code>functionResponse</code></td></tr></tbody></table>
<blockquote>
<p><strong>注意</strong>：OpenAI 自身也在演进——较新的 Responses API 对工具的建模（内置工具、tool search 等）与
Chat Completions 并不完全一致；接入时需明确针对哪一套 API。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="153协议支持--模型支持">1.5.3、协议支持 ≠ 模型支持<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#153%E5%8D%8F%E8%AE%AE%E6%94%AF%E6%8C%81--%E6%A8%A1%E5%9E%8B%E6%94%AF%E6%8C%81" class="hash-link" aria-label="1.5.3、协议支持 ≠ 模型支持的直接链接" title="1.5.3、协议支持 ≠ 模型支持的直接链接" translate="no">​</a></h4>
<p>即便协议格式兼容，<strong>真正决定能不能用工具的是模型本身</strong>，差异体现在多个维度：</p>
<table><thead><tr><th>维度</th><th>差异举例</th></tr></thead><tbody><tr><td>是否支持工具调用</td><td>部分旧模型、部分小参数模型不具备该能力</td></tr><tr><td>版本差异</td><td>DeepSeek-R1 早期不支持工具调用，而 <a href="https://api-docs.deepseek.com/guides/tool_calls/" target="_blank" rel="noopener noreferrer" class="">DeepSeek-V3</a> 支持</td></tr><tr><td><code>tool_choice</code> 模式</td><td><code>required</code> / 指定函数在个别实现会被拒绝（DeepSeek 有相关 issue）</td></tr><tr><td>并行工具调用</td><td>是否允许一次返回多个 <code>tool_calls</code> 因模型 / 端点而异</td></tr><tr><td>流式工具增量</td><td>是否支持 <code>delta.tool_calls[]</code> 分片、还是只有最终响应才有</td></tr><tr><td>strict schema</td><td><code>strict</code> / 严格语法约束的支持程度不一</td></tr><tr><td>推理 + 工具</td><td>思考过程（<code>reasoning_content</code>）与工具调用能否同流</td></tr></tbody></table>
<blockquote>
<p><strong>弊端说明</strong>：只验证「wire 兼容」是不够的。同一个 OpenAI 兼容端点，换模型后可能 <code>tool_choice=required</code>
直接报错，或不再返回并行工具调用。接入时应针对具体「模型 + 端点」做能力探测，而不是假设完全等价。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="154对-agentforge-的意义">1.5.4、对 AgentForge 的意义<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#154%E5%AF%B9-agentforge-%E7%9A%84%E6%84%8F%E4%B9%89" class="hash-link" aria-label="1.5.4、对 AgentForge 的意义的直接链接" title="1.5.4、对 AgentForge 的意义的直接链接" translate="no">​</a></h4>
<p>AgentForge 的做法是「内部统一契约 + Provider 适配 + 显式降级」：</p>
<ul>
<li class="">内部只暴露 <code>ToolSpecification</code> / <code>ToolExecutionRequest</code>，不泄露厂商字段；</li>
<li class="">每个 Provider 只做自己协议的映射（OpenAI <code>tool_calls</code>、Anthropic <code>tool_use</code>）；</li>
<li class="">厂商不支持的通用字段，按第 01 篇 2.4 的三种方式处理：映射 / 忽略或显式校验失败 / 走 <code>customParameters</code>。</li>
</ul>
<p>因此「某个厂商或模型不完全支持」不会污染上层——最多是某个 Provider 显式拒绝，而不是让 Agent 主循环崩掉。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="16延伸阅读与资料参考">1.6、延伸阅读与资料参考<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#16%E5%BB%B6%E4%BC%B8%E9%98%85%E8%AF%BB%E4%B8%8E%E8%B5%84%E6%96%99%E5%8F%82%E8%80%83" class="hash-link" aria-label="1.6、延伸阅读与资料参考的直接链接" title="1.6、延伸阅读与资料参考的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><a href="https://developers.openai.com/api/docs/guides/function-calling" target="_blank" rel="noopener noreferrer" class="">OpenAI《Function calling》官方指南</a>：术语定义
（Function / Tool、Tool Call、Tool Output）与五步流程，本文 1.1 节的主要参考；</li>
<li class=""><a href="https://openai.com/index/function-calling-and-other-api-updates/" target="_blank" rel="noopener noreferrer" class="">OpenAI 2023 年 6 月《Function calling and other API updates》</a>：
Function Calling 能力首次公开发布；</li>
<li class=""><a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview" target="_blank" rel="noopener noreferrer" class="">Anthropic《Tool use with Claude》</a>：
Client Tool / Server Tool 的划分，以及 <code>tool_use</code> / <code>tool_result</code> 的完整往返；</li>
<li class=""><a href="https://arxiv.org/abs/2409.18807" target="_blank" rel="noopener noreferrer" class="">arXiv《LLM With Tools: A Survey》(2409.18807)</a>：工具学习的方法与挑战
（调用时机、工具选择、动态规划）；</li>
<li class=""><a href="https://gorilla.cs.berkeley.edu/leaderboard.html" target="_blank" rel="noopener noreferrer" class="">Berkeley Function Calling Leaderboard (BFCL)</a>：衡量模型
工具调用准确性的公开榜单；</li>
<li class=""><a href="https://github.com/langchain4j/langchain4j" target="_blank" rel="noopener noreferrer" class="">LangChain4j</a>：AgentForge <code>tool</code> 包在设计风格上的重要参考。</li>
</ul>
<p>完整链接见文末「参考资料」。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念tool-包由哪些契约组成">二、核心概念：tool 包由哪些契约组成<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5tool-%E5%8C%85%E7%94%B1%E5%93%AA%E4%BA%9B%E5%A5%91%E7%BA%A6%E7%BB%84%E6%88%90" class="hash-link" aria-label="二、核心概念：tool 包由哪些契约组成的直接链接" title="二、核心概念：tool 包由哪些契约组成的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="21包结构与-api--core-划分">2.1、包结构与 api / core 划分<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#21%E5%8C%85%E7%BB%93%E6%9E%84%E4%B8%8E-api--core-%E5%88%92%E5%88%86" class="hash-link" aria-label="2.1、包结构与 api / core 划分的直接链接" title="2.1、包结构与 api / core 划分的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">cloud.changlu.agentforge.model.tool</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── Tool / P / ReturnBehavior / ToolExecutor            # api：核心契约</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── spec</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolSpecification                               # api：name/description/parameters/strict/metadata</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── ToolParameters                                  # api：JSON-Schema 风格参数（纯 Map，无第三方依赖）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── execution</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolExecution                                   # api：一次执行（请求 + 结果 + 耗时）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolExecutionResult                             # api：执行结果值对象（text + 原始对象 + isError）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolService                                     # core：工具注册 + 推理/执行循环</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── DefaultToolExecutor                             # core：@Tool 方法反射执行器</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── ToolExecutionRequestUtil                        # core：arguments JSON -&gt; Map</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── error</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── ToolArgumentsException / ToolExecutionException # api：两类异常</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── ToolArgumentsErrorHandler / ToolExecutionErrorHandler  # api：错误处理器</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── ToolErrorContext                                # api：错误上下文</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── ToolErrorHandlerResult                          # api：可回给 LLM 的错误结果</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>Tool</code> / <code>P</code> / <code>ReturnBehavior</code> / <code>ToolExecutor</code> / <code>spec</code> / <code>execution</code> 的值对象 / <code>error</code>
都在 <strong>api</strong>（契约）；<code>ToolService</code> / <code>DefaultToolExecutor</code> / <code>ToolExecutionRequestUtil</code> /
<code>ToolSpecifications</code> 在 <strong>core</strong>（默认实现）。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="22四类核心契约">2.2、四类核心契约<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#22%E5%9B%9B%E7%B1%BB%E6%A0%B8%E5%BF%83%E5%A5%91%E7%BA%A6" class="hash-link" aria-label="2.2、四类核心契约的直接链接" title="2.2、四类核心契约的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">tool 包契约</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 声明契约：@Tool / @P / ToolSpecification / ToolParameters</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 执行契约：ToolExecutor（execute / executeWithResult）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 结果契约：ToolExecution / ToolExecutionResult / ReturnBehavior</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── 错误契约：ToolArgumentsException / ToolExecutionException + ErrorHandler + ToolErrorHandlerResult</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三实现思路从方法声明到工具规范">三、实现思路：从方法声明到工具规范<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF%E4%BB%8E%E6%96%B9%E6%B3%95%E5%A3%B0%E6%98%8E%E5%88%B0%E5%B7%A5%E5%85%B7%E8%A7%84%E8%8C%83" class="hash-link" aria-label="三、实现思路：从方法声明到工具规范的直接链接" title="三、实现思路：从方法声明到工具规范的直接链接" translate="no">​</a></h2>
<p><strong>问题引导</strong>：一个普通的 Java 方法，怎样一步步变成模型能看懂、框架能执行的「工具」？</p>
<p>整体设计分两步：</p>
<ol>
<li class=""><strong>先对齐标准 Function Calling</strong>：无论 OpenAI、Anthropic 还是 Gemini，模型侧的交互范式都是同一套——
「工具声明 → 工具调用 → 工具结果」。框架首先要能产出模型要求的<strong>工具声明（JSON-Schema）</strong>、接住模型返回的
<strong>工具调用</strong>，并在执行后回填<strong>工具结果</strong>。</li>
<li class=""><strong>再做统一抽象封装</strong>：把「声明」统一为 <code>ToolSpecification</code> / <code>ToolParameters</code>，把「一次调用」统一为
<code>ToolExecutionRequest</code>，把「结果」统一为 <code>ToolExecutionResult(Message)</code>，把「执行」统一为 <code>ToolExecutor</code>。
完成这一步后，本地方法、HTTP、MCP 都只是这一层的<strong>声明来源 / 执行实现</strong>；而最终离模型最近的，就是这份
provider-neutral 的 <strong>Function Tools 声明</strong>。</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">本地 @Tool 方法 / HTTP / MCP   （都只是“声明来源”）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │  ToolSpecifications 扫描 / 各 Factory 构建</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolSpecification + ToolParameters        ← 统一抽象层（provider-neutral）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │  Provider 映射（第 01 篇第四章）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Provider tools[]（OpenAI function.parameters / Anthropic input_schema）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ↑ 离模型最近的 Function Tools 层</span><br></div></code></pre></div></div>
<p><strong>真实落地</strong>：上面这套抽象在代码里能一一对应到具体类：</p>
<table><thead><tr><th>核心思路</th><th>真实落地（代码）</th></tr></thead><tbody><tr><td>工具声明</td><td><code>@Tool</code> / <code>@P</code> → <code>ToolSpecifications</code> → <code>ToolSpecification</code> / <code>ToolParameters</code></td></tr><tr><td>一次调用</td><td><code>ToolExecutionRequest(id, name, arguments)</code></td></tr><tr><td>执行</td><td><code>ToolExecutor</code> / <code>DefaultToolExecutor</code>（本地反射）</td></tr><tr><td>结果回填</td><td><code>ToolExecutionResult</code> → <code>ToolExecutionResultMessage</code></td></tr><tr><td>驱动循环</td><td><code>ToolService.chat(...)</code></td></tr></tbody></table>
<p><strong>最小核心闭环（真实 curl）</strong>：把上面的抽象放进一次真实往返——标准 Function Calling 本质就是「两步请求」：</p>
<p>① 带工具声明请求模型：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [ {"role":"user","content":"杭州今天天气怎么样？"} ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "type":"function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name":"get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "description":"查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": { "type":"object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type":"string","description":"城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"] }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>模型返回工具调用（<code>content</code> 为 <code>null</code>）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_calls"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_abc123"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"杭州\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">62</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">18</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">80</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>② 本地执行后，用 <code>role=tool</code> 回填结果再请求：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role":"user","content":"杭州今天天气怎么样？"},</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role":"assistant","content":null,"tool_calls":[</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        {"id":"call_abc123","type":"function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">         "function":{"name":"get_weather","arguments":"{\"city\":\"杭州\"}"}}]},</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role":"tool","tool_call_id":"call_abc123","content":"杭州今天 22℃，天气晴。"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      { "type":"function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "function": { "name":"get_weather", "description":"查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": { "type":"object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type":"string","description":"城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"] } } }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>模型给出最终回答：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stop"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 22℃，天气晴，适合出行。"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">96</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">12</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">108</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>在 AgentForge 里，②的往返被完整封装：<code>tool_calls</code> → <code>ToolExecutionRequest</code> → <code>ToolExecutor</code> 执行 →
<code>ToolExecutionResultMessage</code> 回填，由 <code>ToolService.chat()</code> 自动驱动，应用无需手写这段循环。</p>
<p><strong>真实使用场景</strong>：这一层落地后，最常见的用法包括——</p>
<ul>
<li class="">查天气、汇率、股价等<strong>实时信息</strong>：模型决定「调什么工具、传什么参数」，本地方法真正去查询；</li>
<li class="">订单 / 账户类<strong>业务操作</strong>：查询订单、发起退款，<code>@Tool</code> 方法直接调用已有的 Service；</li>
<li class=""><strong>计算 / 数据处理</strong>：计算器、单位换算、SQL 查询，把自然语言转成结构化调用；</li>
<li class=""><strong>接入外部系统</strong>：用 <code>HttpToolExecutor</code> / <code>McpToolExecutor</code> 复用同一套 <code>ToolService</code> 循环。</li>
</ul>
<p>完整的真实请求 / 返回示例见第五章「实战用例」。</p>
<p>综上，本章分两部分：<strong>3.1 标准封装</strong>（与厂商、执行位置无关的协议层契约，也是可扩展性的来源）与
<strong>3.2 默认落地</strong>（AgentForge 默认提供的一套本地 Java 工具实现）。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="31标准封装function-calling-协议层抽象与可扩展设计">3.1、标准封装：Function Calling 协议层抽象与可扩展设计<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#31%E6%A0%87%E5%87%86%E5%B0%81%E8%A3%85function-calling-%E5%8D%8F%E8%AE%AE%E5%B1%82%E6%8A%BD%E8%B1%A1%E4%B8%8E%E5%8F%AF%E6%89%A9%E5%B1%95%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="3.1、标准封装：Function Calling 协议层抽象与可扩展设计的直接链接" title="3.1、标准封装：Function Calling 协议层抽象与可扩展设计的直接链接" translate="no">​</a></h3>
<blockquote>
<p>只讲<strong>与厂商、与执行位置都无关的稳定契约</strong>：工具声明如何表达、执行如何扩展、调用与结果如何统一。
本地、HTTP、MCP、自定义执行器都建立在这一层之上。</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="311toolspecification--toolparameters工具声明的统一规范">3.1.1、ToolSpecification / ToolParameters：工具声明的统一规范<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#311toolspecification--toolparameters%E5%B7%A5%E5%85%B7%E5%A3%B0%E6%98%8E%E7%9A%84%E7%BB%9F%E4%B8%80%E8%A7%84%E8%8C%83" class="hash-link" aria-label="3.1.1、ToolSpecification / ToolParameters：工具声明的统一规范的直接链接" title="3.1.1、ToolSpecification / ToolParameters：工具声明的统一规范的直接链接" translate="no">​</a></h4>
<p><code>ToolSpecification</code> 是一次工具声明的不可变模型：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ToolSpecification</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">ToolParameters</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// JSON-Schema 风格</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Boolean</span><span class="token plain"> strict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)">// 是否要求 Provider 严格校验</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><code>ToolParameters</code> 刻意用<strong>纯 <code>Map</code></strong> 承载 JSON-Schema，保持 api 零第三方依赖：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ToolParameters</span><span class="token plain"> parameters </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">ToolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addProperty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// name / type / desc / required</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：OpenAI 把它落到 <code>tools[].function.parameters</code>，Anthropic 落到 <code>tools[].input_schema</code>；
同一份 <code>ToolParameters</code> 不需要为厂商各写一遍。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="312toolexecutor可替换的执行扩展点">3.1.2、ToolExecutor：可替换的执行扩展点<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#312toolexecutor%E5%8F%AF%E6%9B%BF%E6%8D%A2%E7%9A%84%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E7%82%B9" class="hash-link" aria-label="3.1.2、ToolExecutor：可替换的执行扩展点的直接链接" title="3.1.2、ToolExecutor：可替换的执行扩展点的直接链接" translate="no">​</a></h4>
<p><code>ToolExecutor</code> 是「一次工具调用如何执行」的契约，也是整个 Tool 层可扩展的关键：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@FunctionalInterface</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ToolExecutor</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">executeWithResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">String</span><span class="token plain"> text </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>因为它是函数式接口，执行逻辑可以来自任何地方：</p>
<table><thead><tr><th>实现</th><th>说明</th><th>适用场景</th></tr></thead><tbody><tr><td><code>DefaultToolExecutor</code>（默认，本地反射）</td><td>由 <code>@Tool</code> 方法自动生成，绑定参数后反射调用</td><td>绝大多数普通 Java 方法</td></tr><tr><td>自定义 <code>ToolExecutor</code>（Lambda）</td><td>直接实现执行逻辑，绕过反射</td><td>需要动态路由 / 非注解工具</td></tr><tr><td><code>HttpToolExecutor</code>（Agent 层）</td><td>把参数映射成 HTTP 请求</td><td>远程 HTTP 接口</td></tr><tr><td><code>McpToolExecutor</code>（Agent 层）</td><td>转发到 MCP Server</td><td>远程 MCP 工具</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：上层只依赖 <code>ToolExecutor</code> 契约，因此新增一种工具来源（HTTP / MCP / 自定义）无需改动声明层与
协议层。默认的本地实现见 3.2.3。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="313toolexecutionrequest--toolexecutionresult统一调用与结果模型">3.1.3、ToolExecutionRequest / ToolExecutionResult：统一调用与结果模型<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#313toolexecutionrequest--toolexecutionresult%E7%BB%9F%E4%B8%80%E8%B0%83%E7%94%A8%E4%B8%8E%E7%BB%93%E6%9E%9C%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="3.1.3、ToolExecutionRequest / ToolExecutionResult：统一调用与结果模型的直接链接" title="3.1.3、ToolExecutionRequest / ToolExecutionResult：统一调用与结果模型的直接链接" translate="no">​</a></h4>
<p>模型返回的调用统一为 <code>ToolExecutionRequest</code>，执行产出统一为 <code>ToolExecutionResult</code>：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 原始 JSON 字符串</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">boolean</span><span class="token plain"> isError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 原始对象，不发给 LLM</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">     </span><span class="token comment" style="color:rgb(98, 114, 164)">// 发给 LLM 的文本</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">success</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">failure</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Throwable</span><span class="token plain"> cause</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>一次执行连同耗时、原始对象被记录为 <code>ToolExecution</code>：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ToolExecution</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">LocalDateTime</span><span class="token plain"> startTime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">LocalDateTime</span><span class="token plain"> finishTime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">resultText</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">boolean</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">hasFailed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">Duration</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">duration</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>result</code>（原始对象）与 <code>text</code>（回给 LLM）分离，避免把 Java 对象直接塞进协议消息。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="32默认落地本地-java-方法的工具构建封装">3.2、默认落地：本地 Java 方法的工具构建封装<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#32%E9%BB%98%E8%AE%A4%E8%90%BD%E5%9C%B0%E6%9C%AC%E5%9C%B0-java-%E6%96%B9%E6%B3%95%E7%9A%84%E5%B7%A5%E5%85%B7%E6%9E%84%E5%BB%BA%E5%B0%81%E8%A3%85" class="hash-link" aria-label="3.2、默认落地：本地 Java 方法的工具构建封装的直接链接" title="3.2、默认落地：本地 Java 方法的工具构建封装的直接链接" translate="no">​</a></h3>
<blockquote>
<p>讲 AgentForge <strong>默认提供</strong>的一套本地工具实现：用 <code>@Tool</code> / <code>@P</code> 标注方法，自动生成声明，并用反射执行。</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="321tool-与-p把-java-方法声明成工具">3.2.1、@Tool 与 @P：把 Java 方法声明成工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#321tool-%E4%B8%8E-p%E6%8A%8A-java-%E6%96%B9%E6%B3%95%E5%A3%B0%E6%98%8E%E6%88%90%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="3.2.1、@Tool 与 @P：把 Java 方法声明成工具的直接链接" title="3.2.1、@Tool 与 @P：把 Java 方法声明成工具的直接链接" translate="no">​</a></h4>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@interface</span><span class="token plain"> </span><span class="token class-name">Tool</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)">// 工具名，缺省用方法名</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">           </span><span class="token comment" style="color:rgb(98, 114, 164)">// 描述，多个字符串按换行拼接</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">ReturnBehavior</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">returnBehavior</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">ReturnBehavior</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">TO_LLM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@interface</span><span class="token plain"> </span><span class="token class-name">P</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)">// 参数名（javac 未开 -parameters 时尤其有用）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">             </span><span class="token comment" style="color:rgb(98, 114, 164)">// 参数描述（别名）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">       </span><span class="token comment" style="color:rgb(98, 114, 164)">// 参数描述（别名）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>用法：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">value </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询指定城市的当前天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getWeather</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> city</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 22℃，天气晴。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>返回值约定（由 <code>DefaultToolExecutor</code> 执行）：</p>
<ul>
<li class=""><code>String</code> -&gt; 原样返回；</li>
<li class=""><code>void</code> / <code>null</code> -&gt; 字面量 <code>"Success"</code>；</li>
<li class="">其它类型 -&gt; <code>Json.stringify(...)</code> 成 JSON 字符串。</li>
</ul>
<blockquote>
<p><strong>注意</strong>：<code>@P.value()</code> 与 <code>@P.description()</code> 是别名，二者取其一即可；<code>@P.name()</code> 用于覆盖参数名，
建议始终显式写上，避免依赖 <code>-parameters</code> 编译参数。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="322toolspecifications从-tool-方法自动生成-json-schema">3.2.2、ToolSpecifications：从 @Tool 方法自动生成 JSON-Schema<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#322toolspecifications%E4%BB%8E-tool-%E6%96%B9%E6%B3%95%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90-json-schema" class="hash-link" aria-label="3.2.2、ToolSpecifications：从 @Tool 方法自动生成 JSON-Schema的直接链接" title="3.2.2、ToolSpecifications：从 @Tool 方法自动生成 JSON-Schema的直接链接" translate="no">​</a></h4>
<p><code>ToolSpecifications</code>（core）负责把方法签名翻译成规范：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">ToolSpecification</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toolSpecificationFrom</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Method</span><span class="token plain"> method</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toolNameFrom</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Method</span><span class="token plain"> method</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toolSpecificationsFrom</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Object</span><span class="token plain"> objectWithTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toolSpecificationsFrom</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Class</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics operator">?</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> classWithTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">validateSpecifications</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> toolSpecifications</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>类型映射（Java -&gt; JSON-Schema）：</p>
<table><thead><tr><th>Java 类型</th><th>JSON 类型</th></tr></thead><tbody><tr><td><code>String</code></td><td><code>string</code></td></tr><tr><td><code>int/Integer/long/Long/short/Short/byte/Byte</code></td><td><code>integer</code></td></tr><tr><td><code>double/Double/float/Float</code></td><td><code>number</code></td></tr><tr><td><code>boolean/Boolean</code></td><td><code>boolean</code></td></tr><tr><td>枚举</td><td><code>string</code></td></tr><tr><td>数组 / <code>List</code> / <code>Set</code></td><td><code>array</code></td></tr><tr><td><code>Map</code></td><td><code>object</code></td></tr></tbody></table>
<p>扫描时会遍历类层级（父类方法也会纳入），并用「方法签名」去重。</p>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="323defaulttoolexecutor反射执行与参数绑定">3.2.3、DefaultToolExecutor：反射执行与参数绑定<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#323defaulttoolexecutor%E5%8F%8D%E5%B0%84%E6%89%A7%E8%A1%8C%E4%B8%8E%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A" class="hash-link" aria-label="3.2.3、DefaultToolExecutor：反射执行与参数绑定的直接链接" title="3.2.3、DefaultToolExecutor：反射执行与参数绑定的直接链接" translate="no">​</a></h4>
<p><code>DefaultToolExecutor</code> 是默认的本地实现，核心是「把 <code>arguments</code> JSON 绑定到方法参数」，关键步骤：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">Object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> arguments </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">prepareArguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// arguments JSON -&gt; Map -&gt; 按参数名取值</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">method</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">setAccessible</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> method</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">invoke</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>参数绑定会做<strong>轻量类型转换</strong>：<code>String</code> / 数字（含 <code>BigDecimal</code> 兼容）/ <code>boolean</code> / 枚举 / <code>Map</code>；
若必填参数缺失，抛 <code>ToolArgumentsException</code>；若方法内部抛异常，包装成 <code>ToolExecutionException</code>。</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Object</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result </span><span class="token operator">==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Success"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">instanceof</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">Json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stringify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)">// 非字符串统一序列化为 JSON</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>DefaultToolExecutor</code> 会把 <code>InvocationTargetException.getCause()</code> 解包——如果方法抛的是
<code>ToolArgumentsException</code>，则原样向上抛；否则统一包成 <code>ToolExecutionException</code>，避免异常伪装。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="324toolexecutionrequestutilarguments-json---map">3.2.4、ToolExecutionRequestUtil：arguments JSON -&gt; Map<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#324toolexecutionrequestutilarguments-json---map" class="hash-link" aria-label="3.2.4、ToolExecutionRequestUtil：arguments JSON -> Map的直接链接" title="3.2.4、ToolExecutionRequestUtil：arguments JSON -> Map的直接链接" translate="no">​</a></h4>
<p>模型给的 <code>arguments</code> 是<strong>原始 JSON 字符串</strong>，执行前必须解析：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">argumentsAsMap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// 空 / "{}" -&gt; 空 Map</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// 解析失败 -&gt; ToolArgumentsException</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// 解析结果不是 JSON 对象 -&gt; ToolArgumentsException</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>弊端说明</strong>：模型偶尔会产出非法 JSON 或类型不符的 <code>arguments</code>。这类错误被明确归类为
<code>ToolArgumentsException</code>，交由专门的 handler 处理，而不是让它变成难以定位的运行时异常。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="325returnbehavior结果返回行为">3.2.5、ReturnBehavior：结果返回行为<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#325returnbehavior%E7%BB%93%E6%9E%9C%E8%BF%94%E5%9B%9E%E8%A1%8C%E4%B8%BA" class="hash-link" aria-label="3.2.5、ReturnBehavior：结果返回行为的直接链接" title="3.2.5、ReturnBehavior：结果返回行为的直接链接" translate="no">​</a></h4>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">enum</span><span class="token plain"> </span><span class="token class-name">ReturnBehavior</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token constant" style="color:rgb(189, 147, 249)">TO_LLM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">               </span><span class="token comment" style="color:rgb(98, 114, 164)">// 默认：结果回给 LLM，继续下一轮</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token constant" style="color:rgb(189, 147, 249)">IMMEDIATE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 执行后立即返回给调用方，终止循环</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token constant" style="color:rgb(189, 147, 249)">IMMEDIATE_IF_LAST</span><span class="token plain">     </span><span class="token comment" style="color:rgb(98, 114, 164)">// 仅当它是本轮最后一个工具调用时立即返回</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>即时返回判定规则（任意工具出错都会强制再跑一轮）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">[]                                       -&gt; 继续（无工具调用）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[TO_LLM, ...] 含任一 TO_LLM               -&gt; 继续</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[IMMEDIATE] | [IMMEDIATE, IMMEDIATE]     -&gt; 立即返回</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[IMMEDIATE_IF_LAST]                      -&gt; 立即返回</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[.., IMMEDIATE_IF_LAST]（最后一个）       -&gt; 立即返回</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四toolservice推理-执行循环">四、ToolService：推理-执行循环<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%9B%9Btoolservice%E6%8E%A8%E7%90%86-%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF" class="hash-link" aria-label="四、ToolService：推理-执行循环的直接链接" title="四、ToolService：推理-执行循环的直接链接" translate="no">​</a></h2>
<p><code>ToolService</code>（core）是把上面所有契约串起来的执行引擎。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="41注册工具">4.1、注册工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#41%E6%B3%A8%E5%86%8C%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="4.1、注册工具的直接链接" title="4.1、注册工具的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 1) 扫描对象内的全部 @Tool 方法</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Arrays</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">asList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 2) 注册单个工具对象</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 3) 指定方法名</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 4) 直接注册 specification + executor</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">specification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> request </span><span class="token operator">-&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"..."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 5) 批量注册 Map&lt;ToolSpecification, ToolExecutor&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>重复工具名会抛 <code>IllegalArgumentException</code>，保证注册表唯一。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="42chat-主循环">4.2、chat() 主循环<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#42chat-%E4%B8%BB%E5%BE%AA%E7%8E%AF" class="hash-link" aria-label="4.2、chat() 主循环的直接链接" title="4.2、chat() 主循环的直接链接" translate="no">​</a></h3>
<p><strong>问题引导</strong>：模型可能连续多轮要求调用工具，循环怎样才能既自动又安全？</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">ToolChatResult</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">ChatModel</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">1. 携带已注册 tools 调用 ChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">2. 若 aiMessage.hasToolExecutionRequests()：</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     a. 逐个执行工具（找不到 executor -&gt; 幻觉工具策略，默认抛异常）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     b. 把每个 ToolExecutionResult 转成 ToolExecutionResultMessage（id 关联）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     c. 追加到消息列表</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     d. 若 ReturnBehavior 要求立即返回 -&gt; 停止</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     e. 否则用新消息继续下一轮</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">3. 若无工具调用 -&gt; 返回最终 ChatResponse + 全部 ToolExecution</span><br></div></code></pre></div></div>
<ul>
<li class="">默认 <code>maxToolCallingRoundTrips = 100</code>，超过抛 <code>IllegalStateException</code>，防止模型陷入死循环；</li>
<li class="">结果通过 <code>ToolChatResult</code> 返回：<code>finalResponse()</code> / <code>toolExecutions()</code> / <code>intermediateResponses()</code>。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="43参数合并自动把工具注入请求">4.3、参数合并：自动把工具注入请求<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#43%E5%8F%82%E6%95%B0%E5%90%88%E5%B9%B6%E8%87%AA%E5%8A%A8%E6%8A%8A%E5%B7%A5%E5%85%B7%E6%B3%A8%E5%85%A5%E8%AF%B7%E6%B1%82" class="hash-link" aria-label="4.3、参数合并：自动把工具注入请求的直接链接" title="4.3、参数合并：自动把工具注入请求的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">requestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> userParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">DefaultChatRequestParameters</span><span class="token plain"> toolParameters </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ArrayList</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">toolSpecifications</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// 用户参数覆盖非工具字段；已注册工具始终包含</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">merge</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">toolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> userParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：调用方不需要每次手动把 <code>tools()</code> 塞进 <code>ChatRequestParameters</code>；<code>ToolService</code> 会把已注册工具
自动合并进去，同时保留用户对其他参数的覆盖。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="44错误处理">4.4、错误处理<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#44%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86" class="hash-link" aria-label="4.4、错误处理的直接链接" title="4.4、错误处理的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>异常</th><th>触发点</th><th>默认处理</th></tr></thead><tbody><tr><td><code>ToolArgumentsException</code></td><td>参数 JSON 无法解析 / 类型不符 / 缺必填参数</td><td>抛异常（<code>RETHROW</code>）</td></tr><tr><td><code>ToolExecutionException</code></td><td>工具方法执行失败</td><td>以 <code>ToolErrorHandlerResult.text(...)</code> 回给 LLM</td></tr></tbody></table>
<p>两种错误都可以定制：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">argumentsErrorHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-&gt;</span><span class="token plain"> </span><span class="token class-name">ToolErrorHandlerResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">executionErrorHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-&gt;</span><span class="token plain"> </span><span class="token class-name">ToolErrorHandlerResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"工具执行失败，请重试"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>handler 有两种返回方式：</p>
<ul>
<li class="">返回 <code>ToolErrorHandlerResult.text(msg)</code> -&gt; 错误信息作为工具结果回给 LLM，模型看到后可<strong>自我纠正并重试</strong>；</li>
<li class="">直接抛异常 -&gt; 终止整个工具循环，向上传播给调用方。</li>
</ul>
<p>此外还有「幻觉工具」策略：模型请求了未注册的工具时，默认 <code>THROW_ON_HALLUCINATED</code> 直接抛异常，
可替换为任意 <code>Function&lt;ToolExecutionRequest, ToolExecutionResultMessage&gt;</code>。</p>
<blockquote>
<p><strong>注意</strong>：默认把执行失败回给 LLM（<code>DEFAULT_TOOL_EXECUTION_ERROR_HANDLER</code>），是为了让模型有机会纠正
参数或换工具；但参数非法默认直接抛，避免把明显错误当成正常结果继续。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="45与-provider-function-calling-的关系">4.5、与 Provider Function Calling 的关系<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#45%E4%B8%8E-provider-function-calling-%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="4.5、与 Provider Function Calling 的关系的直接链接" title="4.5、与 Provider Function Calling 的关系的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolService.chat()                     &lt;- 上层 Agent 入口（本篇）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatModel / ChatRequest / ChatResponse &lt;- 第 01 篇标准协议</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI / Anthropic Adapter             &lt;- wire 映射（tool_calls / tool_use）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.toolExecutionRequests()      &lt;- 模型给出的工具调用</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolService 执行 -&gt; ToolExecutionResultMessage 回填</span><br></div></code></pre></div></div>
<p>两层解耦：Provider 负责「把协议解析成 <code>ToolExecutionRequest</code>」，<code>ToolService</code> 负责「执行并回填」。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五实战用例真实-curl-与返回结果">五、实战用例：真实 curl 与返回结果<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%BA%94%E5%AE%9E%E6%88%98%E7%94%A8%E4%BE%8B%E7%9C%9F%E5%AE%9E-curl-%E4%B8%8E%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="五、实战用例：真实 curl 与返回结果的直接链接" title="五、实战用例：真实 curl 与返回结果的直接链接" translate="no">​</a></h2>
<p>本节用 OpenAI Function Calling 走一遍完整链路：定义工具 → 第一轮拿到 <code>tool_calls</code> → 本地执行 →
第二轮回填 <code>tool_result</code> → 拿到最终回答，最后演示用 <code>ToolService</code> 一次性完成。</p>
<blockquote>
<p>说明：下方返回值为 <strong>真实协议结构</strong>（<code>id</code>、时间戳、token 数会随实际调用变化）。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="51定义并注册工具">5.1、定义并注册工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#51%E5%AE%9A%E4%B9%89%E5%B9%B6%E6%B3%A8%E5%86%8C%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="5.1、定义并注册工具的直接链接" title="5.1、定义并注册工具的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">value </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询指定城市的当前天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getWeather</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> city</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 22℃，天气晴。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><code>ToolSpecifications</code> 生成的工具声明，以 OpenAI 的 <code>tools[]</code> 为例序列化后等价于：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"getWeather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"查询指定城市的当前天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"parameters"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"properties"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"city"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"required"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="52第一轮模型返回-tool_calls">5.2、第一轮：模型返回 tool_calls<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#52%E7%AC%AC%E4%B8%80%E8%BD%AE%E6%A8%A1%E5%9E%8B%E8%BF%94%E5%9B%9E-tool_calls" class="hash-link" aria-label="5.2、第一轮：模型返回 tool_calls的直接链接" title="5.2、第一轮：模型返回 tool_calls的直接链接" translate="no">​</a></h3>
<p>curl：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "杭州今天天气怎么样？"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "type": "function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type": "string", "description": "城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（<code>content</code> 为 <code>null</code>，模型请求调用工具）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl-9xYzAbC456"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"object"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chat.completion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"created"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1726200100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_abc123"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"杭州\"}"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_calls"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">62</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">18</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">80</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>AgentForge 侧拿到的是 <code>AiMessage</code>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.text() == null</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.hasToolExecutionRequests() == true</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.toolExecutionRequests() == [ToolExecutionRequest(id=call_abc123, name=get_weather, arguments={"city":"杭州"})]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatResponse.finishReason() == TOOL_EXECUTION</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="53本地执行工具">5.3、本地执行工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#53%E6%9C%AC%E5%9C%B0%E6%89%A7%E8%A1%8C%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="5.3、本地执行工具的直接链接" title="5.3、本地执行工具的直接链接" translate="no">​</a></h3>
<p><code>ToolService</code> 内部会做三件事：按工具名找 <code>ToolExecutor</code> → 解析 <code>arguments</code> → 反射执行。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">request.name      = "get_weather"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">request.arguments = "{\"city\":\"杭州\"}"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │  ToolExecutionRequestUtil.argumentsAsMap()</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Map{ city = "杭州" }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │  DefaultToolExecutor.prepareArguments() + coerce()</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">getWeather("杭州")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionResult{ text = "杭州今天 22℃，天气晴。", isError = false }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionResultMessage(id="call_abc123", toolName="get_weather", text="杭州今天 22℃，天气晴。")</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="54第二轮回填-tool-result">5.4、第二轮：回填 tool result<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#54%E7%AC%AC%E4%BA%8C%E8%BD%AE%E5%9B%9E%E5%A1%AB-tool-result" class="hash-link" aria-label="5.4、第二轮：回填 tool result的直接链接" title="5.4、第二轮：回填 tool result的直接链接" translate="no">​</a></h3>
<p>curl（把 assistant 的 <code>tool_calls</code> 与 <code>role=tool</code> 的结果一起发回）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "杭州今天天气怎么样？"},</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "assistant", "content": null, "tool_calls": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        {"id": "call_abc123", "type": "function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">         "function": {"name": "get_weather", "arguments": "{\"city\":\"杭州\"}"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      ]},</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "tool", "tool_call_id": "call_abc123", "content": "杭州今天 22℃，天气晴。"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "type": "function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type": "string", "description": "城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（模型基于工具结果给出最终回答）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl-9xYzAbC789"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"object"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chat.completion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"created"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1726200105</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 22℃，天气晴，适合出行。"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stop"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">96</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">12</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">108</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>role=tool</code> 的 <code>tool_call_id</code> 必须与 assistant 的 <code>tool_calls[].id</code> 一致（这里是
<code>call_abc123</code>）；AgentForge 用 <code>ToolExecutionResultMessage.id</code> 承载这个关联。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="55一次性用-toolservice-完成多轮">5.5、一次性用 ToolService 完成多轮<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#55%E4%B8%80%E6%AC%A1%E6%80%A7%E7%94%A8-toolservice-%E5%AE%8C%E6%88%90%E5%A4%9A%E8%BD%AE" class="hash-link" aria-label="5.5、一次性用 ToolService 完成多轮的直接链接" title="5.5、一次性用 ToolService 完成多轮的直接链接" translate="no">​</a></h3>
<p>上面两轮的手工编排，交给 <code>ToolService.chat()</code> 一行搞定：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ArrayList</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天天气怎么样？"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolChatResult</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">/* parameters = */</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">finalResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 杭州今天 22℃，天气晴，适合出行。</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolExecutions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">size</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)">// 1（本次调用了几次工具）</span><br></div></code></pre></div></div>
<p><code>result.toolExecutions()</code> 里能拿到每一次执行的 <code>request</code> / <code>result</code> / 起止时间，便于日志与可观测性。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="56anthropic-tool_use-对照">5.6、Anthropic tool_use 对照<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#56anthropic-tool_use-%E5%AF%B9%E7%85%A7" class="hash-link" aria-label="5.6、Anthropic tool_use 对照的直接链接" title="5.6、Anthropic tool_use 对照的直接链接" translate="no">​</a></h3>
<p>同一套 <code>ToolService</code> 代码无需改动，底层换成 Anthropic 时，wire 形态不同但语义一致：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI                                      Anthropic</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  assistant.tool_calls[] {id,name,arguments}  assistant.content[] {type:tool_use, id,name,input}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  role=tool, tool_call_id=...                 role=user, content=[{type:tool_result, tool_use_id=...}]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │                                            │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └──────────────► ToolExecutionRequest ◄──────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                              │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                     ToolService 执行</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                              │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                     ToolExecutionResultMessage</span><br></div></code></pre></div></div>
<p>Anthropic 的第二轮回填消息形如：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"user"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_result"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"tool_use_id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"toolu_1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 22℃，天气晴。"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>真实 curl 与流式 <code>input_json_delta</code> 聚合详见
[Anthropic协议02、AgentForge Anthropic接入核心实践.md](/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议02、AgentForge 适配Anthropic接入核心实践)。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="57一键真实验证脚本">5.7、一键真实验证脚本<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#57%E4%B8%80%E9%94%AE%E7%9C%9F%E5%AE%9E%E9%AA%8C%E8%AF%81%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="5.7、一键真实验证脚本的直接链接" title="5.7、一键真实验证脚本的直接链接" translate="no">​</a></h3>
<p>仓库中的验证脚本本身就覆盖了工具调用：</p>
<ul>
<li class="">openai/verify-openai-chat.sh</li>
<li class="">anthropic/verify-anthropic-chat.sh</li>
</ul>
<p>以 OpenAI 为例：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">OPENAI_API_KEY</span><span class="token operator">=</span><span class="token plain">sk-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> openai/verify-openai-chat.sh</span><br></div></code></pre></div></div>
<p>脚本会依次执行「非流式请求 -&gt; jq 解析 <code>tool_calls</code> -&gt; 流式请求观察 <code>data:</code> 帧」。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六验证测试">六、验证测试<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%85%AD%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="六、验证测试的直接链接" title="六、验证测试的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="61测试矩阵">6.1、测试矩阵<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#61%E6%B5%8B%E8%AF%95%E7%9F%A9%E9%98%B5" class="hash-link" aria-label="6.1、测试矩阵的直接链接" title="6.1、测试矩阵的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>模块</th><th>测试类</th><th>覆盖点</th></tr></thead><tbody><tr><td><code>agentforge-model-core</code></td><td><code>DefaultToolExecutorTest</code></td><td>参数绑定与类型转换、<code>String</code>/<code>void</code>/对象返回值转文本、缺参抛 <code>ToolArgumentsException</code>、方法异常包成 <code>ToolExecutionException</code></td></tr><tr><td><code>agentforge-model-core</code></td><td><code>ToolServiceTest</code></td><td>工具注册去重、推理-执行循环、<code>ReturnBehavior</code> 即时返回、幻觉工具策略、两类错误 handler、<code>@Tool</code> 方法 -&gt; <code>ToolSpecification</code> / 类型映射</td></tr><tr><td><code>agentforge-model-openai</code></td><td><code>OpenAiFunctionCallTest</code></td><td><code>tools</code>/<code>tool_choice</code> 序列化、<code>tool_calls</code> 解析、tool result 回流</td></tr><tr><td><code>agentforge-model-anthropic</code></td><td><code>AnthropicToolUseTest</code></td><td><code>tool_use</code> 解析、<code>tool_result</code> 回流、<code>input_schema</code> 序列化</td></tr></tbody></table>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="62关键断言">6.2、关键断言<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#62%E5%85%B3%E9%94%AE%E6%96%AD%E8%A8%80" class="hash-link" aria-label="6.2、关键断言的直接链接" title="6.2、关键断言的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>argumentsAsMap</code>：空串 / <code>"{}"</code> -&gt; 空 Map；非法 JSON -&gt; <code>ToolArgumentsException</code>；</li>
<li class=""><code>DefaultToolExecutor</code>：<code>null</code> 返回值 -&gt; <code>"Success"</code>；对象返回值 -&gt; JSON 字符串；</li>
<li class=""><code>ToolService</code>：工具名重复注册 -&gt; <code>IllegalArgumentException</code>；</li>
<li class="">循环：模型不再请求工具时返回最终响应；超过 <code>maxToolCallingRoundTrips</code> -&gt; <code>IllegalStateException</code>；</li>
<li class="">错误：默认执行失败回给 LLM；参数错误默认抛出。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：所有测试通过 Fake <code>HttpTransport</code> 与「假模型」完成，不依赖真实 API Key；工具执行本身是纯本地逻辑，
可以完全离线断言。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七边界兼容与演进">七、边界、兼容与演进<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E4%B8%83%E8%BE%B9%E7%95%8C%E5%85%BC%E5%AE%B9%E4%B8%8E%E6%BC%94%E8%BF%9B" class="hash-link" aria-label="七、边界、兼容与演进的直接链接" title="七、边界、兼容与演进的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="71已落地能力">7.1、已落地能力<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#71%E5%B7%B2%E8%90%BD%E5%9C%B0%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="7.1、已落地能力的直接链接" title="7.1、已落地能力的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>@Tool</code> / <code>@P</code> 声明式工具定义；</li>
<li class=""><code>ToolSpecifications</code> 自动生成 <code>ToolSpecification</code> / <code>ToolParameters</code>；</li>
<li class=""><code>DefaultToolExecutor</code> 反射执行 + 轻量类型转换；</li>
<li class="">自定义 <code>ToolExecutor</code>（Lambda）；</li>
<li class=""><code>ToolService</code> 推理-执行循环、参数自动注入、最大轮次保护；</li>
<li class=""><code>ReturnBehavior</code>（<code>TO_LLM</code> / <code>IMMEDIATE</code> / <code>IMMEDIATE_IF_LAST</code>）；</li>
<li class="">两类错误 handler、<code>ToolErrorHandlerResult</code> 回错自愈、幻觉工具策略；</li>
<li class=""><code>ToolExecution</code> / <code>ToolExecutionResult</code> 执行结果值对象；</li>
<li class="">与 OpenAI / Anthropic 工具协议对接（wire 映射见第 01 篇第四章）。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="72待补齐能力">7.2、待补齐能力<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#72%E5%BE%85%E8%A1%A5%E9%BD%90%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="7.2、待补齐能力的直接链接" title="7.2、待补齐能力的直接链接" translate="no">​</a></h3>
<ul>
<li class="">更丰富的参数类型绑定（嵌套 POJO、泛型集合的强类型反序列化）；</li>
<li class="">Structured Output / JSON-Schema 运行时校验；</li>
<li class="">工具执行的并发 / 异步（当前循环为顺序执行）；</li>
<li class="">工具级超时与取消（Cancellation）；</li>
<li class="">工具调用审计 / 指标 / trace 标准化；</li>
<li class="">流式场景下 <code>onPartialToolCall(...)</code> 增量回调（当前仅最终响应暴露完整工具调用）。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="73与-langchain4j-的取舍">7.3、与 LangChain4j 的取舍<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#73%E4%B8%8E-langchain4j-%E7%9A%84%E5%8F%96%E8%88%8D" class="hash-link" aria-label="7.3、与 LangChain4j 的取舍的直接链接" title="7.3、与 LangChain4j 的取舍的直接链接" translate="no">​</a></h3>
<ul>
<li class="">借鉴其 <code>@Tool</code> / <code>@P</code> / <code>ToolSpecification</code> / <code>ToolExecutor</code> / <code>ToolService</code> 的简单风格；</li>
<li class="">但<strong>不引入 AI-Service、反射链、补偿/异步机制</strong>，只保留自包含的最小执行引擎；</li>
<li class=""><code>ToolParameters</code> 用纯 <code>Map</code>，不引入 JSON 库依赖，保持 api 轻量。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="74兼容性原则">7.4、兼容性原则<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#74%E5%85%BC%E5%AE%B9%E6%80%A7%E5%8E%9F%E5%88%99" class="hash-link" aria-label="7.4、兼容性原则的直接链接" title="7.4、兼容性原则的直接链接" translate="no">​</a></h3>
<ol>
<li class="">工具契约优先新增默认方法或新类型，避免破坏已有实现；</li>
<li class="">厂商 wire 细节留在 Provider，<code>tool</code> 包保持 Provider-neutral；</li>
<li class="">执行结果 <code>result</code>（原始对象）与 <code>text</code>（回给 LLM）分离；</li>
<li class="">公共 API 保持 Java 8 可编译，默认推荐运行在 JDK 17；</li>
<li class="">错误处理显式可配，默认「执行失败回给 LLM、参数非法抛异常」。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八总结">八、总结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%85%AB%E6%80%BB%E7%BB%93" class="hash-link" aria-label="八、总结的直接链接" title="八、总结的直接链接" translate="no">​</a></h2>
<p>回到开头那个问题：<strong>模型说「我要调用工具」之后，谁来执行？</strong></p>
<p>AgentForge 的答案是：把「声明 → 规范 → 执行 → 循环 → 错误」拆成一组稳定契约，再用 <code>ToolService</code> 串起来——</p>
<ul>
<li class=""><strong>声明</strong>：<code>@Tool</code> / <code>@P</code> 把 Java 方法变成工具；</li>
<li class=""><strong>规范</strong>：<code>ToolSpecifications</code> 生成与厂商无关的 <code>ToolSpecification</code> / <code>ToolParameters</code>；</li>
<li class=""><strong>执行</strong>：<code>ToolExecutor</code> + <code>DefaultToolExecutor</code> 完成参数绑定与反射调用；</li>
<li class=""><strong>循环</strong>：<code>ToolService.chat()</code> 自动驱动多轮工具调用，并用 <code>ReturnBehavior</code> 与最大轮次保证可控；</li>
<li class=""><strong>错误</strong>：区分参数错误与执行错误，可回给 LLM 自愈，也可直接终止。</li>
</ul>
<p>有了这一层，上层 Agent 只需要面向 <code>ToolExecutionRequest</code> / <code>ToolExecutor</code> 编程，就能在 OpenAI、Anthropic
乃至未来任意协议之上复用同一套工具能力。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8202%E3%80%81Function%20Calling%20%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%E8%BD%AC%E6%8D%A2%E4%B8%8E%E6%89%A7%E8%A1%8C%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0%EF%BC%9ATool%20%E5%A5%91%E7%BA%A6%20+%20ToolService%20%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://developers.openai.com/api/docs/guides/function-calling" target="_blank" rel="noopener noreferrer" class="">OpenAI - Function calling guide</a></p>
<p>[2]. <a href="https://openai.com/index/function-calling-and-other-api-updates/" target="_blank" rel="noopener noreferrer" class="">OpenAI - Function calling and other API updates</a></p>
<p>[3]. <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview" target="_blank" rel="noopener noreferrer" class="">Anthropic - Tool use with Claude</a></p>
<p>[4]. <a href="https://claude.com/blog/tool-use-ga" target="_blank" rel="noopener noreferrer" class="">Anthropic - Claude can now use tools</a></p>
<p>[5]. <a href="https://arxiv.org/abs/2409.18807" target="_blank" rel="noopener noreferrer" class="">arXiv - LLM With Tools: A Survey (2409.18807)</a></p>
<p>[6]. <a href="https://gorilla.cs.berkeley.edu/leaderboard.html" target="_blank" rel="noopener noreferrer" class="">Berkeley Function Calling Leaderboard (BFCL)</a></p>
<p>[7]. <a href="https://ai.google.dev/gemini-api/docs/function-calling" target="_blank" rel="noopener noreferrer" class="">Google - Gemini API Function calling</a></p>
<p>[8]. <a href="https://api-docs.deepseek.com/guides/tool_calls/" target="_blank" rel="noopener noreferrer" class="">DeepSeek - Tool Calls</a></p>
<p>[9]. <a href="https://docs.vllm.ai/en/stable/features/tool_calling/" target="_blank" rel="noopener noreferrer" class="">vLLM - Tool Calling</a></p>
<p>[10]. <a href="https://docs.ollama.com/capabilities/tool-calling" target="_blank" rel="noopener noreferrer" class="">Ollama - Tool calling</a></p>
<p>[11]. <a href="https://github.com/langchain4j/langchain4j" target="_blank" rel="noopener noreferrer" class="">LangChain4j - Tools (Function Calling)</a></p>
<p>[12]. <a href="https://json-schema.org/" target="_blank" rel="noopener noreferrer" class="">JSON Schema</a></p>
<p>整理者:长路 创建时间:2026.10.6 更新时间:2026.10.6</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>模型协议层</category>
            <category>ChatModel</category>
            <category>Function Calling</category>
        </item>
        <item>
            <title><![CDATA[AgentForge模型协议层 ChatModel 原理01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现]]></title>
            <link>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/统一ChatModel协议层01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现</link>
            <guid>https://agentforge-tech.github.io/AgentForge/blog/01、model模型协议层/chatmodel/统一ChatModel协议层01、ChatModel 核心协议层设计：统一 API 契约封装与多协议扩展实现</guid>
            <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[适用版本：release_1.x]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>适用版本：release_1.x<br>
<!-- -->适用模块：<code>agentforge-model</code>（核心契约：<code>agentforge-model-api</code>）<br>
<!-- -->包前缀：<code>cloud.changlu.agentforge.model</code><br>
<!-- -->维护者：长路</p>
</blockquote>
<!-- -->
<p>系列定位：本文属于「AgentForge 核心模块设计原理文章系列 / 01、model 模型协议层 / chatmodel」，是
ChatModel 类别原理课的第 01 篇。全文只讲两件事：</p>
<ol>
<li class=""><code>agentforge-model-api</code> 如何用一套 <strong>Provider-neutral 的接口</strong> 封装 ChatModel 的统一出入参；</li>
<li class="">OpenAI、Anthropic 等 <strong>不同协议模型如何基于这套标准 ChatModel 协议扩展落地</strong>。</li>
</ol>
<p>Provider 的底层 wire 协议（字段名、Header、SSE、finish reason）单独放在 <code>openai/</code> 与 <code>anthropic/</code>
目录，避免 Core 设计与厂商协议混杂。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="文档导航">文档导航<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E6%96%87%E6%A1%A3%E5%AF%BC%E8%88%AA" class="hash-link" aria-label="文档导航的直接链接" title="文档导航的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>文档</th><th>说明</th></tr></thead><tbody><tr><td>当前文章（ChatModel 原理01）</td><td><code>agentforge-model-api</code> 接口封装、标准 ChatModel 协议、多协议扩展设计</td></tr><tr><td>[ChatModel原理02、Function Calling 工具调用转换与执行扩展实现：Tool 契约 + ToolService 执行循环.md](/blog/01、model模型协议层/chatmodel/统一ChatModel协议层02、Function Calling 工具调用转换与执行扩展实现：Tool 契约 + ToolService 执行循环)</td><td>第 02 篇：<code>@Tool</code> 声明、工具规范、反射执行与 <code>ToolService</code> 执行循环</td></tr><tr><td><a class="" href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/openai/OpenAI%E5%8D%8F%E8%AE%AE01%E3%80%81OpenAI%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3">openai/OpenAI协议01、OpenAI底层协议快速理解.md</a></td><td>OpenAI Chat Completions 底层协议：请求 / 响应 / 流式 / 工具调用 wire</td></tr><tr><td>[openai/OpenAI协议02、AgentForge OpenAI接入核心实践.md](/blog/01、model模型协议层/chatmodel/openai/OpenAI协议02、AgentForge 适配OpenAI接入核心实践)</td><td>AgentForge OpenAI Provider 映射、Builder、流式聚合、错误与演进</td></tr><tr><td><a class="" href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/anthropic/Anthropic%E5%8D%8F%E8%AE%AE01%E3%80%81Anthropic%E5%BA%95%E5%B1%82%E5%8D%8F%E8%AE%AE%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3">anthropic/Anthropic协议01、Anthropic底层协议快速理解.md</a></td><td>Anthropic Messages 底层协议：system 顶层字段 / content block / SSE / tool_use</td></tr><tr><td>[anthropic/Anthropic协议02、AgentForge Anthropic接入核心实践.md](/blog/01、model模型协议层/chatmodel/anthropic/Anthropic协议02、AgentForge 适配Anthropic接入核心实践)</td><td>AgentForge Anthropic Provider 映射、流式聚合、兼容风险与演进</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="目录">目录<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E7%9B%AE%E5%BD%95" class="hash-link" aria-label="目录的直接链接" title="目录的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%B8%80%E5%B1%82-chatmodel-%E5%8D%8F%E8%AE%AE%E5%B1%82" class="">一、背景：为什么需要一层 ChatModel 协议层？</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E4%B8%80%E5%A5%97%E6%A0%87%E5%87%86-chatmodel-%E5%8D%8F%E8%AE%AE%E9%95%BF%E4%BB%80%E4%B9%88%E6%A0%B7" class="">二、核心概念：一套标准 ChatModel 协议长什么样？</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AFagentforge-model-api-%E5%A6%82%E4%BD%95%E5%B0%81%E8%A3%85%E7%BB%9F%E4%B8%80%E5%87%BA%E5%85%A5%E5%8F%82" class="">三、实现思路：agentforge-model-api 如何封装统一出入参</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%9B%9B%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E4%B8%8D%E5%90%8C%E5%8D%8F%E8%AE%AE%E6%A8%A1%E5%9E%8B%E5%A6%82%E4%BD%95%E5%9F%BA%E4%BA%8E%E6%A0%87%E5%87%86%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95" class="">四、多协议扩展：不同协议模型如何基于标准协议扩展</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%BA%94%E5%AE%9E%E6%88%98%E4%B8%80%E6%AC%A1%E5%AE%8C%E6%95%B4%E7%9A%84%E5%A4%9A%E8%BD%AE%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8" class="">五、实战：一次完整的多轮工具调用</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%85%AD%E5%AE%9E%E6%88%98%E7%94%A8%E4%BE%8B%E7%9C%9F%E5%AE%9E-curl-%E4%B8%8E%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C" class="">六、实战用例：真实 curl 与返回结果</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B8%83%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95" class="">七、验证测试</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%85%AB%E8%BE%B9%E7%95%8C%E5%85%BC%E5%AE%B9%E4%B8%8E%E6%BC%94%E8%BF%9B" class="">八、边界、兼容与演进</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B9%9D%E6%80%BB%E7%BB%93" class="">九、总结</a></li>
<li class=""><a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="">参考资料</a></li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一背景为什么需要一层-chatmodel-协议层">一、背景：为什么需要一层 ChatModel 协议层？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B8%80%E8%83%8C%E6%99%AF%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%B8%80%E5%B1%82-chatmodel-%E5%8D%8F%E8%AE%AE%E5%B1%82" class="hash-link" aria-label="一、背景：为什么需要一层 ChatModel 协议层？的直接链接" title="一、背景：为什么需要一层 ChatModel 协议层？的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="11从一个具体问题说起">1.1、从一个具体问题说起<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#11%E4%BB%8E%E4%B8%80%E4%B8%AA%E5%85%B7%E4%BD%93%E9%97%AE%E9%A2%98%E8%AF%B4%E8%B5%B7" class="hash-link" aria-label="1.1、从一个具体问题说起的直接链接" title="1.1、从一个具体问题说起的直接链接" translate="no">​</a></h3>
<p>在开发 AgentForge 的 Agent 层时，我们遇到了这样一个具体问题：</p>
<blockquote>
<p>上层 ReAct Agent 的业务代码里，今天接的是 OpenAI，明天可能就要接 Anthropic，后天还要接一个
企业内部网关。怎样保证换模型时，Agent 主循环一行代码都不用改？</p>
</blockquote>
<p>如果没有统一抽象，最常见的写法是这样的：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 不推荐：Agent 里直接 new 供应商 SDK</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">OpenAiClient</span><span class="token plain"> client </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">OpenAiClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatCompletionResult</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chatCompletion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">String</span><span class="token plain"> text </span><span class="token operator">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getChoices</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>这段代码把三样东西牢牢绑死了：<strong>供应商 SDK 类型</strong>、<strong>供应商请求/响应结构</strong>、<strong>供应商字段命名</strong>。
一旦要换 Anthropic Messages（它的 system 是顶层字段、工具叫 <code>tool_use</code>、返回是 content block 数组），
Agent 层就得推倒重写。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="12如果直接绑定厂商-sdk-会怎样">1.2、如果直接绑定厂商 SDK 会怎样？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#12%E5%A6%82%E6%9E%9C%E7%9B%B4%E6%8E%A5%E7%BB%91%E5%AE%9A%E5%8E%82%E5%95%86-sdk-%E4%BC%9A%E6%80%8E%E6%A0%B7" class="hash-link" aria-label="1.2、如果直接绑定厂商 SDK 会怎样？的直接链接" title="1.2、如果直接绑定厂商 SDK 会怎样？的直接链接" translate="no">​</a></h3>
<p>把「不抽象」的代价逐条列出来：</p>
<ul>
<li class=""><strong>上层污染</strong>：Agent / Memory / Tool Calling 充斥 <code>if (provider == OPENAI)</code> 之类的分支；</li>
<li class=""><strong>测试困难</strong>：每次单测都要真实 API Key、真实网络，CI 跑不起来；</li>
<li class=""><strong>扩展昂贵</strong>：新增一家模型 = 改一处上层业务，回归范围不可控；</li>
<li class=""><strong>协议差异外泄</strong>：<code>finish_reason</code>、<code>prompt_tokens</code>、<code>tool_calls</code> 这些厂商专属概念渗透到业务代码。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：模型协议层的核心价值，不是「多支持几家模型」，而是 <strong>把厂商差异收敛到一个可替换的边界内</strong>。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="13这一层要达成的四个目标">1.3、这一层要达成的四个目标<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#13%E8%BF%99%E4%B8%80%E5%B1%82%E8%A6%81%E8%BE%BE%E6%88%90%E7%9A%84%E5%9B%9B%E4%B8%AA%E7%9B%AE%E6%A0%87" class="hash-link" aria-label="1.3、这一层要达成的四个目标的直接链接" title="1.3、这一层要达成的四个目标的直接链接" translate="no">​</a></h3>
<p>因此 <code>agentforge-model-api</code> 的定位是：<strong>AgentForge 最底层、最稳定的模型访问抽象层</strong>。它不直接绑定
OpenAI、Anthropic 或任何第三方 Java SDK，只定义统一的请求、响应、消息、流式回调、HTTP 传输和异常边界。</p>
<p>四个设计目标：</p>
<ol>
<li class=""><strong>Provider-neutral</strong>：Agent、Memory、Tool Calling 等上层模块只依赖契约层（<code>agentforge-model-api</code>）
与 core，不依赖任何供应商 SDK；</li>
<li class=""><strong>协议适配下沉</strong>：OpenAI / Anthropic 的字段名、Header、SSE、finish reason 等差异全部由 Provider 模块消化；</li>
<li class=""><strong>Java 8 API 兼容</strong>：默认开发环境可用 JDK 17，但公共 LLM API 避免 Java 9+ 语言特性和集合工厂；</li>
<li class=""><strong>为后续 Agent 能力预留扩展点</strong>：消息类型、<code>customParameters</code>、<code>metadata</code>、<code>HttpTransport</code> 都保留扩展空间。</li>
</ol>
<blockquote>
<p><strong>注意</strong>：这里的 Java 8 是指 <strong>公共 API 的语法与 bytecode 兼容</strong>，并不代表项目只能在 JDK 8 上构建，
内部实现仍推荐运行在 JDK 17。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="14从请求到响应协议层全景">1.4、从请求到响应：协议层全景<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#14%E4%BB%8E%E8%AF%B7%E6%B1%82%E5%88%B0%E5%93%8D%E5%BA%94%E5%8D%8F%E8%AE%AE%E5%B1%82%E5%85%A8%E6%99%AF" class="hash-link" aria-label="1.4、从请求到响应：协议层全景的直接链接" title="1.4、从请求到响应：协议层全景的直接链接" translate="no">​</a></h3>
<p>标准 ChatModel 协议贯穿始终，整条调用链如下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Agent / Application</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatModel / StreamingChatModel          &lt;- 统一入口：一套接口</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatRequest + ChatMessage + ChatRequestParameters   &lt;- 统一入参</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Provider Adapter(OpenAI / Anthropic / ...)          &lt;- 只做协议转换</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">HttpTransport                           &lt;- 统一传输 SPI</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Provider HTTP API  -&gt;  Provider JSON / SSE          &lt;- 厂商差异被隔离在 Provider 内</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatResponse + TokenUsage + FinishReason            &lt;- 统一出参</span><br></div></code></pre></div></div>
<p>一句话概括：<strong>上面看到的永远是 <code>ChatRequest</code> 进、<code>ChatResponse</code> 出；厂商协议只在 Provider 模块内部存在。</strong></p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心概念一套标准-chatmodel-协议长什么样">二、核心概念：一套标准 ChatModel 协议长什么样？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%BA%8C%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E4%B8%80%E5%A5%97%E6%A0%87%E5%87%86-chatmodel-%E5%8D%8F%E8%AE%AE%E9%95%BF%E4%BB%80%E4%B9%88%E6%A0%B7" class="hash-link" aria-label="二、核心概念：一套标准 ChatModel 协议长什么样？的直接链接" title="二、核心概念：一套标准 ChatModel 协议长什么样？的直接链接" translate="no">​</a></h2>
<p>在写代码之前，先回答一个问题：<strong>所谓「标准 ChatModel 协议」，到底由哪些部分构成？</strong></p>
<p>答案可以拆成四层：模块分层、包结构、核心契约、以及 Provider 必须遵守的行为约束。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="21模块分层与依赖方向">2.1、模块分层与依赖方向<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#21%E6%A8%A1%E5%9D%97%E5%88%86%E5%B1%82%E4%B8%8E%E4%BE%9D%E8%B5%96%E6%96%B9%E5%90%91" class="hash-link" aria-label="2.1、模块分层与依赖方向的直接链接" title="2.1、模块分层与依赖方向的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-model</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── agentforge-model-api              # 契约层：接口 + req/vo，零第三方依赖</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── chat.ChatModel / StreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── chat.message.*                # ChatMessage 体系 + Content 多模态基座</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── chat.request.*                # ChatRequest / ChatRequestParameters / ToolChoice</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── chat.response.*               # ChatResponse / StreamingChatResponseHandler / TokenUsage / FinishReason</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── tool / tool.spec / tool.execution / tool.error</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── http.*                        # HttpRequest / HttpResponse / HttpTransport / StreamingHttpResponseHandler</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── exception.ModelException</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── agentforge-model-core             # 无厂商依赖的默认实现与执行引擎</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── chat.request.DefaultChatRequestParameters</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── http.JdkHttpTransport         # 基于 HttpURLConnection</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── internal.json.Json            # 内部 JSON 能力（非公共 API）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── tool.execution.ToolService / DefaultToolExecutor / ToolExecutionRequestUtil</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       tool.spec.ToolSpecifications</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── agentforge-model-openai           # OpenAI Chat Completions 协议实现</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── OpenAiChatModel / OpenAiStreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── OpenAiMessages                # 包内共享 wire helper（非公共 API）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── agentforge-model-anthropic        # Anthropic Messages 协议实现</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── AnthropicChatModel / AnthropicStreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── AnthropicProtocol             # Blocking / Streaming 共享 wire mapping</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── agentforge-model-registry         # 开箱即用的模型工厂</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── LlmFactory / LlmEnum / LlmBasicConfig</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── models.IModel / BaseModel / OpenAiModel / AnthropicModel</span><br></div></code></pre></div></div>
<p>依赖方向严格单向、无环：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">            agentforge-model-api   （契约，无依赖）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    ▲</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ┌───────────┼───────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │           │               │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  model-core   model-openai   model-anthropic</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▲           ▲               ▲</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └───────────┴───────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            agentforge-model-registry</span><br></div></code></pre></div></div>
<table><thead><tr><th>模块</th><th>职责</th><th>是否含厂商 SDK</th></tr></thead><tbody><tr><td><code>agentforge-model-api</code></td><td>稳定契约：接口、req/vo、消息、Tool 契约、HTTP SPI、异常</td><td>否</td></tr><tr><td><code>agentforge-model-core</code></td><td>无厂商依赖的默认实现：参数合并、JDK 传输、JSON、Tool 执行引擎</td><td>否</td></tr><tr><td><code>agentforge-model-openai</code></td><td>OpenAI 协议映射</td><td>否（手写 wire，不引入 SDK）</td></tr><tr><td><code>agentforge-model-anthropic</code></td><td>Anthropic 协议映射</td><td>否（手写 wire，不引入 SDK）</td></tr><tr><td><code>agentforge-model-registry</code></td><td>按配置装配 Provider，对上层提供工厂</td><td>否</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：Core 只定义稳定、Provider-neutral 的 LLM 边界；Provider 模块只负责协议转换，绝不把供应商
SDK 类型泄露到上层。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="22契约层包结构">2.2、契约层包结构<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#22%E5%A5%91%E7%BA%A6%E5%B1%82%E5%8C%85%E7%BB%93%E6%9E%84" class="hash-link" aria-label="2.2、契约层包结构的直接链接" title="2.2、契约层包结构的直接链接" translate="no">​</a></h3>
<p>契约层是本文重点，单独展开：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">cloud.changlu.agentforge.model</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── chat</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ChatModel.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── StreamingChatModel.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── ChatMessage.java / ChatMessageType.java / AbstractTextMessage.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── SystemMessage.java / UserMessage.java / AiMessage.java / CustomMessage.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── ToolExecutionRequest.java / ToolExecutionResultMessage.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── Content.java / ContentType.java / TextContent.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── request</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── ChatRequest.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── ChatRequestParameters.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── ToolChoice.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── response</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── ChatResponse.java / StreamingChatResponseHandler.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── TokenUsage.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       └── FinishReason.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── tool</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── Tool.java / P.java / ReturnBehavior.java / ToolExecutor.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── spec</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── ToolSpecification.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── ToolParameters.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── execution</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── ToolExecution.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── ToolExecutionResult.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── error</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── ToolArgumentsException.java / ToolExecutionException.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       ├── ToolArgumentsErrorHandler.java / ToolExecutionErrorHandler.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│       └── ToolErrorContext.java / ToolErrorHandlerResult.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── http</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── HttpRequest.java / HttpResponse.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── HttpTransport.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── StreamingHttpResponseHandler.java</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── exception</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── ModelException.java</span><br></div></code></pre></div></div>
<p><code>agentforge-model-core</code> 放置默认实现与执行引擎（<strong>不在 api 中</strong>）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">cloud.changlu.agentforge.model</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── chat.request.DefaultChatRequestParameters   # 参数默认实现 + merge</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── http.JdkHttpTransport                       # HttpURLConnection 传输实现</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── internal.json.Json                          # 内部 JSON（非公共 API）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── tool</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── spec.ToolSpecifications                 # @Tool 方法 -&gt; ToolSpecification</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── execution.ToolService / DefaultToolExecutor / ToolExecutionRequestUtil</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>api</code> 是「契约」，<code>core</code> 是「默认实现」。上层依赖 api 拿能力，跑起来时通常引入 core。
千万不要把 <code>JdkHttpTransport</code>、<code>DefaultChatRequestParameters</code>、<code>Json</code> 这些实现类当成公共 API。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="23标准协议由哪些核心契约组成">2.3、标准协议由哪些核心契约组成？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#23%E6%A0%87%E5%87%86%E5%8D%8F%E8%AE%AE%E7%94%B1%E5%93%AA%E4%BA%9B%E6%A0%B8%E5%BF%83%E5%A5%91%E7%BA%A6%E7%BB%84%E6%88%90" class="hash-link" aria-label="2.3、标准协议由哪些核心契约组成？的直接链接" title="2.3、标准协议由哪些核心契约组成？的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">标准 ChatModel 协议</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 入口契约：ChatModel / StreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 入参契约：ChatRequest + ChatRequestParameters（+ ToolChoice）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 消息契约：ChatMessage 体系（System / User / Ai / ToolResult / Custom + Content）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 出参契约：ChatResponse + TokenUsage + FinishReason</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 流式契约：StreamingChatResponseHandler（partial / thinking / complete / error）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── 传输契约：HttpTransport + StreamingHttpResponseHandler</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── 异常契约：ModelException</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三实现思路agentforge-model-api-如何封装统一出入参">三、实现思路：agentforge-model-api 如何封装统一出入参<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B8%89%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AFagentforge-model-api-%E5%A6%82%E4%BD%95%E5%B0%81%E8%A3%85%E7%BB%9F%E4%B8%80%E5%87%BA%E5%85%A5%E5%8F%82" class="hash-link" aria-label="三、实现思路：agentforge-model-api 如何封装统一出入参的直接链接" title="三、实现思路：agentforge-model-api 如何封装统一出入参的直接链接" translate="no">​</a></h2>
<p>本章逐层拆解契约层的接口封装与实现。核心思想是：<strong>入参统一收敛为 <code>ChatRequest</code>，出参统一收敛为
<code>ChatResponse</code>，同步与流式共享同一套消息与响应模型。</strong></p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="31chatmodel同步模型的最低稳定边界">3.1、ChatModel：同步模型的最低稳定边界<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#31chatmodel%E5%90%8C%E6%AD%A5%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%9C%80%E4%BD%8E%E7%A8%B3%E5%AE%9A%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="3.1、ChatModel：同步模型的最低稳定边界的直接链接" title="3.1、ChatModel：同步模型的最低稳定边界的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@FunctionalInterface</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ChatModel</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token plain"> chatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> userMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics operator">?</span><span class="token generics"> </span><span class="token generics keyword" style="color:rgb(189, 147, 249);font-style:italic">extends</span><span class="token generics"> </span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>设计要点：</p>
<ul>
<li class="">Provider <strong>只需要实现一个主入口</strong> <code>ChatResponse chat(ChatRequest)</code>；</li>
<li class="">三个便利方法（单条字符串、变长消息、消息列表）都是 <code>default</code>，最终都收敛成 <code>ChatRequest</code>，
Provider 不需要重复实现多套入口；</li>
<li class=""><code>@FunctionalInterface</code> 让简单的模型/测试实现可以直接用 Lambda。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：便利方法收敛到 <code>ChatRequest</code>，是「一个 Provider 只实现一个方法」的关键。若每个便利方法都
要求 Provider 各写一遍，Provider 数量一多就会失控。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="32streamingchatmodel流式为什么用回调">3.2、StreamingChatModel：流式为什么用回调？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#32streamingchatmodel%E6%B5%81%E5%BC%8F%E4%B8%BA%E4%BB%80%E4%B9%88%E7%94%A8%E5%9B%9E%E8%B0%83" class="hash-link" aria-label="3.2、StreamingChatModel：流式为什么用回调？的直接链接" title="3.2、StreamingChatModel：流式为什么用回调？的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">StreamingChatModel</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token plain"> chatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">StreamingChatResponseHandler</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> userMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">StreamingChatResponseHandler</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">StreamingChatResponseHandler</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">ChatMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics operator">?</span><span class="token generics"> </span><span class="token generics keyword" style="color:rgb(189, 147, 249);font-style:italic">extends</span><span class="token generics"> </span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">StreamingChatResponseHandler</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><strong>为什么不用 <code>Flow</code>、Reactive Streams 或 Reactor？</strong> 我们来对比几种方案：</p>
<table><thead><tr><th>方案</th><th>优点</th><th>弊端说明</th></tr></thead><tbody><tr><td>回调式（当前选型）</td><td>Java 8 兼容、零额外依赖、实现直观</td><td>需要自己处理回调时序与异常收敛</td></tr><tr><td><code>java.util.concurrent.Flow</code></td><td>JDK 9+ 标准</td><td>公共 API 被迫升级到 JDK 9+，破坏 Java 8 兼容</td></tr><tr><td>Reactor / RxJava</td><td>背压、组合算子强大</td><td>最底层协议层强依赖响应式框架，依赖变重</td></tr><tr><td><code>CompletableFuture&lt;Stream&gt;</code></td><td>简单</td><td>流式增量语义表达不自然，取消/错误处理繁琐</td></tr></tbody></table>
<p>最终选择回调式，理由是：<strong>协议层要轻，响应式框架的复杂度不该下沉到最底层。</strong></p>
<p>流式生命周期被规范为：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">开始请求</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├── onPartialThinking(partialThinking)  0..N 次   （推理模型的思考增量，可选）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├── onPartialResponse(partialText)      0..N 次   （可见文本增量）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├── onCompleteResponse(response)        成功时恰好 1 次</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   └── onError(error)                      失败时 1 次</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">StreamingChatResponseHandler</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onPartialResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> partialResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onPartialThinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> partialThinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onCompleteResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatResponse</span><span class="token plain"> completeResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Throwable</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：<code>onPartialThinking</code> 提供默认空实现，专门承接 DeepSeek 风格 <code>reasoning_content</code>、Anthropic
<code>thinking</code> 块等推理增量。这样新增回调也<strong>不会破坏已有实现</strong>（零改动可编译）。</p>
</blockquote>
<p><strong>问题引导</strong>：流式和非流式的结果模型会不会分裂成两套？</p>
<p>不会。<code>onCompleteResponse</code> 返回的是<strong>已经聚合完成的标准 <code>ChatResponse</code></strong>，因此流式与非流式最终复用统一的
响应模型；Provider 负责在流结束前把工具调用增量、token usage、finish reason 全部聚合进 <code>ChatResponse</code>。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="33chatrequest不可变统一入参">3.3、ChatRequest：不可变统一入参<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#33chatrequest%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%BB%9F%E4%B8%80%E5%85%A5%E5%8F%82" class="hash-link" aria-label="3.3、ChatRequest：不可变统一入参的直接链接" title="3.3、ChatRequest：不可变统一入参的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ChatRequest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ChatMessage</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>约束：</p>
<ul>
<li class=""><code>messages</code> 必须至少包含一条消息，否则构造时抛 <code>IllegalArgumentException</code>；</li>
<li class="">Builder 支持 <code>message(单条)</code> 与 <code>messages(集合)</code>；</li>
<li class="">构建后对消息列表做不可变包装（<code>Collections.unmodifiableList</code>）；</li>
<li class=""><code>parameters</code> 允许为空，此时全部使用 Provider Model Builder 的 model-level 默认参数。</li>
</ul>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatRequest</span><span class="token plain"> request </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">SystemMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"You are a Java assistant."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Explain CompletableFuture."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"provider-model"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1024</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="34chatrequestparameters参数契约与合并规则">3.4、ChatRequestParameters：参数契约与合并规则<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#34chatrequestparameters%E5%8F%82%E6%95%B0%E5%A5%91%E7%BA%A6%E4%B8%8E%E5%90%88%E5%B9%B6%E8%A7%84%E5%88%99" class="hash-link" aria-label="3.4、ChatRequestParameters：参数契约与合并规则的直接链接" title="3.4、ChatRequestParameters：参数契约与合并规则的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>Core 字段</th><th>Java 类型</th><th>含义</th></tr></thead><tbody><tr><td><code>modelName()</code></td><td><code>String</code></td><td>Provider 模型 ID</td></tr><tr><td><code>temperature()</code></td><td><code>Double</code></td><td>采样随机度，是否支持由 Provider 决定</td></tr><tr><td><code>maxTokens()</code></td><td><code>Integer</code></td><td>最大输出 Token 数</td></tr><tr><td><code>topP()</code></td><td><code>Double</code></td><td>nucleus sampling 参数</td></tr><tr><td><code>stopSequences()</code></td><td><code>List&lt;String&gt;</code></td><td>停止序列</td></tr><tr><td><code>customParameters()</code></td><td><code>Map&lt;String,Object&gt;</code></td><td>Provider 特有扩展字段</td></tr><tr><td><code>tools()</code></td><td><code>List&lt;ToolSpecification&gt;</code></td><td>本次请求允许模型调用的工具声明</td></tr><tr><td><code>toolChoice()</code></td><td><code>ToolChoice</code></td><td><code>AUTO</code> / <code>NONE</code> / <code>REQUIRED</code> / <code>SPECIFIC</code></td></tr><tr><td><code>toolChoiceName()</code></td><td><code>String</code></td><td><code>SPECIFIC</code> 时指定的工具名</td></tr></tbody></table>
<p>前六个字段是抽象方法，后三个是<strong>接口默认方法</strong>：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Double</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Integer</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Double</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">topP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">stopSequences</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">customParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolSpecification</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">ToolChoice</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toolChoice</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">toolChoiceName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：新增能力优先用<strong>接口默认方法</strong>。这样即便已有第三方实现了 <code>ChatRequestParameters</code>，也能零改动
继续编译。这是契约持续演进而不破坏兼容的关键手法。</p>
</blockquote>
<p>Provider 面对通用参数有三种合法处理方式：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Core 通用参数</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├── Provider 原生支持 -&gt; 映射成供应商字段</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ├── Provider 不支持   -&gt; 忽略或显式校验失败</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   └── Provider 特有能力 -&gt; 通过 customParameters 扩展</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="341参数合并规则">3.4.1、参数合并规则<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#341%E5%8F%82%E6%95%B0%E5%90%88%E5%B9%B6%E8%A7%84%E5%88%99" class="hash-link" aria-label="3.4.1、参数合并规则的直接链接" title="3.4.1、参数合并规则的直接链接" translate="no">​</a></h4>
<p><code>DefaultChatRequestParameters</code>（core 模块）负责 model-level defaults 与 request-level overrides 合并：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Model Builder defaults</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">DefaultChatRequestParameters.merge(defaults, overrides)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▲</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Request overrides</span><br></div></code></pre></div></div>
<p>规则是：<strong>请求级非空值覆盖模型级默认值</strong>，实现为「先 apply defaults，再 apply overrides」，每个字段仅在
非空时写入；<code>customParameters</code> 采用 Map 合并，请求级同名 key 覆盖模型级同名 key。</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">static</span><span class="token plain"> </span><span class="token class-name">DefaultChatRequestParameters</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">merge</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> defaults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">ChatRequestParameters</span><span class="token plain"> overrides</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">Builder</span><span class="token plain"> builder </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">apply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> defaults</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">apply</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> overrides</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>示例：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// model-level</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">OpenAiChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"model-a"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.7</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">2048</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// request-level 只覆盖温度</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 最终: modelName=model-a, maxTokens=2048, temperature=0.1</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="35chatresponse--tokenusage--finishreason统一出参">3.5、ChatResponse / TokenUsage / FinishReason：统一出参<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#35chatresponse--tokenusage--finishreason%E7%BB%9F%E4%B8%80%E5%87%BA%E5%8F%82" class="hash-link" aria-label="3.5、ChatResponse / TokenUsage / FinishReason：统一出参的直接链接" title="3.5、ChatResponse / TokenUsage / FinishReason：统一出参的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">AiMessage</span><span class="token plain"> aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">TokenUsage</span><span class="token plain"> tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">FinishReason</span><span class="token plain"> finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<table><thead><tr><th>字段</th><th>是否必须</th><th>说明</th></tr></thead><tbody><tr><td><code>aiMessage</code></td><td>是</td><td>最终模型文本 / 思考 / 工具调用消息</td></tr><tr><td><code>tokenUsage</code></td><td>否</td><td>标准化 Token 统计</td></tr><tr><td><code>finishReason</code></td><td>否</td><td>标准化结束原因</td></tr><tr><td><code>metadata</code></td><td>否</td><td>保留 Provider 原始但通用层不需要理解的元数据</td></tr></tbody></table>
<blockquote>
<p><strong>重点</strong>：Provider 的原始 JSON 不直接向上泄露，但关键 Provider 信息可以保留到 <code>metadata</code> 中。例如
OpenAI 保存 <code>id</code> / <code>model</code> / <code>created</code>，Anthropic 保存 <code>id</code> / <code>model</code> / <code>type</code>。</p>
</blockquote>
<p>TokenUsage 统一三字段：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">TokenUsage</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">long</span><span class="token plain"> inputTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">long</span><span class="token plain"> outputTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">long</span><span class="token plain"> totalTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI:     prompt_tokens -&gt; inputTokens, completion_tokens -&gt; outputTokens, total_tokens -&gt; totalTokens</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Anthropic:  input_tokens  -&gt; inputTokens, output_tokens    -&gt; outputTokens, input + output -&gt; totalTokens</span><br></div></code></pre></div></div>
<p>FinishReason 统一枚举：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">enum</span><span class="token plain"> </span><span class="token class-name">FinishReason</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">STOP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">LENGTH</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">TOOL_EXECUTION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">CONTENT_FILTER</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">OTHER</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI stop/length/tool_calls/content_filter -&gt; STOP/LENGTH/TOOL_EXECUTION/CONTENT_FILTER</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Anthropic end_turn|stop_sequence/max_tokens/tool_use -&gt; STOP/LENGTH/TOOL_EXECUTION</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="36chatmessage消息体系">3.6、ChatMessage：消息体系<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#36chatmessage%E6%B6%88%E6%81%AF%E4%BD%93%E7%B3%BB" class="hash-link" aria-label="3.6、ChatMessage：消息体系的直接链接" title="3.6、ChatMessage：消息体系的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ChatMessage</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">ChatMessageType</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">UnsupportedOperationException</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"Message type "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">" does not expose a text payload"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>消息类型由 <code>ChatMessageType</code> 枚举统一登记，并绑定到具体实现类：</p>
<table><thead><tr><th>类型</th><th>实现类</th><th>当前语义</th></tr></thead><tbody><tr><td><code>SYSTEM</code></td><td><code>SystemMessage</code></td><td>系统指令（继承 <code>AbstractTextMessage</code>）</td></tr><tr><td><code>USER</code></td><td><code>UserMessage</code></td><td>用户输入，可携带 <code>name</code> 与多模态 <code>List&lt;Content&gt;</code></td></tr><tr><td><code>AI</code></td><td><code>AiMessage</code></td><td>模型输出 / 历史助手消息，可携带 <code>thinking</code> 与 <code>toolExecutionRequests</code></td></tr><tr><td><code>TOOL_EXECUTION_RESULT</code></td><td><code>ToolExecutionResultMessage</code></td><td>工具执行结果</td></tr><tr><td><code>CUSTOM</code></td><td><code>CustomMessage</code></td><td>Provider 特有消息</td></tr></tbody></table>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="361aimessage">3.6.1、AiMessage<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#361aimessage" class="hash-link" aria-label="3.6.1、AiMessage的直接链接" title="3.6.1、AiMessage的直接链接" translate="no">​</a></h4>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">AiMessage</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">implements</span><span class="token plain"> </span><span class="token class-name">ChatMessage</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> thinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">ToolExecutionRequest</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> toolExecutionRequests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> attributes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>text()</code> 允许为 <code>null</code>（纯工具调用时）；</li>
<li class=""><code>thinking()</code> 保留推理内容字段位；</li>
<li class=""><code>hasToolExecutionRequests()</code> / <code>toolExecutionRequests()</code> 暴露工具调用；</li>
<li class=""><code>attributes()</code> 提供 Provider 扩展位，防御性复制为只读 Map。</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="362usermessage-与-content-多模态基座">3.6.2、UserMessage 与 Content 多模态基座<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#362usermessage-%E4%B8%8E-content-%E5%A4%9A%E6%A8%A1%E6%80%81%E5%9F%BA%E5%BA%A7" class="hash-link" aria-label="3.6.2、UserMessage 与 Content 多模态基座的直接链接" title="3.6.2、UserMessage 与 Content 多模态基座的直接链接" translate="no">​</a></h4>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">UserMessage</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">implements</span><span class="token plain"> </span><span class="token class-name">ChatMessage</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">Content</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> contents</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>name</code> 可为空，Provider 可忽略；</li>
<li class=""><code>contents</code> 至少一条，构建后不可变；</li>
<li class=""><code>Content</code> 是多模态内容基接口，<code>type()</code> 返回 <code>ContentType</code>；当前只落地 <code>TextContent</code>（<code>ContentType.TEXT</code>），
后续 <code>ImageContent</code> / <code>AudioContent</code> / <code>VideoContent</code> / <code>PdfFileContent</code> 可<strong>在不改消息语义</strong>的前提下扩展。</li>
</ul>
<p>保留的历史兼容访问器：</p>
<table><thead><tr><th>方法</th><th>语义</th></tr></thead><tbody><tr><td><code>text()</code></td><td>单一 <code>TextContent</code> 时返回文本；否则抛 <code>UnsupportedOperationException</code></td></tr><tr><td><code>hasSingleText()</code></td><td>是否只有一条 <code>TextContent</code></td></tr><tr><td><code>singleText()</code></td><td>只有一条 <code>TextContent</code> 时返回，否则抛异常</td></tr></tbody></table>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"alice"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">TextContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"describe this"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">TextContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"..."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"alice"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Arrays</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">Content</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token function" style="color:rgb(80, 250, 123)">asList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">TextContent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"hi"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="363工具消息与自定义消息">3.6.3、工具消息与自定义消息<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#363%E5%B7%A5%E5%85%B7%E6%B6%88%E6%81%AF%E4%B8%8E%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B6%88%E6%81%AF" class="hash-link" aria-label="3.6.3、工具消息与自定义消息的直接链接" title="3.6.3、工具消息与自定义消息的直接链接" translate="no">​</a></h4>
<ul>
<li class=""><code>ToolExecutionRequest</code>：模型发起的一次工具调用，字段 <code>id</code> / <code>name</code> / <code>arguments</code>（原始 JSON 字符串）；</li>
<li class=""><code>ToolExecutionResultMessage</code>：工具执行结果，字段 <code>id</code> / <code>toolName</code> / <code>text</code> / <code>isError</code> / <code>attributes</code>，
<code>id</code> 必须与被调用的 <code>ToolExecutionRequest.id()</code> 一致，用于关联；</li>
<li class=""><code>CustomMessage</code>：承载 Provider 特有、框架无法预定义结构的消息，只暴露不可变的 <code>attributes()</code>。
Provider 若不支持应显式拒绝，而不是静默降级为普通 user message。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="37httptransport传输与协议解耦">3.7、HttpTransport：传输与协议解耦<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#37httptransport%E4%BC%A0%E8%BE%93%E4%B8%8E%E5%8D%8F%E8%AE%AE%E8%A7%A3%E8%80%A6" class="hash-link" aria-label="3.7、HttpTransport：传输与协议解耦的直接链接" title="3.7、HttpTransport：传输与协议解耦的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">HttpTransport</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">HttpResponse</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">HttpRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throws</span><span class="token plain"> </span><span class="token class-name">IOException</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">executeStreaming</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">HttpRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">StreamingHttpResponseHandler</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">onError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">UnsupportedOperationException</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token string" style="color:rgb(255, 121, 198)">"Streaming HTTP is not supported by "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getClass</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>Provider 只依赖 <code>HttpTransport</code>，因此可以：</p>
<ul>
<li class="">默认使用 <code>JdkHttpTransport</code>（基于 <code>HttpURLConnection</code>）；</li>
<li class="">测试时注入 Fake Transport（这是不依赖真实 API Key 的关键）；</li>
<li class="">后续适配 OkHttp、Apache HttpClient 或企业内部网关；</li>
<li class="">完全不修改 <code>ChatModel</code> API。</li>
</ul>
<p>底层流处理接口只认识 HTTP 行，<strong>不解析 SSE</strong>：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">StreamingHttpResponseHandler</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onOpen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> statusCode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">List</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onLine</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onComplete</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Throwable</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">HTTP line / SSE event</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Provider Stream Parser     &lt;- 厂商差异在这里消化</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">partial text / thinking / ChatResponse</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：SSE 是供应商协议的一部分，必须由 OpenAI / Anthropic Provider 自己解析。因此未来 Anthropic
SSE 即使事件格式与 OpenAI 完全不同，也不需要修改 Core。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="38modelexception-与-internaljsonjson">3.8、ModelException 与 internal.json.Json<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#38modelexception-%E4%B8%8E-internaljsonjson" class="hash-link" aria-label="3.8、ModelException 与 internal.json.Json的直接链接" title="3.8、ModelException 与 internal.json.Json的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ModelException</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">extends</span><span class="token plain"> </span><span class="token class-name">RuntimeException</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">Integer</span><span class="token plain"> statusCode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> responseBody</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>它既可以表达 HTTP 4xx / 5xx，也可以表达网络 IOException、JSON 解析失败、Provider 响应结构不符合预期。
HTTP 非 2xx 时保留 <code>message</code> / <code>statusCode</code> / <code>responseBody</code>，便于后续统一做 retry、rate-limit、日志和可观测性。</p>
<p>JSON 能力放在 <code>internal</code> 包中，意味着它<strong>不是 AgentForge 对外公共 API</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Java Map/List -&gt; JSON String</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">JSON String    -&gt; Map/List</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：未来即使内部替换 JSON 实现，也不应影响 <code>ChatModel</code>、<code>ChatRequest</code>、<code>ChatResponse</code> 等公共契约。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四多协议扩展不同协议模型如何基于标准协议扩展">四、多协议扩展：不同协议模型如何基于标准协议扩展<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%9B%9B%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E4%B8%8D%E5%90%8C%E5%8D%8F%E8%AE%AE%E6%A8%A1%E5%9E%8B%E5%A6%82%E4%BD%95%E5%9F%BA%E4%BA%8E%E6%A0%87%E5%87%86%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95" class="hash-link" aria-label="四、多协议扩展：不同协议模型如何基于标准协议扩展的直接链接" title="四、多协议扩展：不同协议模型如何基于标准协议扩展的直接链接" translate="no">​</a></h2>
<p>这是本文的核心。标准协议定好之后，OpenAI、Anthropic 以及未来任意协议，如何基于它扩展？</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="41扩展铁律">4.1、扩展铁律<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#41%E6%89%A9%E5%B1%95%E9%93%81%E5%BE%8B" class="hash-link" aria-label="4.1、扩展铁律的直接链接" title="4.1、扩展铁律的直接链接" translate="no">​</a></h3>
<ol>
<li class=""><strong>只新增 Provider 模块</strong>，不修改 <code>agentforge-model-api</code> 的既有契约；</li>
<li class=""><strong>只实现 <code>ChatModel</code> / <code>StreamingChatModel</code></strong>，不重新定义请求/响应类型；</li>
<li class=""><strong>厂商 wire 细节必须留在 Provider 模块内部</strong>（包私有 helper）；</li>
<li class="">需要新增通用能力时，优先给接口加<strong>默认方法</strong>；</li>
<li class="">厂商私有字段走 <code>customParameters</code> / <code>metadata</code> / <code>attributes</code>，不污染 Core。</li>
</ol>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="42provider-模块骨架">4.2、Provider 模块骨架<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#42provider-%E6%A8%A1%E5%9D%97%E9%AA%A8%E6%9E%B6" class="hash-link" aria-label="4.2、Provider 模块骨架的直接链接" title="4.2、Provider 模块骨架的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-model-xxx</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── pom.xml                       # 依赖 agentforge-model-api（+ core）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── src/main/java/.../xxx</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── XxxChatModel              implements ChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── XxxStreamingChatModel     implements StreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── XxxProtocol / XxxMessages # 包私有 wire helper（Blocking/Streaming 共享）</span><br></div></code></pre></div></div>
<p>现有模块的 pom 依赖：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-model-openai     -&gt; agentforge-model-api + agentforge-model-core</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">agentforge-model-anthropic  -&gt; agentforge-model-api + agentforge-model-core</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="43实战代码五步实现一个-provider">4.3、实战代码：五步实现一个 Provider<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#43%E5%AE%9E%E6%88%98%E4%BB%A3%E7%A0%81%E4%BA%94%E6%AD%A5%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA-provider" class="hash-link" aria-label="4.3、实战代码：五步实现一个 Provider的直接链接" title="4.3、实战代码：五步实现一个 Provider的直接链接" translate="no">​</a></h3>
<p>无论什么协议，实现过程固定为五步：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">1. merge 默认参数和请求参数</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">2. ChatRequest -&gt; Provider Request JSON</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">3. HttpTransport 执行请求</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">4. Provider Response JSON -&gt; ChatResponse</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">5. Provider Error -&gt; ModelException</span><br></div></code></pre></div></div>
<p>以 <code>OpenAiChatModel</code> 为骨架：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">final</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">OpenAiChatModel</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">implements</span><span class="token plain"> </span><span class="token class-name">ChatModel</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">ChatResponse</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token plain"> chatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 1. merge</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">DefaultChatRequestParameters</span><span class="token plain"> parameters </span><span class="token operator">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">merge</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">defaultParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> chatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">requireModelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// 2. ChatRequest -&gt; Provider Request JSON</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">Map</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token generics class-name">String</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token generics"> </span><span class="token generics class-name">Object</span><span class="token generics punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> payload </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildPayload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">HttpRequest</span><span class="token plain"> request </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">HttpRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">baseUrl </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/chat/completions"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">header</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"application/json"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">stringify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 3. execute</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">HttpResponse</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> httpTransport</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">isSuccessful</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ModelException</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OpenAI request failed with HTTP "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token operator">+</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">statusCode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">statusCode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 4. Provider Response JSON -&gt; ChatResponse</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">parseResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">IOException</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token comment" style="color:rgb(98, 114, 164)">// 5. Provider Error -&gt; ModelException</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ModelException</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OpenAI request failed"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>Provider Builder 负责持有：<code>baseUrl</code> / <code>apiKey</code> / <code>anthropicVersion</code> / <code>modelName</code> / 采样参数 /
<code>customHeaders</code> / <code>httpTransport</code> / 超时，并在构造时把这些参数收进 model-level 的
<code>DefaultChatRequestParameters</code>。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="44请求侧映射对比">4.4、请求侧映射对比<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#44%E8%AF%B7%E6%B1%82%E4%BE%A7%E6%98%A0%E5%B0%84%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="4.4、请求侧映射对比的直接链接" title="4.4、请求侧映射对比的直接链接" translate="no">​</a></h3>
<p><strong>问题引导</strong>：OpenAI 和 Anthropic 差异到底有多大？看这张对照表就清楚了。</p>
<table><thead><tr><th>Core</th><th>OpenAI (Chat Completions)</th><th>Anthropic (Messages)</th></tr></thead><tbody><tr><td><code>SystemMessage</code></td><td><code>messages[].role=system</code></td><td>顶层 <code>system</code> 字段（多个 system 用空行拼接）</td></tr><tr><td><code>UserMessage</code>（单文本）</td><td><code>messages[].role=user, content=String</code></td><td><code>messages[].role=user, content=String</code></td></tr><tr><td><code>UserMessage</code>（多 Content）</td><td><code>content=[{type:text,text}]</code></td><td><code>content=[{type:text,text}]</code></td></tr><tr><td><code>AiMessage</code>（含工具调用）</td><td><code>role=assistant</code> + <code>tool_calls[]</code></td><td><code>role=assistant</code> + <code>content=[{type:tool_use,...}]</code></td></tr><tr><td><code>ToolExecutionResultMessage</code></td><td><code>role=tool</code> + <code>tool_call_id</code></td><td><code>role=user</code> + <code>content=[{type:tool_result,...}]</code></td></tr><tr><td><code>customParameters</code></td><td>直接铺到 payload 顶层</td><td>直接铺到 payload 顶层</td></tr><tr><td><code>tools</code></td><td><code>tools[].function.parameters</code></td><td><code>tools[].input_schema</code></td></tr><tr><td><code>toolChoice</code></td><td><code>"auto"/"none"/"required"/{type:function,...}</code></td><td><code>{type:auto/none/any/tool,...}</code></td></tr><tr><td><code>temperature/maxTokens/topP/stop</code></td><td><code>temperature/max_tokens/top_p/stop</code></td><td><code>temperature/max_tokens/top_p/stop_sequences</code></td></tr></tbody></table>
<p>协议差异点，正是扩展设计要隔离的地方：</p>
<ul>
<li class=""><strong>system 位置不同</strong>：OpenAI 放在消息数组里，Anthropic 提为顶层字段；</li>
<li class=""><strong>tool result 角色不同</strong>：OpenAI 是 <code>role=tool</code>，Anthropic 是 <code>role=user</code> 下的 <code>tool_result</code> 块；</li>
<li class=""><strong>tool_use 入参不同</strong>：OpenAI <code>arguments</code> 是 JSON 字符串，Anthropic <code>input</code> 是对象，需要序列化对齐。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="45响应侧映射对比">4.5、响应侧映射对比<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#45%E5%93%8D%E5%BA%94%E4%BE%A7%E6%98%A0%E5%B0%84%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="4.5、响应侧映射对比的直接链接" title="4.5、响应侧映射对比的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI assistant message                 Anthropic assistant message</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  tool_calls[] {id,type,                   content[] {type:"tool_use",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    function{name,arguments}}                id,name,input{...}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │                                        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼                                        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ToolExecutionRequest                     ToolExecutionRequest</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └──────────────┬─────────────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                       ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            AiMessage.toolExecutionRequests()</span><br></div></code></pre></div></div>
<p>要点：</p>
<ul>
<li class="">OpenAI <code>finish_reason=tool_calls</code>（旧版 <code>function_call</code>）与 Anthropic <code>stop_reason=tool_use</code>
统一映射为 <code>FinishReason.TOOL_EXECUTION</code>；</li>
<li class="">纯工具调用时 <code>content</code> 可能为 <code>null</code>，此时 <code>AiMessage.text()</code> 返回 <code>null</code>；</li>
<li class="">Anthropic <code>tool_use.input</code> 是对象，序列化成 JSON 字符串存入 <code>arguments</code>，与 OpenAI 对齐；</li>
<li class="">原始 <code>id</code> / <code>model</code> / <code>created</code> / <code>type</code> 进 <code>ChatResponse.metadata</code>。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="46流式聚合">4.6、流式聚合<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#46%E6%B5%81%E5%BC%8F%E8%81%9A%E5%90%88" class="hash-link" aria-label="4.6、流式聚合的直接链接" title="4.6、流式聚合的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">HttpTransport.executeStreaming(request, state)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │  onOpen / onLine / onComplete / onError</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Provider Stream Parser（实现 StreamingHttpResponseHandler）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── 文本增量 -&gt; handler.onPartialResponse(...)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ├── 思考增量 -&gt; handler.onPartialThinking(...)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        └── 工具增量 -&gt; 内部按 index 聚合</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼ onCompleteResponse(聚合后的 ChatResponse)</span><br></div></code></pre></div></div>
<p><strong>文本与思考实时回调，工具调用只在最终响应暴露</strong>，与 LangChain4j 保持一致。</p>
<p>OpenAI <code>delta.tool_calls[]</code> 按 <code>index</code> 分桶：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">chunk 1: {index:0, id:"call_1", function:{name:"getWeather", arguments:""}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">chunk 2: {index:0, function:{arguments:"{\"city\":"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">chunk 3: {index:0, function:{arguments:"\"hangzhou\"}"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼  merge by index</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionRequest(id=call_1, name=getWeather, arguments={"city":"hangzhou"})</span><br></div></code></pre></div></div>
<ul>
<li class="">多数 OpenAI 兼容端点带 <code>index</code>；当 <code>index</code> 缺省时，以「出现新的 <code>id</code>」作为新调用起点做兜底分桶；</li>
<li class="">同一 <code>index</code> 的多个 <code>arguments</code> 片段按到达顺序拼接，原始 JSON 文本不重新格式化。</li>
</ul>
<p>Anthropic 以 <code>content_block_start</code>（<code>tool_use</code>）开块：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">content_block_start (index=1, tool_use, id=toolu_1, name=get_weather)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">content_block_delta (index=1, input_json_delta: '{"city":')</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">content_block_delta (index=1, input_json_delta: '"hangzhou"}')</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼  accumulate by content block index</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolExecutionRequest(id=toolu_1, name=get_weather, arguments={"city":"hangzhou"})</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="47共享-wire-helper">4.7、共享 wire helper<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#47%E5%85%B1%E4%BA%AB-wire-helper" class="hash-link" aria-label="4.7、共享 wire helper的直接链接" title="4.7、共享 wire helper的直接链接" translate="no">​</a></h3>
<p>每个 Provider 建议抽出一个<strong>包私有</strong>的 wire helper，供 Blocking / Streaming 复用：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAiMessages（package-private, final）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── serialize(messages)          ChatMessage -&gt; OpenAI messages[]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── serializeTools(tools)        ToolSpecification -&gt; tools[]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── toolChoice(parameters)       ToolChoice -&gt; tool_choice</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── parseToolCalls(toolCalls)    tool_calls[] -&gt; ToolExecutionRequest</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── extractContent(content)      String | content[] -&gt; text</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AnthropicProtocol（package-private, final）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── collectSystemMessages(messages)  system 顶层字段</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── serializeMessages(messages)      messages[]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── serializeTools(tools)            input_schema</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── extractToolUses(contentBlocks)   tool_use -&gt; ToolExecutionRequest</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── extractText(contentBlocks)       text block -&gt; text</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：它是 Provider 的实现细节，<strong>不是公共 API</strong>，因此不会污染 <code>agentforge-model-api</code>。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="48模型注册与工厂">4.8、模型注册与工厂<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#48%E6%A8%A1%E5%9E%8B%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%B7%A5%E5%8E%82" class="hash-link" aria-label="4.8、模型注册与工厂的直接链接" title="4.8、模型注册与工厂的直接链接" translate="no">​</a></h3>
<p><strong>问题引导</strong>：上层要接模型时，难道还要 new 具体的 <code>OpenAiChatModel</code> 吗？</p>
<p>不用。<code>agentforge-model-registry</code> 在 Provider 之上提供开箱即用的装配能力：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">LlmBasicConfig（provider / url / modelName / apiKey / props）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">LlmFactory.buildChatModel(config) / buildStreamChatModel(config)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │  LlmEnum.of(providerCode) -&gt; IModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAiModel / AnthropicModel  实现 IModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAiChatModel / AnthropicChatModel  (底层仍是标准 ChatModel)</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">IModel</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">ChatModel</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">LlmBasicConfig</span><span class="token plain"> llmBasicConfig</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">StreamingChatModel</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">buildStreamChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">LlmBasicConfig</span><span class="token plain"> llmBasicConfig</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p><code>LlmEnum</code> 登记「providerCode -&gt; IModel 实现类」，<code>LlmFactory</code> 反射实例化并委托构建，例如
<code>OPENAI(1, "OpenAI", OpenAiModel.class)</code>、<code>ANTHROPIC(2, "Anthropic", AnthropicModel.class)</code>。</p>
<blockquote>
<p><strong>注意</strong>：<code>IModel</code> 注释已预留：后续 <code>EmbeddingModel</code> 等<strong>其他模型类别</strong>也在这里继续扩展。这意味着
「标准协议 + Provider 扩展」的模式会沿着模型类别继续复制。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="49新增-provider-检查清单">4.9、新增 Provider 检查清单<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#49%E6%96%B0%E5%A2%9E-provider-%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95" class="hash-link" aria-label="4.9、新增 Provider 检查清单的直接链接" title="4.9、新增 Provider 检查清单的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] 新建 agentforge-model-xxx 模块，依赖 api（+ core）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] XxxChatModel implements ChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] XxxStreamingChatModel implements StreamingChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] XxxProtocol / XxxMessages 包私有 wire helper（Blocking/Streaming 共享）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] 五步实现：merge -&gt; request -&gt; transport -&gt; response -&gt; exception</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] 映射 FinishReason / TokenUsage / metadata / tool 调用</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] 把 model 参数收进 DefaultChatRequestParameters 默认值</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] 补充单测：纯文本 / 工具调用 / 流式聚合 / 错误 / 参数合并</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">[ ] 在 agentforge-model-registry 增加 IModel 实现并登记 LlmEnum</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五实战一次完整的多轮工具调用">五、实战：一次完整的多轮工具调用<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%BA%94%E5%AE%9E%E6%88%98%E4%B8%80%E6%AC%A1%E5%AE%8C%E6%95%B4%E7%9A%84%E5%A4%9A%E8%BD%AE%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8" class="hash-link" aria-label="五、实战：一次完整的多轮工具调用的直接链接" title="五、实战：一次完整的多轮工具调用的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="51为什么还需要一层-tool-layer">5.1、为什么还需要一层 Tool layer？<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#51%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%98%E9%9C%80%E8%A6%81%E4%B8%80%E5%B1%82-tool-layer" class="hash-link" aria-label="5.1、为什么还需要一层 Tool layer？的直接链接" title="5.1、为什么还需要一层 Tool layer？的直接链接" translate="no">​</a></h3>
<p><strong>问题引导</strong>：Provider 已经能解析 <code>tool_calls</code> 了，为什么 Model API 里还有一套 <code>ToolService</code>？</p>
<p>因为两者解决的是不同问题：</p>
<ul>
<li class="">Provider 层解决「<strong>模型协议里如何表达工具调用</strong>」（wire 映射）；</li>
<li class="">Tool layer 解决「<strong>上层 Agent 如何把普通 Java 方法变成可被 LLM 调用的工具，并自动驱动多轮执行</strong>」。</li>
</ul>
<p>它对应包 <code>cloud.changlu.agentforge.model.tool</code>，拆分为顶层 + 三个子包：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">cloud.changlu.agentforge.model.tool</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── Tool / P / ReturnBehavior / ToolExecutor          # 核心契约</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── spec</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolSpecification                             # name/description/parameters/strict</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolParameters                               # JSON-Schema 风格（纯 Map，无第三方依赖）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── ToolSpecifications（core）                    # @Tool 方法 -&gt; ToolSpecification</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">├── execution</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolExecution / ToolExecutionResult（api）    # 一次执行（请求+结果+耗时）/ 值对象</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── ToolService / DefaultToolExecutor（core）     # 注册 + 推理/执行循环 / 反射执行器</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── ToolExecutionRequestUtil（core）              # arguments JSON -&gt; Map</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">└── error</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    ├── ToolArgumentsException / ToolExecutionException</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    └── ToolArgumentsErrorHandler / ToolExecutionErrorHandler</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ToolErrorContext / ToolErrorHandlerResult</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="52快速构建工具">5.2、快速构建工具<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#52%E5%BF%AB%E9%80%9F%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="5.2、快速构建工具的直接链接" title="5.2、快速构建工具的直接链接" translate="no">​</a></h3>
<p>用 <code>@Tool</code> 标注方法，交给 <code>ToolService</code> 注册：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">value </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Returns the weather for the given city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getWeather</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@P</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"city name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token plain"> city</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Weather in "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> city </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">": 22C sunny"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ToolService</span><span class="token plain"> toolService </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">ToolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Arrays</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">asList</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">WeatherTools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 扫描全部 @Tool 方法</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolChatResult</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> toolService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">finalResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p><code>ToolSpecifications</code> 把方法名 / <code>@P</code> 参数名 / 类型自动转成 <code>ToolSpecification</code> + <code>ToolParameters</code>，
与 Provider 一侧的 <code>tools()</code> / <code>toolChoice()</code> 天然对接。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="53toolexecutor-与执行循环">5.3、ToolExecutor 与执行循环<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#53toolexecutor-%E4%B8%8E%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF" class="hash-link" aria-label="5.3、ToolExecutor 与执行循环的直接链接" title="5.3、ToolExecutor 与执行循环的直接链接" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@FunctionalInterface</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ToolExecutor</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token class-name">String</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token class-name">ToolExecutionResult</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">executeWithResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">Object</span><span class="token plain"> memoryId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>DefaultToolExecutor</code>（core）是默认反射实现：把 <code>arguments()</code> JSON 绑定到 <code>@Tool</code> 方法参数并调用；<!-- -->
<ul>
<li class=""><code>String</code> -&gt; 原样返回；</li>
<li class=""><code>void</code> -&gt; 字面量 <code>"Success"</code>；</li>
<li class="">其它 -&gt; <code>Json.stringify(...)</code>。</li>
</ul>
</li>
<li class="">也可用 Lambda 提供自定义 <code>ToolExecutor</code>。</li>
</ul>
<p><code>ToolService.chat(...)</code> 的循环：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">1. 携带已注册 tools 调用 ChatModel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">2. 若 aiMessage.hasToolExecutionRequests()：</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     a. 逐个执行工具（找不到 executor -&gt; 默认抛异常）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     b. 把每个 ToolExecutionResult 转成 ToolExecutionResultMessage（id 关联）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     c. 追加到消息列表</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     d. 若 ReturnBehavior 要求立即返回 -&gt; 停止</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">     e. 否则用新消息继续下一轮</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">3. 若无工具调用 -&gt; 返回最终 ChatResponse + 全部 ToolExecution</span><br></div></code></pre></div></div>
<ul>
<li class="">默认 <code>maxToolCallingRoundTrips = 100</code>，防止死循环；</li>
<li class=""><code>ReturnBehavior</code>：<code>TO_LLM</code>（默认）、<code>IMMEDIATE</code>、<code>IMMEDIATE_IF_LAST</code>；</li>
<li class="">任意工具出错都会强制再跑一轮，让 LLM 看到错误并纠正重试。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="54错误处理">5.4、错误处理<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#54%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86" class="hash-link" aria-label="5.4、错误处理的直接链接" title="5.4、错误处理的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>异常</th><th>触发点</th><th>默认处理</th></tr></thead><tbody><tr><td><code>ToolArgumentsException</code></td><td>参数 JSON 无法解析 / 类型不符 / 缺必填参数</td><td>抛异常（<code>RETHROW</code>）</td></tr><tr><td><code>ToolExecutionException</code></td><td>工具方法执行失败</td><td>以 <code>ToolErrorHandlerResult.text(...)</code> 回给 LLM</td></tr></tbody></table>
<p>可通过 <code>argumentsErrorHandler(...)</code> / <code>executionErrorHandler(...)</code> 定制：要么返回
<code>ToolErrorHandlerResult.text(msg)</code>（回给 LLM），要么直接抛异常（终止调用）。</p>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="55与-function-calling-协议层的关系">5.5、与 Function Calling 协议层的关系<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#55%E4%B8%8E-function-calling-%E5%8D%8F%E8%AE%AE%E5%B1%82%E7%9A%84%E5%85%B3%E7%B3%BB" class="hash-link" aria-label="5.5、与 Function Calling 协议层的关系的直接链接" title="5.5、与 Function Calling 协议层的关系的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolService.chat()                     &lt;- 上层 Agent 入口（复用本章 Tool layer）</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatModel / ChatRequest / ChatResponse &lt;- 第三/四章标准协议</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">OpenAI / Anthropic Adapter             &lt;- 第四章多协议扩展</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">AiMessage.toolExecutionRequests()      &lt;- 模型给出的工具调用</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ToolService 执行 -&gt; ToolExecutionResultMessage 回填</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六实战用例真实-curl-与返回结果">六、实战用例：真实 curl 与返回结果<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%85%AD%E5%AE%9E%E6%88%98%E7%94%A8%E4%BE%8B%E7%9C%9F%E5%AE%9E-curl-%E4%B8%8E%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C" class="hash-link" aria-label="六、实战用例：真实 curl 与返回结果的直接链接" title="六、实战用例：真实 curl 与返回结果的直接链接" translate="no">​</a></h2>
<p>本章把最常见的调用形态——<strong>非流式 / 流式</strong> 与 <strong>纯文本 / Function Calling / 思考过程</strong>——用真实 curl 与
返回结果完整走一遍，方便对照第三章的 <code>ChatRequest</code> / <code>ChatResponse</code> 映射与第四章的协议转换。其中
OpenAI 侧额外覆盖「工具调用」与「推理模型思考过程」两类返回。</p>
<blockquote>
<p>说明：下方返回值为 <strong>真实协议结构</strong>（<code>id</code>、时间戳、token 数会随实际调用变化）；仓库中提供了可直接运行的
<code>openai/verify-openai-chat.sh</code> 与 <code>anthropic/verify-anthropic-chat.sh</code> 用于真实环境验证。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="61openai">6.1、OpenAI<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#61openai" class="hash-link" aria-label="6.1、OpenAI的直接链接" title="6.1、OpenAI的直接链接" translate="no">​</a></h3>
<p>本节覆盖 4 类 OpenAI 返回：非流式纯文本、非流式 Function Calling、流式思考过程、流式 Function Calling。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="611非流式纯文本">6.1.1、非流式（纯文本）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#611%E9%9D%9E%E6%B5%81%E5%BC%8F%E7%BA%AF%E6%96%87%E6%9C%AC" class="hash-link" aria-label="6.1.1、非流式（纯文本）的直接链接" title="6.1.1、非流式（纯文本）的直接链接" translate="no">​</a></h4>
<p>curl：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "system", "content": "You are a helpful assistant."},</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "用一句话解释什么是 CompletableFuture。"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "temperature": 0.7,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "max_tokens": 256</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（节选）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl-9xYzAbC123"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"object"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chat.completion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"created"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1726200000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"CompletableFuture 是 Java 8 引入的异步编程工具，用来表示一个尚未完成、但未来会给出结果的计算。"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"stop"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">28</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">38</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">66</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>AgentForge 侧映射：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">choices[0].message.content -&gt; AiMessage.text()</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">choices[0].finish_reason   -&gt; FinishReason.STOP</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">usage.prompt_tokens        -&gt; TokenUsage.inputTokens  = 28</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">usage.completion_tokens    -&gt; TokenUsage.outputTokens = 38</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">usage.total_tokens         -&gt; TokenUsage.totalTokens  = 66</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">id / model / created       -&gt; ChatResponse.metadata</span><br></div></code></pre></div></div>
<p>对应调用：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">OpenAiChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.7</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">SystemMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"You are a helpful assistant."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"用一句话解释什么是 CompletableFuture。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">totalTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 66</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)">// STOP</span><br></div></code></pre></div></div>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="612非流式function-calling">6.1.2、非流式（Function Calling）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#612%E9%9D%9E%E6%B5%81%E5%BC%8Ffunction-calling" class="hash-link" aria-label="6.1.2、非流式（Function Calling）的直接链接" title="6.1.2、非流式（Function Calling）的直接链接" translate="no">​</a></h4>
<p>curl（携带 <code>tools</code> 与 <code>tool_choice</code>）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "杭州今天天气怎么样？"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "type": "function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type": "string", "description": "城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（模型决定调用工具，<code>content</code> 为 <code>null</code>）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chatcmpl-9xYzAbC456"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"object"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"chat.completion"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"created"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1726200100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4o-mini"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"choices"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"index"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"tool_calls"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"call_abc123"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"function"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"function"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token property">"arguments"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"{\"city\":\"杭州\"}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"finish_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tool_calls"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"prompt_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">62</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"completion_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">18</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"total_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">80</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>AgentForge 侧映射：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">message.tool_calls[].id                 -&gt; ToolExecutionRequest.id        = "call_abc123"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">message.tool_calls[].function.name      -&gt; ToolExecutionRequest.name      = "get_weather"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">message.tool_calls[].function.arguments -&gt; ToolExecutionRequest.arguments = {"city":"杭州"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">message.content = null                  -&gt; AiMessage.text()               = null</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">finish_reason=tool_calls                -&gt; FinishReason.TOOL_EXECUTION</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">usage.*                                 -&gt; TokenUsage</span><br></div></code></pre></div></div>
<p>对应调用与「工具结果回填」：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ToolSpecification</span><span class="token plain"> weather </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">ToolSpecification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"get_weather"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"查询指定城市的当前天气"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">addProperty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"city"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"城市名"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> first </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天天气怎么样？"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">parameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">weather</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolChoice</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolChoice</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">AUTO</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 模型返回工具调用：AiMessage.text() == null，finishReason = TOOL_EXECUTION</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ToolExecutionRequest</span><span class="token plain"> call </span><span class="token operator">=</span><span class="token plain"> first</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">toolExecutionRequests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">call</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// get_weather</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">call</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">arguments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// {"city":"杭州"}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 执行工具后，把结果以 ToolExecutionResultMessage（id 关联）回填，再发起第二轮</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> second </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天天气怎么样？"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">first</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ToolExecutionResultMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">call</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> call</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"杭州今天 22℃，天气晴。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">second</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// "杭州今天 22℃，天气晴。"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">second</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">       </span><span class="token comment" style="color:rgb(98, 114, 164)">// STOP</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：工具调用请求与工具执行结果通过 <code>id</code> 关联；<code>AiMessage</code> 同时容纳文本与 <code>toolExecutionRequests()</code>，
因此「文本 + 工具调用」的混合输出也能表达。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="613流式纯文本--思考过程">6.1.3、流式（纯文本 + 思考过程）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#613%E6%B5%81%E5%BC%8F%E7%BA%AF%E6%96%87%E6%9C%AC--%E6%80%9D%E8%80%83%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="6.1.3、流式（纯文本 + 思考过程）的直接链接" title="6.1.3、流式（纯文本 + 思考过程）的直接链接" translate="no">​</a></h4>
<p>以 OpenAI-compatible 推理模型为例（如 DeepSeek 系列，思考增量放在 <code>reasoning_content</code>）：</p>
<p>curl（<code>-N</code> 关闭缓冲，持续输出 SSE）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-N</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "deepseek-reasoner",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream": true,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream_options": { "include_usage": true },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "用一句话解释什么是 CompletableFuture。"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（<code>data:</code> 帧，先 <code>reasoning_content</code> 后 <code>content</code>）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC789","object":"chat.completion.chunk","created":1726200200,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"用户想要一句话解释","content":null},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC789","object":"chat.completion.chunk","created":1726200200,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"reasoning_content":"CompletableFuture，我需要给出准确又简洁的定义。","content":null},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC789","object":"chat.completion.chunk","created":1726200200,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"content":"CompletableFuture"},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC789","object":"chat.completion.chunk","created":1726200200,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"content":" 是 Java 8 引入的异步编程工具。"},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC789","object":"chat.completion.chunk","created":1726200200,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC789","object":"chat.completion.chunk","created":1726200200,"model":"deepseek-reasoner","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":52,"total_tokens":66}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: [DONE]</span><br></div></code></pre></div></div>
<p>AgentForge 侧回调序列：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">onPartialThinking("用户想要一句话解释")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">onPartialThinking("CompletableFuture，我需要给出准确又简洁的定义。")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">onPartialResponse("CompletableFuture")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">onPartialResponse(" 是 Java 8 引入的异步编程工具。")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">onCompleteResponse(ChatResponse{ text="CompletableFuture 是 Java 8 引入的异步编程工具。",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                thinking="用户想要一句话解释CompletableFuture，我需要给出准确又简洁的定义。",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                finishReason=STOP, tokenUsage=(14,52,66) })</span><br></div></code></pre></div></div>
<p>对应调用：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">StreamingChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">OpenAiStreamingChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"deepseek-reasoner"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"用一句话解释什么是 CompletableFuture。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">StreamingChatResponseHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onPartialThinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> partialThinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"[思考] "</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> partialThinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 思考增量，可选展示</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onPartialResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">String</span><span class="token plain"> partialResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">partialResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">               </span><span class="token comment" style="color:rgb(98, 114, 164)">// 正式回答，逐字输出</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onCompleteResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatResponse</span><span class="token plain"> completeResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"thinking="</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> completeResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">thinking</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"done="</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> completeResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">finishReason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token annotation punctuation" style="color:rgb(248, 248, 242)">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">Throwable</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">printStackTrace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p>注意：<code>[DONE]</code> 帧只表示 SSE 结束；真正的聚合结果在最后一个带 <code>usage</code> 的 chunk 之后由
<code>onCompleteResponse</code> 给出。<code>stream_options.include_usage=true</code> 是拿到 <code>usage</code> 的前提。</p>
</blockquote>
<blockquote>
<p><strong>重点</strong>：<code>reasoning_content</code>（部分实现用 <code>thinking</code>）会被识别为思考增量，实时走 <code>onPartialThinking</code>，
最终聚合进 <code>AiMessage.thinking()</code>；可见回答走 <code>onPartialResponse</code>，两条通道互不干扰。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="614流式function-calling-增量聚合">6.1.4、流式（Function Calling 增量聚合）<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#614%E6%B5%81%E5%BC%8Ffunction-calling-%E5%A2%9E%E9%87%8F%E8%81%9A%E5%90%88" class="hash-link" aria-label="6.1.4、流式（Function Calling 增量聚合）的直接链接" title="6.1.4、流式（Function Calling 增量聚合）的直接链接" translate="no">​</a></h4>
<p>curl（<code>stream: true</code> + <code>tools</code>）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-N</span><span class="token plain"> https://api.openai.com/v1/chat/completions </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Authorization: Bearer </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$OPENAI_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "gpt-4o-mini",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream": true,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream_options": { "include_usage": true },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "杭州今天天气怎么样？"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tools": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "type": "function",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        "function": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "name": "get_weather",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "description": "查询指定城市的当前天气",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          "parameters": {</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "type": "object",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "properties": { "city": { "type": "string", "description": "城市名" } },</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">            "required": ["city"]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">          }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">        }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      }</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "tool_choice": "auto"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（<code>delta.tool_calls[]</code> 按 <code>index</code> 分片到达）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC101","object":"chat.completion.chunk","created":1726200300,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC101","object":"chat.completion.chunk","created":1726200300,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC101","object":"chat.completion.chunk","created":1726200300,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"杭州\"}"}}]},"finish_reason":null}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC101","object":"chat.completion.chunk","created":1726200300,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"id":"chatcmpl-9xYzAbC101","object":"chat.completion.chunk","created":1726200300,"model":"gpt-4o-mini","choices":[],"usage":{"prompt_tokens":62,"completion_tokens":18,"total_tokens":80}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: [DONE]</span><br></div></code></pre></div></div>
<p>AgentForge 侧聚合结果：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">按 index=0 逐片拼接 arguments：</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  id        = "call_abc123"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  name      = "get_weather"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  arguments = "{\"city\":" + "\"杭州\"}" = {"city":"杭州"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼ onCompleteResponse</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatResponse{ text=null,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              toolExecutionRequests=[ToolExecutionRequest(id=call_abc123, name=get_weather, arguments={"city":"杭州"})],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              finishReason=TOOL_EXECUTION }</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：流式下工具调用不会通过 <code>onPartialResponse</code> 暴露；它只在 <code>onCompleteResponse</code> 的
<code>AiMessage.toolExecutionRequests()</code> 上一次性给出。多数 OpenAI 兼容端点带 <code>index</code>，缺省时以新出现的
<code>id</code> 作为新调用起点兜底分桶。</p>
</blockquote>
<p><strong>补充：思考 + 工具调用同流</strong>。OpenAI-compatible 推理模型在带 <code>tools</code> 时，可能先流式输出
<code>reasoning_content</code>，再输出 <code>tool_calls</code>。两条通道先后触发，最终仍聚合为同一个 <code>ChatResponse</code>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {...,"delta":{"reasoning_content":"用户问的是杭州天气，需要调用 get_weather。","content":null}...}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {...,"delta":{"reasoning_content":"参数 city=杭州。","content":null}...}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {...,"delta":{"tool_calls":[{"index":0,"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":"{\"city\":\"杭州\"}"}}]}...}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {...,"delta":{},"finish_reason":"tool_calls"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: [DONE]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        ▼ onCompleteResponse</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ChatResponse{ thinking="用户问的是杭州天气，需要调用 get_weather。参数 city=杭州。",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              toolExecutionRequests=[ToolExecutionRequest(id=call_abc123, name=get_weather, arguments={"city":"杭州"})],</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              finishReason=TOOL_EXECUTION }</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="62anthropic">6.2、Anthropic<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#62anthropic" class="hash-link" aria-label="6.2、Anthropic的直接链接" title="6.2、Anthropic的直接链接" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="621非流式">6.2.1、非流式<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#621%E9%9D%9E%E6%B5%81%E5%BC%8F" class="hash-link" aria-label="6.2.1、非流式的直接链接" title="6.2.1、非流式的直接链接" translate="no">​</a></h4>
<p>curl：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> https://api.anthropic.com/v1/messages </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x-api-key: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ANTHROPIC_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"anthropic-version: 2023-06-01"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "claude-3-5-sonnet-latest",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "max_tokens": 256,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "system": "You are a helpful assistant.",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "用一句话解释什么是 CompletableFuture。"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（节选）：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"id"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"msg_01AbCdEfGh"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"role"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"assistant"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"model"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"claude-3-5-sonnet-latest"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"CompletableFuture 是 Java 8 引入的异步编程工具，用来表示一个尚未完成、但未来会给出结果的计算。"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_reason"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"end_turn"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"stop_sequence"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token null keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"usage"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"input_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"output_tokens"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">42</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>AgentForge 侧映射：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">content[].type == "text"  -&gt; 拼接成 AiMessage.text()</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">stop_reason=end_turn      -&gt; FinishReason.STOP</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">usage.input_tokens        -&gt; TokenUsage.inputTokens  = 20</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">usage.output_tokens       -&gt; TokenUsage.outputTokens = 42</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">input + output            -&gt; TokenUsage.totalTokens  = 62</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">id / model / type         -&gt; ChatResponse.metadata</span><br></div></code></pre></div></div>
<p>对应调用：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">ChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">AnthropicChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ANTHROPIC_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"claude-3-5-sonnet-latest"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">ChatResponse</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">ChatRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">SystemMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"You are a helpful assistant."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">UserMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"用一句话解释什么是 CompletableFuture。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">aiMessage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">out</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">println</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">tokenUsage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">totalTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)">// 62</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>重点</strong>：<code>system</code> 在 OpenAI 里是 <code>messages[].role=system</code>，在 Anthropic 里是<strong>顶层 <code>system</code> 字段</strong>，
但 AgentForge 上层始终使用同一个 <code>SystemMessage</code>。</p>
</blockquote>
<br>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="622流式">6.2.2、流式<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#622%E6%B5%81%E5%BC%8F" class="hash-link" aria-label="6.2.2、流式的直接链接" title="6.2.2、流式的直接链接" translate="no">​</a></h4>
<p>curl：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-N</span><span class="token plain"> https://api.anthropic.com/v1/messages </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"x-api-key: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ANTHROPIC_API_KEY</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"anthropic-version: 2023-06-01"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-H</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "model": "claude-3-5-sonnet-latest",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "max_tokens": 256,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "stream": true,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    "messages": [</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">      {"role": "user", "content": "用一句话解释什么是 CompletableFuture。"}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    ]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">  }'</span><br></div></code></pre></div></div>
<p>返回（<code>event:</code> + <code>data:</code> 帧）：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_start","message":{"id":"msg_01AbCdEfGh","type":"message","role":"assistant","model":"claude-3-5-sonnet-latest","content":[],"stop_reason":null,"usage":{"input_tokens":20,"output_tokens":1}}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_start</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"CompletableFuture"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" 是 Java 8 引入的异步编程工具。"}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: content_block_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"content_block_stop","index":0}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_delta</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":24}}</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">event: message_stop</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">data: {"type":"message_stop"}</span><br></div></code></pre></div></div>
<p>AgentForge 侧回调序列：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">onPartialResponse("CompletableFuture")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">onPartialResponse(" 是 Java 8 引入的异步编程工具。")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">onCompleteResponse(ChatResponse{ text="CompletableFuture 是 Java 8 引入的异步编程工具。",</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                finishReason=STOP, tokenUsage=(20,24) })</span><br></div></code></pre></div></div>
<p>对应调用与 6.1.2 相同，只是把模型换成 <code>AnthropicStreamingChatModel</code>：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">StreamingChatModel</span><span class="token plain"> model </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">AnthropicStreamingChatModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">apiKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token class-name">System</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getenv</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"ANTHROPIC_API_KEY"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">modelName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"claude-3-5-sonnet-latest"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">maxTokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"用一句话解释什么是 CompletableFuture。"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>弊端说明</strong>：Anthropic 流式的 <code>usage</code> 分两处给出——<code>message_start</code> 给 <code>input_tokens</code>，
<code>message_delta</code> 给累计的 <code>output_tokens</code>，Provider 需要把两处合并后再写入 <code>TokenUsage</code>。</p>
</blockquote>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="63一键真实验证脚本">6.3、一键真实验证脚本<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#63%E4%B8%80%E9%94%AE%E7%9C%9F%E5%AE%9E%E9%AA%8C%E8%AF%81%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="6.3、一键真实验证脚本的直接链接" title="6.3、一键真实验证脚本的直接链接" translate="no">​</a></h3>
<p>仓库中已提供两份可直接运行的脚本：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">openai/verify-openai-chat.sh</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">anthropic/verify-anthropic-chat.sh</span><br></div></code></pre></div></div>
<p>以 OpenAI 为例：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">OPENAI_API_KEY</span><span class="token operator">=</span><span class="token plain">sk-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">bash</span><span class="token plain"> openai/verify-openai-chat.sh</span><br></div></code></pre></div></div>
<p>脚本会依次执行「非流式请求 -&gt; jq 解析 <code>tool_calls</code> -&gt; 流式请求观察 <code>data:</code> 帧」；Anthropic 脚本同理。
真实返回同样遵循本章的映射规则。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七验证测试">七、验证测试<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B8%83%E9%AA%8C%E8%AF%81%E6%B5%8B%E8%AF%95" class="hash-link" aria-label="七、验证测试的直接链接" title="七、验证测试的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="71测试矩阵">7.1、测试矩阵<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#71%E6%B5%8B%E8%AF%95%E7%9F%A9%E9%98%B5" class="hash-link" aria-label="7.1、测试矩阵的直接链接" title="7.1、测试矩阵的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>模块</th><th>测试类</th><th>覆盖点</th></tr></thead><tbody><tr><td><code>agentforge-model-api</code></td><td><code>AiMessageToolCallTest</code></td><td>单/多工具调用、文本+工具调用、thinking/attributes、值相等与不可变、<code>UserMessage</code> name + 多 <code>Content</code>、<code>ToolExecutionRequest</code> 字段往返</td></tr><tr><td><code>agentforge-model-api</code></td><td><code>ChatModelTest</code> / <code>StreamingChatModelTest</code></td><td>便利方法收敛、流式生命周期</td></tr><tr><td><code>agentforge-model-core</code></td><td><code>DefaultChatRequestParametersTest</code></td><td>默认值 / merge 覆盖 / customParameters Map 合并</td></tr><tr><td><code>agentforge-model-core</code></td><td><code>ToolServiceTest</code> / <code>DefaultToolExecutorTest</code></td><td>推理-执行循环、参数绑定、错误处理</td></tr><tr><td><code>agentforge-model-openai</code></td><td><code>OpenAiFunctionCallTest</code></td><td>单/多 <code>tool_calls</code>、混合文本、<code>tools</code>/<code>tool_choice</code>（含 SPECIFIC）序列化、tool result 回流</td></tr><tr><td><code>agentforge-model-openai</code></td><td><code>OpenAiStreamingFunctionCallTest</code></td><td>单工具调用 delta 合并、多工具交错、文本+工具同流、无 <code>index</code> 兜底</td></tr><tr><td><code>agentforge-model-anthropic</code></td><td><code>AnthropicToolUseTest</code></td><td><code>tool_use</code> 解析、多 <code>tool_use</code>、<code>tool_result</code> 回流、<code>input_schema</code> 序列化、多 <code>Content</code> 用户消息</td></tr><tr><td><code>agentforge-model-anthropic</code></td><td><code>AnthropicStreamingToolUseTest</code></td><td><code>input_json_delta</code> 聚合、<code>text_delta</code> 与多 <code>tool_use</code> 混合流式、metadata / token usage</td></tr></tbody></table>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="72如何做到不依赖真实-api-key">7.2、如何做到不依赖真实 API Key<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#72%E5%A6%82%E4%BD%95%E5%81%9A%E5%88%B0%E4%B8%8D%E4%BE%9D%E8%B5%96%E7%9C%9F%E5%AE%9E-api-key" class="hash-link" aria-label="7.2、如何做到不依赖真实 API Key的直接链接" title="7.2、如何做到不依赖真实 API Key的直接链接" translate="no">​</a></h3>
<p><strong>问题引导</strong>：140+ 单测如何在没有真实 API Key 的情况下稳定跑通？</p>
<p>答案就是 <code>HttpTransport</code> SPI。测试时注入一个 Fake Transport，直接返回预置的 JSON / SSE 行，即可覆盖：</p>
<ul>
<li class="">请求 payload 序列化是否正确；</li>
<li class="">响应解析、finish reason、token usage 映射是否正确；</li>
<li class="">流式聚合、工具调用合并是否正确；</li>
<li class="">错误路径（HTTP 4xx/5xx）是否转成 <code>ModelException</code>。</li>
</ul>
<blockquote>
<p><strong>重点</strong>：传输层的可替换性，不只是为了支持 OkHttp，更是为了让核心协议逻辑能够被离线、确定性地单测。</p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八边界兼容与演进">八、边界、兼容与演进<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%85%AB%E8%BE%B9%E7%95%8C%E5%85%BC%E5%AE%B9%E4%B8%8E%E6%BC%94%E8%BF%9B" class="hash-link" aria-label="八、边界、兼容与演进的直接链接" title="八、边界、兼容与演进的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="81已落地能力">8.1、已落地能力<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#81%E5%B7%B2%E8%90%BD%E5%9C%B0%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="8.1、已落地能力的直接链接" title="8.1、已落地能力的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>ChatModel</code> / <code>StreamingChatModel</code> 统一入口；</li>
<li class=""><code>ChatRequest</code> / <code>ChatRequestParameters</code> / <code>ChatResponse</code> 统一出入参；</li>
<li class=""><code>AiMessage.toolExecutionRequests()</code> + <code>ToolExecutionRequest</code>；</li>
<li class=""><code>ToolSpecification</code> / <code>ToolParameters</code>（JSON-Schema 风格，纯 <code>Map</code>）/ <code>ToolChoice</code>；</li>
<li class="">OpenAI / Anthropic 的 tool 请求发送链路与 tool-result wire mapping；</li>
<li class="">OpenAI 流式 <code>tool_calls</code> delta 按 <code>index</code> 聚合；</li>
<li class=""><code>AnthropicStreamingChatModel</code>（含 <code>tool_use</code> <code>input_json_delta</code> 聚合）；</li>
<li class=""><code>AiMessage.thinking()</code> / <code>attributes</code> 与 <code>StreamingChatResponseHandler.onPartialThinking(...)</code>；</li>
<li class=""><code>UserMessage</code> 的 <code>name</code> + <code>List&lt;Content&gt;</code> 多模态建模（当前提供 <code>TextContent</code>）；</li>
<li class=""><code>agentforge-model-registry</code> 工厂化装配。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="82待补齐能力">8.2、待补齐能力<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#82%E5%BE%85%E8%A1%A5%E9%BD%90%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="8.2、待补齐能力的直接链接" title="8.2、待补齐能力的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><code>ImageContent</code> / <code>AudioContent</code> / <code>VideoContent</code> / <code>PdfFileContent</code> 等多模态 <code>Content</code> 实现与 Provider wire mapping；</li>
<li class="">流式过程暴露 <code>onPartialToolCall(...)</code> 增量回调（当前仅最终 <code>ChatResponse</code> 暴露完整工具调用）；</li>
<li class="">Structured Output / JSON Schema 强类型参数绑定与运行时校验；</li>
<li class=""><code>thinking</code> 的 Provider 级强类型协议（当前只保留字段位）；</li>
<li class="">Prompt Cache；</li>
<li class="">retry / backoff / rate-limit policy；</li>
<li class="">metrics / tracing / request-id 标准化；</li>
<li class="">其他模型类别：<code>EmbeddingModel</code> / <code>ImageModel</code> / <code>AudioModel</code>。</li>
</ul>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="83customparameters-的边界">8.3、customParameters 的边界<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#83customparameters-%E7%9A%84%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="8.3、customParameters 的边界的直接链接" title="8.3、customParameters 的边界的直接链接" translate="no">​</a></h3>
<p><code>customParameters</code> 用于快速支持 Provider 新字段：</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">DefaultChatRequestParameters</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">customParameter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"reasoning_effort"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"high"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">build</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>弊端说明</strong>：<code>customParameters</code> 若被滥用，会让 Core 类型系统逐步失效，上层重新被迫感知供应商字段。
因此它只应是「临时通道」，而不是「长期方案」。</p>
</blockquote>
<p>判断标准：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">仅单一 Provider 使用             -&gt; customParameters</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">多个 Provider 都出现且语义稳定   -&gt; Core 强类型参数</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Agent/Tool 核心流程必须理解       -&gt; Core 强类型模型</span><br></div></code></pre></div></div>
<br>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="84兼容性原则">8.4、兼容性原则<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#84%E5%85%BC%E5%AE%B9%E6%80%A7%E5%8E%9F%E5%88%99" class="hash-link" aria-label="8.4、兼容性原则的直接链接" title="8.4、兼容性原则的直接链接" translate="no">​</a></h3>
<ol>
<li class="">优先新增接口默认方法或新类型，避免破坏已有 Provider；</li>
<li class="">Core 不暴露供应商 SDK 类型；</li>
<li class="">Provider 特有协议优先留在 Provider module；</li>
<li class="">request/response 对象尽量不可变；</li>
<li class="">公共 API 保持 Java 8 可编译，默认推荐运行在 JDK 17；</li>
<li class="">Streaming 与 Blocking 最终必须收敛到同一 <code>ChatResponse</code> 语义。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="九总结">九、总结<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E4%B9%9D%E6%80%BB%E7%BB%93" class="hash-link" aria-label="九、总结的直接链接" title="九、总结的直接链接" translate="no">​</a></h2>
<p>回到开头那个问题：<strong>换模型时，Agent 主循环能不能一行不改？</strong></p>
<p>AgentForge 的答案是：可以。做法是把「变」与「不变」彻底分开——</p>
<ul>
<li class=""><strong>不变</strong>：<code>ChatModel</code> / <code>ChatRequest</code> / <code>ChatMessage</code> / <code>ChatResponse</code> 语义、流式生命周期、异常边界；</li>
<li class=""><strong>变</strong>：每家厂商的 wire 协议，全部下沉到各自的 Provider 模块，并借助 <code>HttpTransport</code>、包私有 wire helper
与 <code>LlmFactory</code> 完成隔离与装配。</li>
</ul>
<p>由此形成一条可复制的扩展路径：<strong>定义标准协议 → 新增 Provider 模块 → 五步协议转换 → 注册进 LlmFactory</strong>。
这套边界保证 AgentForge 从 LLM 层继续向 Tool Calling、Agent Runtime、Memory 和 Workflow 演进时，上层
不会被某一家模型协议锁死，也让「新增一个协议模型」退化为一件低风险、可复制的工作。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考资料">参考资料<a href="https://agentforge-tech.github.io/AgentForge/blog/01%E3%80%81model%E6%A8%A1%E5%9E%8B%E5%8D%8F%E8%AE%AE%E5%B1%82/chatmodel/%E7%BB%9F%E4%B8%80ChatModel%E5%8D%8F%E8%AE%AE%E5%B1%8201%E3%80%81ChatModel%20%E6%A0%B8%E5%BF%83%E5%8D%8F%E8%AE%AE%E5%B1%82%E8%AE%BE%E8%AE%A1%EF%BC%9A%E7%BB%9F%E4%B8%80%20API%20%E5%A5%91%E7%BA%A6%E5%B0%81%E8%A3%85%E4%B8%8E%E5%A4%9A%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E5%AE%9E%E7%8E%B0#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99" class="hash-link" aria-label="参考资料的直接链接" title="参考资料的直接链接" translate="no">​</a></h2>
<p>[1]. <a href="https://github.com/langchain4j/langchain4j" target="_blank" rel="noopener noreferrer" class="">LangChain4j - Build LLM-powered applications in Java</a></p>
<p>[2]. <a href="https://platform.openai.com/docs/api-reference/chat" target="_blank" rel="noopener noreferrer" class="">OpenAI Chat Completions API Reference</a></p>
<p>[3]. <a href="https://docs.anthropic.com/en/api/messages" target="_blank" rel="noopener noreferrer" class="">Anthropic Messages API Reference</a></p>
<p>[4]. <a href="https://platform.openai.com/docs/api-reference/chat/streaming" target="_blank" rel="noopener noreferrer" class="">OpenAI Streaming Responses (SSE)</a></p>
<p>[5]. <a href="https://docs.anthropic.com/en/api/messages-streaming" target="_blank" rel="noopener noreferrer" class="">Anthropic Streaming Messages</a></p>
<p>整理者:长路 创建时间:2026.9.13 更新时间:2026.10.6</p>]]></content:encoded>
            <category>AgentForge</category>
            <category>模型协议层</category>
            <category>ChatModel</category>
        </item>
    </channel>
</rss>