<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://blog.zeerd.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://blog.zeerd.com/" rel="alternate" type="text/html" /><updated>2026-03-27T20:16:16+08:00</updated><id>http://blog.zeerd.com/feed.xml</id><title type="html">zeerd’s blog</title><subtitle>闲来添雅趣，无事自逍遥。对窗静望雪，一盏茶香绕。</subtitle><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><entry><title type="html">ColorOS(Android16) 运行 Termux/Ubuntu</title><link href="http://blog.zeerd.com/color-os-termux/" rel="alternate" type="text/html" title="ColorOS(Android16) 运行 Termux/Ubuntu" /><published>2026-03-27T00:00:00+08:00</published><updated>2026-03-27T00:00:00+08:00</updated><id>http://blog.zeerd.com/color-os-termux</id><content type="html" xml:base="http://blog.zeerd.com/color-os-termux/"><![CDATA[<p>在<code class="language-html highlighter-rouge">Termux</code>中基于<code class="language-html highlighter-rouge">proot-distro</code>运行<code class="language-html highlighter-rouge">ubuntu</code>，手机一熄屏，<code class="language-html highlighter-rouge">Termux</code>就被<code class="language-html highlighter-rouge">kill -9</code>了。解决方案：</p>

<!--break-->

<ol>
  <li>关闭电池优化：</li>
</ol>

<ul>
  <li>打开手机 设置 &gt; 电池 &gt; 应用电池用量（或“应用耗电管理”）。</li>
  <li>找到 Termux，将其设置为 “允许后台运行” 或 “允许完全后台行为”（不要选“智能控制”或“自动优化”）。</li>
</ul>

<ol>
  <li>锁定后台任务：</li>
</ol>

<ul>
  <li>打开 Termux，然后进入手机的 “最近任务” 视图（多任务切换界面）。</li>
  <li>找到 Termux 的卡片，点击右上角的菜单（或长按卡片），选择 “锁定”（通常会显示一个小锁图标）。这样一键清理内存时不会误杀它。</li>
</ul>

<ol>
  <li>Termux 内部配置：获取唤醒锁</li>
</ol>

<p>即使系统允许后台，CPU 休眠也可能导致进程挂起。你需要让 Termux 申请“唤醒锁”。
打开 Termux，安装 API 包：</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code>pkg <span class="nb">install </span>termux-api
termux-wake-lock
</code></pre></div></div>

<p>执行后，系统会弹窗询问是否允许 Termux 忽略电池优化，请务必点击 “允许”。</p>

<ol>
  <li>ADB 解除“幽灵进程”限制</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="c"># 1. 禁用设备配置的同步延迟限制（让设置立即生效）</span>
adb shell device_config set_sync_disabled_for_tests persistent
<span class="c"># 2. 将最大幽灵进程数修改为极大值（默认通常只有32-64）</span>
adb shell device_config put activity_manager max_phantom_processes 65536
</code></pre></div></div>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="Android" /><category term="Termux" /><summary type="html"><![CDATA[在Termux中基于proot-distro运行ubuntu，手机一熄屏，Termux就被kill -9了。解决方案：]]></summary></entry><entry><title type="html">Python 脚本实现 MCP 的中途提问（Elicitation）功能</title><link href="http://blog.zeerd.com/mcp-elicitation/" rel="alternate" type="text/html" title="Python 脚本实现 MCP 的中途提问（Elicitation）功能" /><published>2025-08-21T00:00:00+08:00</published><updated>2025-08-21T00:00:00+08:00</updated><id>http://blog.zeerd.com/mcp-elicitation</id><content type="html" xml:base="http://blog.zeerd.com/mcp-elicitation/"><![CDATA[<p><code class="language-html highlighter-rouge">Elicitation</code>的具体介绍可以参照<code class="language-html highlighter-rouge">MCP</code>的<a href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation">规范文档</a>。
该功能允许 <code class="language-html highlighter-rouge">MCP</code> 服务器在工具执行过程中暂停，并通过客户端向用户请求额外的输入信息，非常适合处理需要人工介入或确认的场景。</p>

<!--break-->

<p><code class="language-html highlighter-rouge">Elicitation</code>的流程示意如下：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant User
    participant Client
    participant Server

    Note over Client,Server: 1. 初始工具调用
    Client-&gt;&gt;Server: callTool(create_order, params)
    activate Server
    
    Note over Server: 2. 执行工具逻辑
    Note over Server: 3. 触发 Elicitation
    Server-&gt;&gt;Client: elicitationRequest (暂停执行)
    deactivate Server
    
    Note over Client: 4. 显示对话框
    Client-&gt;&gt;User: 显示确认对话框
    User-&gt;&gt;Client: 用户输入响应
    
    Note over Client: 5. 发送用户响应
    Client-&gt;&gt;Server: elicitationResponse (继续请求)
    activate Server
    
    Note over Server: 6. 恢复执行
    Server--&gt;&gt;Server: 处理用户响应并继续
    
    Note over Server: 7. 返回最终结果
    Server-&gt;&gt;Client: 工具执行结果
    deactivate Server
    
    Note over Client: 8. 显示结果
    Client-&gt;&gt;User: 显示最终结果
</code></pre>

<h2 id="示例程序">示例程序</h2>

<h3 id="服务端">服务端</h3>

<p>在<code class="language-html highlighter-rouge">my_tool</code>运行过程中，调用<code class="language-html highlighter-rouge">ctx.elicit</code>向客户端发起咨询。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
<span class="kn">from</span> <span class="nn">mcp.server.fastmcp</span> <span class="kn">import</span> <span class="n">Context</span><span class="p">,</span> <span class="n">FastMCP</span>


<span class="k">class</span> <span class="nc">InputSchema</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="nb">input</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s">"some infomation"</span><span class="p">)</span>


<span class="n">mcp</span> <span class="o">=</span> <span class="n">FastMCP</span><span class="p">(</span><span class="s">"my_tool"</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">12345</span><span class="p">)</span>


<span class="o">@</span><span class="n">mcp</span><span class="p">.</span><span class="n">tool</span><span class="p">()</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">my_tool</span><span class="p">(</span><span class="n">prompt</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="n">Context</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'my_tool:prompt=</span><span class="si">{</span><span class="n">prompt</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ctx</span><span class="p">.</span><span class="n">elicit</span><span class="p">(</span>  <span class="c1"># 关键点
</span>        <span class="n">message</span><span class="o">=</span><span class="p">(</span><span class="sa">f</span><span class="s">"Input something about </span><span class="si">{</span><span class="n">prompt</span><span class="si">}</span><span class="s">"</span><span class="p">),</span>
        <span class="n">schema</span><span class="o">=</span><span class="n">InputSchema</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'my_tool:result=</span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">action</span> <span class="o">==</span> <span class="s">"accept"</span> <span class="ow">and</span> <span class="n">result</span><span class="p">.</span><span class="n">data</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="nb">input</span><span class="p">:</span>
            <span class="n">res</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"[SUCCESS] </span><span class="si">{</span><span class="n">prompt</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">result</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="nb">input</span><span class="si">}</span><span class="s">"</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">res</span> <span class="o">=</span> <span class="s">"[CANCELLED] No Input"</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">res</span> <span class="o">=</span> <span class="s">"[CANCELLED] Input cancelled"</span>
    <span class="k">return</span> <span class="n">res</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="n">mcp</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">transport</span><span class="o">=</span><span class="s">'sse'</span><span class="p">)</span>

</code></pre></div></div>

<h3 id="客户端">客户端</h3>

<p>注册<code class="language-html highlighter-rouge">elicitation_callback</code>回调，响应服务端发起的咨询。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="kn">import</span> <span class="nn">asyncio</span>

<span class="kn">from</span> <span class="nn">mcp</span> <span class="kn">import</span> <span class="n">ClientSession</span>
<span class="kn">from</span> <span class="nn">mcp.types</span> <span class="kn">import</span> <span class="n">ElicitResult</span>
<span class="kn">from</span> <span class="nn">mcp.client.sse</span> <span class="kn">import</span> <span class="n">sse_client</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">elicitation_callback</span><span class="p">(</span>
    <span class="n">context</span><span class="p">,</span>
    <span class="n">params</span>
<span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'elicitation_callback:context: </span><span class="si">{</span><span class="n">context</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'elicitation_callback:params: </span><span class="si">{</span><span class="n">params</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="s">"Input something about"</span> <span class="ow">in</span> <span class="n">params</span><span class="p">.</span><span class="n">message</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">ElicitResult</span><span class="p">(</span>
            <span class="n">action</span><span class="o">=</span><span class="s">"accept"</span><span class="p">,</span>
            <span class="n">content</span><span class="o">=</span><span class="p">{</span><span class="s">"input"</span><span class="p">:</span> <span class="s">"def"</span><span class="p">},</span>
        <span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">ElicitResult</span><span class="p">(</span><span class="n">action</span><span class="o">=</span><span class="s">"decline"</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'elicitation_callback:result: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">result</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">mcp_url</span> <span class="o">=</span> <span class="s">'http://127.0.0.1:12345/sse'</span>

    <span class="k">async</span> <span class="k">with</span> <span class="n">sse_client</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="n">mcp_url</span><span class="p">)</span> <span class="k">as</span> <span class="n">streams</span><span class="p">:</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">ClientSession</span><span class="p">(</span>
            <span class="o">*</span><span class="n">streams</span><span class="p">,</span>
            <span class="n">elicitation_callback</span><span class="o">=</span><span class="n">elicitation_callback</span>  <span class="c1"># 关键点
</span>        <span class="p">)</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
            <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="n">initialize</span><span class="p">()</span>
            <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="n">call_tool</span><span class="p">(</span>
                <span class="s">'my_tool'</span><span class="p">,</span> <span class="p">{</span><span class="s">'prompt'</span><span class="p">:</span> <span class="s">'abc'</span><span class="p">}</span>
            <span class="p">)</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'</span><span class="se">\n</span><span class="s">result: </span><span class="si">{</span><span class="n">result</span><span class="p">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">text</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>

</code></pre></div></div>

<h3 id="运行结果">运行结果</h3>

<pre><code class="language-log">elicitation_callback:context: RequestContext(request_id=0, meta=None, session=&lt;mcp.client.session.ClientSession object at 0x78a46440e270&gt;, lifespan_context=None, request=None)
elicitation_callback:params: meta=None message='Input something about abc' requestedSchema={'properties': {'input': {'description': 'some infomation', 'title': 'Input', 'type': 'string'}}, 'required': ['input'], 'title': 'InputSchema', 'type': 'object'}
elicitation_callback:result: meta=None action='accept' content={'input': 'def'}

result: [SUCCESS] abc: def
</code></pre>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="AI" /><category term="MCP" /><category term="Python" /><summary type="html"><![CDATA[Elicitation的具体介绍可以参照MCP的规范文档。 该功能允许 MCP 服务器在工具执行过程中暂停，并通过客户端向用户请求额外的输入信息，非常适合处理需要人工介入或确认的场景。]]></summary></entry><entry><title type="html">Python 脚本直接调用摩卡社区的 Hosted 的 MCP 服务</title><link href="http://blog.zeerd.com/mcp-client-sse/" rel="alternate" type="text/html" title="Python 脚本直接调用摩卡社区的 Hosted 的 MCP 服务" /><published>2025-08-13T00:00:00+08:00</published><updated>2025-08-13T00:00:00+08:00</updated><id>http://blog.zeerd.com/mcp-client-sse</id><content type="html" xml:base="http://blog.zeerd.com/mcp-client-sse/"><![CDATA[<p><a href="https://github.com/theailanguage/mcp_client">这里</a>有一些例子，但是实现的有限繁琐。
因此，我将核心代码抽出来看看。</p>

<!--break-->

<p>例如：<a href="https://www.modelscope.cn/mcp/servers/@modelcontextprotocol/fetch">Fetch网页内容抓取</a></p>

<p>页面右侧可以生成一个 URL ，类似：<code class="language-html highlighter-rouge">https://mcp.api-inference.modelscope.net/xxxxx/sse</code></p>

<p>运行下面脚本时， mcp_url 传入这个 URL； target_url 传入想获取的网址</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="kn">from</span> <span class="nn">mcp</span> <span class="kn">import</span> <span class="n">ClientSession</span>
<span class="kn">from</span> <span class="nn">mcp.client.sse</span> <span class="kn">import</span> <span class="n">sse_client</span>


<span class="k">class</span> <span class="nc">FetchMarkdown</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mcp_url</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">mcp_url</span> <span class="o">=</span> <span class="n">mcp_url</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target_url</span><span class="p">):</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">sse_client</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">mcp_url</span><span class="p">)</span> <span class="k">as</span> <span class="n">streams</span><span class="p">:</span>
            <span class="k">async</span> <span class="k">with</span> <span class="n">ClientSession</span><span class="p">(</span><span class="o">*</span><span class="n">streams</span><span class="p">)</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
                <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="n">initialize</span><span class="p">()</span>
                <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="n">list_tools</span><span class="p">()</span>
                <span class="n">tools</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">tools</span>
                <span class="n">tool_names</span> <span class="o">=</span> <span class="p">[</span><span class="n">tool</span><span class="p">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">tool</span> <span class="ow">in</span> <span class="n">tools</span><span class="p">]</span>
                <span class="k">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">Connected with tools:"</span><span class="p">,</span> <span class="n">tool_names</span><span class="p">)</span>

                <span class="k">if</span> <span class="s">'fetch'</span> <span class="ow">in</span> <span class="n">tool_names</span><span class="p">:</span>
                    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="n">call_tool</span><span class="p">(</span>
                        <span class="s">'fetch'</span><span class="p">,</span> <span class="p">{</span><span class="s">'url'</span><span class="p">:</span> <span class="n">target_url</span><span class="p">}</span>
                    <span class="p">)</span>
                    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'</span><span class="se">\n</span><span class="s">result:</span><span class="se">\n</span><span class="si">{</span><span class="n">result</span><span class="p">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">text</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">mcp_url</span> <span class="o">=</span> <span class="p">(</span>
        <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span>
        <span class="k">else</span> <span class="s">'https://mcp.api-inference.modelscope.net/xxxxx/sse'</span>
    <span class="p">)</span>
    <span class="n">target_url</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span> <span class="k">else</span> <span class="s">'https://example.org'</span>
    <span class="n">fetcher</span> <span class="o">=</span> <span class="n">FetchMarkdown</span><span class="p">(</span>
        <span class="n">mcp_url</span><span class="o">=</span><span class="n">mcp_url</span>
    <span class="p">)</span>
    <span class="k">await</span> <span class="n">fetcher</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">target_url</span><span class="p">)</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="n">asyncio</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</code></pre></div></div>

<p>核心就是 <a href="https://modelcontextprotocol.io/">mcp</a> 的<code class="language-html highlighter-rouge">Python SDK</code>提供了一系列类似 <a href="https://github.com/modelcontextprotocol/python-sdk/blob/v1.12.4/src/mcp/client/sse.py#L24">sse_client</a> 的接口。根据需求选用。</p>

<p>比如<a href="https://smithery.ai">smithery.ai</a>的服务基本上就是使用 <a href="https://github.com/modelcontextprotocol/python-sdk/blob/v1.12.4/src/mcp/client/streamable_http.py#L441">streamablehttp_client</a> 来访问。</p>

<p>下附一个基本的MCP Server框架代码（就不额外开个帖子了）：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="kn">from</span> <span class="nn">mcp.server.fastmcp</span> <span class="kn">import</span> <span class="n">FastMCP</span>

<span class="n">mcp</span> <span class="o">=</span> <span class="n">FastMCP</span><span class="p">(</span><span class="s">"index"</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8088</span><span class="p">,</span> <span class="n">log_level</span><span class="o">=</span><span class="s">'DEBUG'</span><span class="p">)</span>  <span class="c1"># 创建 MCP 服务器
</span><span class="n">g_indexes</span> <span class="o">=</span> <span class="p">[]</span>


<span class="o">@</span><span class="n">mcp</span><span class="p">.</span><span class="n">tool</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="n">indexes</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="k">global</span> <span class="n">g_indexes</span>
    <span class="n">g_indexes</span> <span class="o">=</span> <span class="n">indexes</span><span class="p">.</span><span class="n">copy</span><span class="p">()</span>
    <span class="k">return</span> <span class="bp">True</span>


<span class="o">@</span><span class="n">mcp</span><span class="p">.</span><span class="n">tool</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
    <span class="k">return</span> <span class="n">g_indexes</span> <span class="o">+</span> <span class="p">[</span><span class="n">text</span><span class="p">]</span>


<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="n">mcp</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">transport</span><span class="o">=</span><span class="s">'sse'</span><span class="p">)</span>  <span class="c1"># 启动服务！
</span>
</code></pre></div></div>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="AI" /><category term="MCP" /><category term="Python" /><summary type="html"><![CDATA[这里有一些例子，但是实现的有限繁琐。 因此，我将核心代码抽出来看看。]]></summary></entry><entry><title type="html">大语言模型 API： 工具调用</title><link href="http://blog.zeerd.com/llm-api-tools/" rel="alternate" type="text/html" title="大语言模型 API： 工具调用" /><published>2025-07-31T00:00:00+08:00</published><updated>2025-07-31T00:00:00+08:00</updated><id>http://blog.zeerd.com/llm-api-tools</id><content type="html" xml:base="http://blog.zeerd.com/llm-api-tools/"><![CDATA[<p><a href="https://github.com/ollama/ollama-python/blob/main/examples/tools.py">官方例子</a>在此。
<!--break--></p>

<p>实际使用中，我发现<code class="language-html highlighter-rouge">Qwen</code>模型<code class="language-html highlighter-rouge">format</code>参数和<code class="language-html highlighter-rouge">tools</code>参数同时存在时，<code class="language-html highlighter-rouge">tools</code>参数会失效。
这个现象目前在 Qwen（尤其是开源本地部署版本如 Qwen3-32B）中是已知的行为特征。</p>

<p>因此，需要区分传入：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="c1"># pip install ollama genson
</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="kn">from</span> <span class="nn">ollama</span> <span class="kn">import</span> <span class="n">Client</span>
<span class="kn">from</span> <span class="nn">genson</span> <span class="kn">import</span> <span class="n">SchemaBuilder</span>

<span class="n">host</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s">'localhost'</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span> <span class="k">else</span> <span class="s">'qwen3-8b'</span>

<span class="n">client</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span>
    <span class="n">host</span><span class="o">=</span><span class="sa">f</span><span class="s">'http://</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s">:11434'</span>
<span class="p">)</span>
<span class="n">messages</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span>
        <span class="s">"content"</span><span class="p">:</span> <span class="s">"""
        你支持以下工具：
        1) 给定函数名获取函数的完整声明。
        请遵守：
        - 当问题涉及未知函数时必用工具
        - 不要解释工具机制
        """</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span>
        <span class="s">"content"</span><span class="p">:</span> <span class="p">(</span>
            <span class="s">"编写一个演示如何使用make_joke和send_joke的示例程序(sample-code)。"</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">]</span>


<span class="k">def</span> <span class="nf">get_decl</span><span class="p">(</span><span class="n">function_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="n">functions</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"make_joke"</span><span class="p">:</span> <span class="s">'const std::string&amp; make_joke(void);'</span><span class="p">,</span>
        <span class="s">"send_joke"</span><span class="p">:</span> <span class="s">'bool send_joke(const std::string&amp; joke);'</span><span class="p">,</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">functions</span><span class="p">[</span><span class="n">function_name</span><span class="p">]</span>


<span class="c1"># 定义工具函数注册表
</span><span class="n">tool_func_map</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"get_decl"</span><span class="p">:</span> <span class="n">get_decl</span><span class="p">,</span>
    <span class="c1"># 以后可以加更多工具
</span><span class="p">}</span>


<span class="k">def</span> <span class="nf">build_format</span><span class="p">(</span><span class="n">formats</span><span class="p">):</span>
    <span class="n">builder</span> <span class="o">=</span> <span class="n">SchemaBuilder</span><span class="p">()</span>
    <span class="n">builder</span><span class="p">.</span><span class="n">add_object</span><span class="p">(</span><span class="n">formats</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">builder</span><span class="p">.</span><span class="n">to_schema</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">))</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">chat</span><span class="p">(</span>
        <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span>
        <span class="n">messages</span><span class="o">=</span><span class="n">messages</span><span class="p">,</span>
        <span class="n">tools</span><span class="o">=</span><span class="p">[</span>
            <span class="p">{</span>
                <span class="s">"function"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s">"name"</span><span class="p">:</span> <span class="s">"get_decl"</span><span class="p">,</span>
                    <span class="s">"parameters"</span><span class="p">:</span> <span class="p">{</span>
                        <span class="s">"properties"</span><span class="p">:</span> <span class="p">{</span>
                            <span class="s">"function_name"</span><span class="p">:</span> <span class="p">{</span><span class="s">"type"</span><span class="p">:</span> <span class="s">"string"</span><span class="p">}</span>
                        <span class="p">},</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"object"</span><span class="p">,</span>
                    <span class="p">}</span>
                <span class="p">},</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"function"</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">],</span>
        <span class="nb">format</span><span class="o">=</span><span class="nb">format</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">response</span>


<span class="k">def</span> <span class="nf">call_function</span><span class="p">(</span><span class="n">response</span><span class="p">):</span>
    <span class="n">tool_responses</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="c1"># 正确追加 assistant 回复
</span>    <span class="n">assistant_message</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"role"</span><span class="p">:</span> <span class="s">"assistant"</span><span class="p">,</span>
        <span class="s">"content"</span><span class="p">:</span> <span class="n">response</span><span class="p">[</span><span class="s">'message'</span><span class="p">].</span><span class="n">get</span><span class="p">(</span><span class="s">'content'</span><span class="p">,</span> <span class="s">''</span><span class="p">),</span>
        <span class="s">'tool_calls'</span><span class="p">:</span> <span class="n">response</span><span class="p">[</span><span class="s">'message'</span><span class="p">][</span><span class="s">'tool_calls'</span><span class="p">]</span>
    <span class="p">}</span>
    <span class="n">tool_responses</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">assistant_message</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">call</span> <span class="ow">in</span> <span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">tool_calls</span><span class="p">:</span>
        <span class="n">func_name</span> <span class="o">=</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">name</span>
        <span class="n">func_args</span> <span class="o">=</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">arguments</span>
        <span class="c1"># 动态查找并调用
</span>        <span class="k">if</span> <span class="n">func_name</span> <span class="ow">in</span> <span class="n">tool_func_map</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="n">tool_func_map</span><span class="p">[</span><span class="n">func_name</span><span class="p">](</span><span class="o">**</span><span class="n">func_args</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="p">{</span><span class="s">"error"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"未找到工具函数: </span><span class="si">{</span><span class="n">func_name</span><span class="si">}</span><span class="s">"</span><span class="p">}</span>

        <span class="n">tool_responses</span><span class="p">.</span><span class="n">append</span><span class="p">({</span>
            <span class="s">"role"</span><span class="p">:</span> <span class="s">"tool"</span><span class="p">,</span>
            <span class="s">"content"</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="bp">False</span><span class="p">),</span>
            <span class="s">'tool_name'</span><span class="p">:</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">name</span>
        <span class="p">})</span>
    <span class="k">return</span> <span class="n">tool_responses</span>


<span class="c1"># 注意：第一次提问期待AI调用工具，这次不能带format。
</span><span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span>

<span class="c1"># 检查是否返回了工具调用指令
</span><span class="k">if</span> <span class="s">'tool_calls'</span> <span class="ow">in</span> <span class="n">response</span><span class="p">[</span><span class="s">'message'</span><span class="p">]:</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"✅ </span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s"> 支持工具调用"</span><span class="p">)</span>

    <span class="n">tool_responses</span> <span class="o">=</span> <span class="n">call_function</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
    <span class="n">messages</span><span class="p">.</span><span class="n">extend</span><span class="p">(</span><span class="n">tool_responses</span><span class="p">)</span>

    <span class="c1"># 第二次提问，期待AI回复最终答案，这次可以带format
</span>    <span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="n">build_format</span><span class="p">({</span><span class="s">"sample-code"</span><span class="p">:</span> <span class="s">""</span><span class="p">}))</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"---</span><span class="se">\n</span><span class="s">最终回复内容: </span><span class="se">\n</span><span class="si">{</span><span class="n">response</span><span class="p">[</span><span class="s">'sample-code'</span><span class="p">]</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">'response=</span><span class="si">{</span><span class="n">response</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"❌ </span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s"> 不支持工具调用"</span><span class="p">)</span>
</code></pre></div></div>

<p>输出结果（某一次）：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="syntax"><code>---
最终回复内容: 
int main() {
    std::string my_joke = make_joke();
    if (send_joke(my_joke)) {
        std::cout <span class="nt">&lt;</span><span class="err">&lt;</span> <span class="err">"</span><span class="na">Joke</span> <span class="na">sent</span> <span class="na">successfully.</span><span class="err">";</span>
    <span class="err">}</span> <span class="na">else</span> <span class="err">{</span>
        <span class="na">std::cout</span> <span class="err">&lt;&lt;</span> <span class="err">"</span><span class="na">Failed</span> <span class="na">to</span> <span class="na">send</span> <span class="na">joke.</span><span class="err">";</span>
    <span class="err">}</span>
    <span class="na">return</span> <span class="err">0;</span>
<span class="err">}</span>
</code></pre></div></div>

<p>继续使用，又会发现新的问题：模型可能会分多次来调用工具。</p>

<p>这使得我们无法掌控附加 format 的时机。</p>

<p>目前最可靠的方法：</p>

<ul>
  <li>第一阶段：只用 tools，不用 format → 让模型自由发起多轮工具调用</li>
  <li>最后一轮：启用 format=”json” → 强制最终输出为 JSON 结构</li>
</ul>

<p>问题：</p>

<p>这样会浪费一次请求。
因为最后一次回复 tool 没有附带 format ，导致这次的回答不可用。
必须再追加一次提问，这一次附带 format 。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="c1"># 这次尝试使用 openai 的 API
# pip install openai genson
</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="kn">from</span> <span class="nn">openai</span> <span class="kn">import</span> <span class="n">OpenAI</span>
<span class="kn">from</span> <span class="nn">genson</span> <span class="kn">import</span> <span class="n">SchemaBuilder</span>

<span class="n">host</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s">'localhost:11434'</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span> <span class="k">else</span> <span class="s">'qwen3:32b'</span>

<span class="n">client</span> <span class="o">=</span> <span class="n">OpenAI</span><span class="p">(</span>
    <span class="n">base_url</span><span class="o">=</span><span class="sa">f</span><span class="s">"http://</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s">/v1"</span><span class="p">,</span>
    <span class="n">api_key</span><span class="o">=</span><span class="s">"None"</span>  <span class="c1"># 任意字符串（服务端无需认证）
</span><span class="p">)</span>

<span class="n">messages</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span>
        <span class="s">"content"</span><span class="p">:</span> <span class="s">"""
        你支持以下工具：
        1) 给定函数名获取函数的完整声明。

        当你为某个函数（例如A函数）生成单元测试用例时，请遵循以下指导原则：
        1. 使用tools功能获取目标函数（例如A函数）的声明信息。
        2. 在分析该函数的过程中，仔细检查是否存在对其他函数的调用或依赖。这包括但不限于直接函数调用、利用其他函数返回的数据等情况。
        3. 每当识别到一个依赖函数（例如B函数），无论何时何地，在继续生成测试用例之前，立即使用tools功能查询并获取该依赖函数的声明信息。
        4. 将新获取的依赖函数声明信息纳入考量范围，确保生成的测试用例能够正确处理依赖关系，并覆盖所有可能的情况。
        5. 重复步骤2至步骤4，直到完成对该函数及其所有依赖项的分析，并生成完整的单元测试用例。
        """</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span>
        <span class="s">"content"</span><span class="p">:</span> <span class="p">(</span>
            <span class="s">"已知存在已经实现了的send_joke函数。</span><span class="se">\n</span><span class="s">"</span>
            <span class="s">"参照如下设计图编写一个演示如何使用send_joke的示例程序(sample-code)。"</span>
            <span class="s">"如果遇到了任何不存在的函数，请通过tools获取对应的函数声明。</span><span class="se">\n</span><span class="s">"</span>
            <span class="s">"""
            ```mermaid
            graph TD
                A[make_joke] --&gt; B[send_joke]
                A --&gt; C[print]
                B --&gt; D[print]
            ```
            """</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">]</span>


<span class="k">def</span> <span class="nf">get_decl</span><span class="p">(</span><span class="n">function_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="n">functions</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"make_joke"</span><span class="p">:</span> <span class="p">{</span>
            <span class="s">"decl"</span><span class="p">:</span> <span class="s">'const std::string&amp; make_joke(void);'</span><span class="p">,</span>
            <span class="s">"header_file"</span><span class="p">:</span> <span class="s">"joke_generator.h"</span>
        <span class="p">},</span>
        <span class="s">"send_joke"</span><span class="p">:</span> <span class="p">{</span>
            <span class="s">"decl"</span><span class="p">:</span> <span class="s">'bool send_joke(const std::string&amp; joke);'</span><span class="p">,</span>
            <span class="s">"header_file"</span><span class="p">:</span> <span class="s">"joke_generator.h"</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"✅ get_decl(</span><span class="si">{</span><span class="n">function_name</span><span class="si">}</span><span class="s">)"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">functions</span><span class="p">[</span><span class="n">function_name</span><span class="p">]</span>


<span class="c1"># 定义工具函数注册表
</span><span class="n">tool_func_map</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"get_decl"</span><span class="p">:</span> <span class="n">get_decl</span><span class="p">,</span>
    <span class="c1"># 以后可以加更多工具
</span><span class="p">}</span>


<span class="k">def</span> <span class="nf">build_format</span><span class="p">(</span><span class="n">formats</span><span class="p">):</span>
    <span class="n">builder</span> <span class="o">=</span> <span class="n">SchemaBuilder</span><span class="p">()</span>
    <span class="n">builder</span><span class="p">.</span><span class="n">add_object</span><span class="p">(</span><span class="n">formats</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">builder</span><span class="p">.</span><span class="n">to_schema</span><span class="p">()</span>


<span class="k">def</span> <span class="nf">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span>
        <span class="s">'✅ chat:</span><span class="se">\n</span><span class="s">'</span> <span class="o">+</span>
        <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="nb">str</span><span class="p">)</span>
    <span class="p">)</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">chat</span><span class="p">.</span><span class="n">completions</span><span class="p">.</span><span class="n">create</span><span class="p">(</span>
        <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span>
        <span class="n">messages</span><span class="o">=</span><span class="n">messages</span><span class="p">,</span>
        <span class="n">tools</span><span class="o">=</span><span class="p">[</span>
            <span class="p">{</span>
                <span class="s">"function"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s">"name"</span><span class="p">:</span> <span class="s">"get_decl"</span><span class="p">,</span>
                    <span class="s">"parameters"</span><span class="p">:</span> <span class="p">{</span>
                        <span class="s">"properties"</span><span class="p">:</span> <span class="p">{</span>
                            <span class="s">"function_name"</span><span class="p">:</span> <span class="p">{</span><span class="s">"type"</span><span class="p">:</span> <span class="s">"string"</span><span class="p">}</span>
                        <span class="p">},</span>
                        <span class="s">"type"</span><span class="p">:</span> <span class="s">"object"</span><span class="p">,</span>
                    <span class="p">}</span>
                <span class="p">},</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="s">"function"</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">],</span>
        <span class="n">response_format</span><span class="o">=</span><span class="p">{</span>
            <span class="s">'type'</span><span class="p">:</span> <span class="s">'json_schema'</span><span class="p">,</span>
            <span class="s">'json_schema'</span><span class="p">:</span>
            <span class="p">{</span>
                <span class="s">"name"</span><span class="p">:</span> <span class="s">'sample-code'</span><span class="p">,</span>
                <span class="s">"schema"</span><span class="p">:</span> <span class="nb">format</span>
            <span class="p">}</span>
        <span class="p">},</span>
    <span class="p">)</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>


<span class="k">def</span> <span class="nf">call_function</span><span class="p">(</span><span class="n">response</span><span class="p">):</span>
    <span class="n">tool_responses</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="c1"># 正确追加 assistant 回复
</span>    <span class="n">assistant_message</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"role"</span><span class="p">:</span> <span class="s">"assistant"</span><span class="p">,</span>
        <span class="s">"content"</span><span class="p">:</span> <span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span><span class="p">,</span>
        <span class="s">'tool_calls'</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span>
                <span class="s">"id"</span><span class="p">:</span> <span class="n">call</span><span class="p">.</span><span class="nb">id</span><span class="p">,</span>
                <span class="s">"type"</span><span class="p">:</span> <span class="n">call</span><span class="p">.</span><span class="nb">type</span><span class="p">,</span>
                <span class="s">"function"</span><span class="p">:</span> <span class="p">{</span>
                    <span class="s">"name"</span><span class="p">:</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">name</span><span class="p">,</span>
                    <span class="s">"arguments"</span><span class="p">:</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">arguments</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="k">for</span> <span class="n">call</span> <span class="ow">in</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">tool_calls</span> <span class="ow">or</span> <span class="p">[])</span>
        <span class="p">]</span>
    <span class="p">}</span>
    <span class="n">tool_responses</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">assistant_message</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">call</span> <span class="ow">in</span> <span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">tool_calls</span><span class="p">:</span>
        <span class="n">func_name</span> <span class="o">=</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">name</span>
        <span class="n">func_args</span> <span class="o">=</span> <span class="n">call</span><span class="p">.</span><span class="n">function</span><span class="p">.</span><span class="n">arguments</span>
        <span class="c1"># 如果func_args是字符串，尝试解析为字典
</span>        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">func_args</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
            <span class="n">func_args</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">func_args</span><span class="p">)</span>

        <span class="c1"># 动态查找并调用
</span>        <span class="k">if</span> <span class="n">func_name</span> <span class="ow">in</span> <span class="n">tool_func_map</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="n">tool_func_map</span><span class="p">[</span><span class="n">func_name</span><span class="p">](</span><span class="o">**</span><span class="n">func_args</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="p">{</span><span class="s">"error"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"未找到工具函数: </span><span class="si">{</span><span class="n">func_name</span><span class="si">}</span><span class="s">"</span><span class="p">}</span>

        <span class="n">tool_responses</span><span class="p">.</span><span class="n">append</span><span class="p">({</span>
            <span class="s">"role"</span><span class="p">:</span> <span class="s">"tool"</span><span class="p">,</span>
            <span class="s">"content"</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="bp">False</span><span class="p">),</span>
            <span class="s">'tool_call_id'</span><span class="p">:</span> <span class="n">call</span><span class="p">.</span><span class="nb">id</span>
        <span class="p">})</span>
    <span class="k">return</span> <span class="n">tool_responses</span>


<span class="c1"># 注意：第一次提问期待AI调用工具，这次不能带format。
</span><span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span>
<span class="c1"># 循环处理所有 tool_calls，直到拿到最终 content
</span><span class="k">while</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">,</span> <span class="s">"tool_calls"</span><span class="p">,</span> <span class="bp">None</span><span class="p">):</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"✅ </span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s"> 支持工具调用"</span><span class="p">)</span>
    <span class="n">tool_responses</span> <span class="o">=</span> <span class="n">call_function</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
    <span class="n">messages</span><span class="p">.</span><span class="n">extend</span><span class="p">(</span><span class="n">tool_responses</span><span class="p">)</span>
    <span class="c1"># 再次提问，如果AI继续调用工具，则循环；
</span>    <span class="c1"># 否则，这次回答的是我们的问题，但是不带 format 只能忽略（浪费一次 token )
</span>    <span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span>

<span class="c1"># 重新提问，这次附带 format 。
</span><span class="n">final_prompt</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span>
    <span class="s">"content"</span><span class="p">:</span> <span class="p">(</span>
        <span class="s">"现在你已获取所有依赖函数信息，请生成测试用例，输出格式必须为 JSON。"</span>
    <span class="p">)</span>
<span class="p">}</span>
<span class="n">messages</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">final_prompt</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="p">(</span><span class="n">messages</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="n">build_format</span><span class="p">({</span><span class="s">"sample-code"</span><span class="p">:</span> <span class="s">""</span><span class="p">}))</span>

<span class="n">res</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span>
<span class="k">try</span><span class="p">:</span>
    <span class="n">res</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">res</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"---</span><span class="se">\n</span><span class="s">最终回复内容: </span><span class="se">\n</span><span class="si">{</span><span class="n">res</span><span class="p">[</span><span class="s">'sample-code'</span><span class="p">]</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">except</span> <span class="n">json</span><span class="p">.</span><span class="n">JSONDecodeError</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"❌ 无法解析JSON格式的回复: </span><span class="si">{</span><span class="n">response</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="n">response</span> <span class="o">=</span> <span class="p">{</span><span class="s">'sample-code'</span><span class="p">:</span> <span class="n">response</span><span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="AI" /><category term="Ollama" /><category term="OpenAI" /><category term="API" /><category term="Python" /><summary type="html"><![CDATA[官方例子在此。]]></summary></entry><entry><title type="html">大语言模型 API： 结构化输出</title><link href="http://blog.zeerd.com/llm-api-format/" rel="alternate" type="text/html" title="大语言模型 API： 结构化输出" /><published>2025-07-26T00:00:00+08:00</published><updated>2025-07-26T00:00:00+08:00</updated><id>http://blog.zeerd.com/llm-api-format</id><content type="html" xml:base="http://blog.zeerd.com/llm-api-format/"><![CDATA[<p>官方提供了通过 <a href="https://github.com/ollama/ollama-python/blob/main/examples/structured-outputs.py">BaseModel</a> 规范输出格式的例子。
<!--break--></p>

<p>但是，实际使用的情况下，很多时候提示词都是从模板中读取出来的，输出格式也不是限定的，需要根据实际情况动态生成。而无法预先通过<code class="language-html highlighter-rouge">BaseModel</code>为每种情况创建子类。</p>

<p>下面，给出了一个借助<code class="language-html highlighter-rouge">Genson</code>动态生成<code class="language-html highlighter-rouge">JSON Schema</code>的例子，用于辅助<code class="language-html highlighter-rouge">Ollama</code>进行输出内容的规范化。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="c1"># pip install ollama genson
</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">ollama</span> <span class="kn">import</span> <span class="n">Client</span>
<span class="kn">from</span> <span class="nn">genson</span> <span class="kn">import</span> <span class="n">SchemaBuilder</span>

<span class="c1"># 提供一个回答问题的格式模板
# 这里的值（即例子中的0、""和False）并本身不重要，重要的是他们表达了自己的类型
</span><span class="n">json_data</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"Summary"</span><span class="p">:</span> <span class="s">""</span><span class="p">,</span>
    <span class="s">"Points"</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span>
            <span class="s">"Percent"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
            <span class="s">"Abstract"</span><span class="p">:</span> <span class="s">""</span><span class="p">,</span>
            <span class="s">"Key"</span><span class="p">:</span> <span class="bp">False</span>
        <span class="p">}</span>
    <span class="p">]</span>
<span class="p">}</span>

<span class="c1"># 使用 Genson 库生成 JSON Schema
# 这将生成一个标准的 JSON Schema ，描述了 json_data 的结构
</span><span class="n">builder</span> <span class="o">=</span> <span class="n">SchemaBuilder</span><span class="p">()</span>
<span class="n">builder</span><span class="p">.</span><span class="n">add_object</span><span class="p">(</span><span class="n">json_data</span><span class="p">)</span>
<span class="n">fmt</span> <span class="o">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">to_schema</span><span class="p">()</span>

<span class="n">client</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'http://localhost:11434'</span><span class="p">)</span>
<span class="c1"># 简单起见，直接读取本脚本自身作为提示词的一部分
</span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">__file__</span><span class="p">),</span> <span class="s">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>

<span class="c1"># 调用 ollama 接口提问
</span><span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">chat</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="s">'qwen3:32b'</span><span class="p">,</span>
    <span class="n">messages</span><span class="o">=</span><span class="p">[</span>
        <span class="p">{</span>
            <span class="s">'role'</span><span class="p">:</span> <span class="s">'user'</span><span class="p">,</span>
            <span class="s">'content'</span><span class="p">:</span> <span class="p">(</span>
                <span class="s">"请对下面代码进行审查。</span><span class="se">\n</span><span class="s">"</span>
                <span class="s">"首先，使用一句话，总结一下全文(Summary)。</span><span class="se">\n</span><span class="s">"</span>
                <span class="s">"然后，概括出最多不超过3条的摘要(Abstract)，并逐一列出它们。"</span>
                <span class="s">"说明每个摘要占用篇幅的百分比(Percent)及是否为关键点(Key)。</span><span class="se">\n</span><span class="s">"</span>
                <span class="sa">f</span><span class="s">"---</span><span class="se">\n</span><span class="si">{</span><span class="n">content</span><span class="si">}</span><span class="se">\n</span><span class="s">---</span><span class="se">\n</span><span class="s">"</span>
            <span class="p">)</span>
        <span class="p">},</span>
    <span class="p">],</span>
    <span class="nb">format</span><span class="o">=</span><span class="n">fmt</span><span class="p">,</span>
<span class="p">)</span>

<span class="c1"># 输出反馈的答案，验证答案的格式是否与前文的“json_data”期望的一致
</span><span class="n">response_json</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">content</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">response_json</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">))</span>
</code></pre></div></div>

<p>输出如下：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"Summary"</span><span class="p">:</span><span class="w"> </span><span class="s2">"该代码使用 Ollama 和 Genson 库来审查代码，生成一个包含总结和摘要的 JSON 格式反馈，其中摘要部分包括百分比和关键点标志。"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Points"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Percent"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Abstract"</span><span class="p">:</span><span class="w"> </span><span class="s2">"导入了必要的库，如 json、os、ollama 和 genson，并定义了一个 JSON 数据模板。"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Percent"</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Abstract"</span><span class="p">:</span><span class="w"> </span><span class="s2">"使用 Genson 库生成 JSON Schema，以描述 json_data 的结构。"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Percent"</span><span class="p">:</span><span class="w"> </span><span class="mi">60</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Abstract"</span><span class="p">:</span><span class="w"> </span><span class="s2">"通过 Ollama 接口调用模型进行代码审查，传递提示词和格式要求，并输出验证结果。"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>如果使用的是<code class="language-html highlighter-rouge">OpenAI</code>的接口，则需要将<code class="language-html highlighter-rouge">format=fmt</code>替换成：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="n">response_format</span><span class="o">=</span><span class="p">{</span>
    <span class="s">'type'</span><span class="p">:</span> <span class="s">'json_schema'</span><span class="p">,</span>
    <span class="s">'json_schema'</span><span class="p">:</span>
    <span class="p">{</span>
        <span class="s">"schema"</span><span class="p">:</span> <span class="n">fmt</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="AI" /><category term="Ollama" /><category term="OpenAI" /><category term="API" /><category term="Python" /><summary type="html"><![CDATA[官方提供了通过 BaseModel 规范输出格式的例子。]]></summary></entry><entry><title type="html">跟跑 Ryzen AI Software Getting Start 的一些记录</title><link href="http://blog.zeerd.com/amd-ryzen-ai-note/" rel="alternate" type="text/html" title="跟跑 Ryzen AI Software Getting Start 的一些记录" /><published>2025-03-23T00:00:00+08:00</published><updated>2025-03-23T00:00:00+08:00</updated><id>http://blog.zeerd.com/amd-ryzen-ai-note</id><content type="html" xml:base="http://blog.zeerd.com/amd-ryzen-ai-note/"><![CDATA[<!--break-->

<p><a href="https://ryzenai.docs.amd.com/en/1.3/">Ryzen AI Software 1.3</a></p>

<p>下面内容是我研究这些教材时自己琢磨出来的（也有AI告诉的），未必是对的。只是我这么改之后，能用了。</p>

<h2 id="quicktestpy">quicktest.py</h2>

<p>运行<code class="language-html highlighter-rouge">python quicktest.py</code>失败，提示错误：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code>Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
  File <span class="s2">"C:</span><span class="se">\P</span><span class="s2">rogram Files</span><span class="se">\R</span><span class="s2">yzenAI</span><span class="se">\1</span><span class="s2">.3.1</span><span class="se">\q</span><span class="s2">uicktest</span><span class="se">\q</span><span class="s2">uicktest.py"</span>, line 45, <span class="k">in</span> &lt;module&gt;
    apu_type <span class="o">=</span> get_apu_info<span class="o">()</span>
  File <span class="s2">"C:</span><span class="se">\P</span><span class="s2">rogram Files</span><span class="se">\R</span><span class="s2">yzenAI</span><span class="se">\1</span><span class="s2">.3.1</span><span class="se">\q</span><span class="s2">uicktest</span><span class="se">\q</span><span class="s2">uicktest.py"</span>, line 16, <span class="k">in </span>get_apu_info
    <span class="k">if</span> <span class="s1">'PCI\\VEN_1022&amp;DEV_1502&amp;REV_00'</span> <span class="k">in </span>stdout.decode<span class="o">()</span>: apu_type <span class="o">=</span> <span class="s1">'PHX/HPT'</span>
UnicodeDecodeError: <span class="s1">'utf-8'</span> codec can<span class="s1">'t decode byte 0xb9 in position 14: invalid start byte
</span></code></pre></div></div>

<p>原因和解决办法：
脚本默认系统返回的设备信息列表是<code class="language-html highlighter-rouge">UTF-8</code>的。但是中文版Windows返回的是<code class="language-html highlighter-rouge">GBK</code>的。这个可以通过<code class="language-html highlighter-rouge">chcp</code>命令来确认。</p>

<p>解决办法方法：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="syntax"><code>    <span class="n">output</span> <span class="o">=</span> <span class="n">stdout</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="s">"gbk"</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s">"ignore"</span><span class="p">)</span>  <span class="c1"># »ò cp936
</span>    <span class="n">apu_type</span> <span class="o">=</span> <span class="s">''</span>
    <span class="k">if</span> <span class="s">'PCI</span><span class="se">\\</span><span class="s">VEN_1022&amp;DEV_1502&amp;REV_00'</span> <span class="ow">in</span> <span class="n">output</span><span class="p">:</span> <span class="n">apu_type</span> <span class="o">=</span> <span class="s">'PHX/HPT'</span>
    <span class="k">if</span> <span class="s">'PCI</span><span class="se">\\</span><span class="s">VEN_1022&amp;DEV_17F0&amp;REV_00'</span> <span class="ow">in</span> <span class="n">output</span><span class="p">:</span> <span class="n">apu_type</span> <span class="o">=</span> <span class="s">'STX'</span>
    <span class="k">if</span> <span class="s">'PCI</span><span class="se">\\</span><span class="s">VEN_1022&amp;DEV_17F0&amp;REV_10'</span> <span class="ow">in</span> <span class="n">output</span><span class="p">:</span> <span class="n">apu_type</span> <span class="o">=</span> <span class="s">'STX'</span>
    <span class="k">if</span> <span class="s">'PCI</span><span class="se">\\</span><span class="s">VEN_1022&amp;DEV_17F0&amp;REV_11'</span> <span class="ow">in</span> <span class="n">output</span><span class="p">:</span> <span class="n">apu_type</span> <span class="o">=</span> <span class="s">'STX'</span>
    <span class="k">if</span> <span class="s">'PCI</span><span class="se">\\</span><span class="s">VEN_1022&amp;DEV_17F0&amp;REV_20'</span> <span class="ow">in</span> <span class="n">output</span><span class="p">:</span> <span class="n">apu_type</span> <span class="o">=</span> <span class="s">'KRK'</span>
</code></pre></div></div>

<p>按上面内容修改脚本。</p>

<h2 id="加速">加速</h2>

<p>运行<code class="language-html highlighter-rouge">prepare_model_data.py</code>时需要下载一些模型文件。</p>

<p>由于各种原因，默认的网络可能网速不太理想。这时候可以借助一些代理服务器。</p>

<p><code class="language-html highlighter-rouge">Power Shell</code>中设置的方法是（直接在命令行敲）：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="syntax"><code>$env:HTTP_PROXY = "http://username:password@proxy_server:port"
$env:HTTPS_PROXY = "http://username:password@proxy_server:port"
</code></pre></div></div>

<p>可以用<code class="language-html highlighter-rouge">ls Env:</code>检查设置是否成功。</p>

<h2 id="yolov8">yolov8</h2>

<p><a href="https://github.com/amd/RyzenAI-SW/tree/main/tutorial/yolov8/yolov8_python">Yolov8 Python Implementation</a></p>

<p>弄了好半天也没能让 <code class="language-html highlighter-rouge">Jupyter note</code> 找到 <code class="language-html highlighter-rouge">onnxruntime</code>。决定先试验一下<code class="language-html highlighter-rouge">python yolov8.py</code>。结果运行结束之后没有任何图片生成。</p>

<p>这是因为<code class="language-html highlighter-rouge">yolov8.py</code>只是一个性能测试，根本没有将结果保存下来。</p>

<p>参考一下<code class="language-html highlighter-rouge">yolov8.ipynb</code>，在每个<code class="language-html highlighter-rouge">preds</code>后面添加<code class="language-html highlighter-rouge">plot_images</code>的调用。</p>

<p>记得去<code class="language-html highlighter-rouge">yolov8_utils.py</code>里面把<code class="language-html highlighter-rouge">plot_images</code>最后面的<code class="language-html highlighter-rouge">display(annotator.im)</code>删掉。因为命令行下没有<code class="language-html highlighter-rouge">display</code>。</p>

<h3 id="jupyter-notebook">Jupyter Notebook</h3>

<pre><code class="language-base">(base) PS E:\RyzenAI-SW\tutorial\yolov8\yolov8_python&gt; conda create --name yolov8_env --clone ryzen-ai-1.3.1
(base) PS E:\RyzenAI-SW\tutorial\yolov8\yolov8_python&gt; conda activate yolov8_env
(yolov8_env) PS E:\RyzenAI-SW\tutorial\yolov8\yolov8_python&gt; conda install ipykernel -y
(yolov8_env) PS E:\RyzenAI-SW\tutorial\yolov8\yolov8_python&gt; python -m ipykernel install --user --name yolov8_env --display-name yolov8
(yolov8_env) PS E:\RyzenAI-SW\tutorial\yolov8\yolov8_python&gt; jupyter.exe notebook
</code></pre>

<p>注意，打开Jupyter Notebook之后，列出的kernel名字应该是<code class="language-html highlighter-rouge">Python [conda env:yolov8_env]</code>。</p>

<p>如果<code class="language-html highlighter-rouge">ipykernel</code>是在<code class="language-html highlighter-rouge">base</code>环境创建的，名字里面只有<code class="language-html highlighter-rouge">yolov8</code>。这种情况下，notebook里面找不到<code class="language-html highlighter-rouge">ryzen-ai-1.3.1</code>中已经安装好的<code class="language-html highlighter-rouge">onnxruntime</code>。</p>

<p>但是这样，安装<code class="language-html highlighter-rouge">ipykernel</code>的时候，一些其他模块的版本会被调整。其实是不太好的。理论上应该有其他办法。</p>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="AI" /><category term="AMD" /><category term="Ryzen" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">WSL删除文件后磁盘空间释放方法</title><link href="http://blog.zeerd.com/reduce-wsl-disk/" rel="alternate" type="text/html" title="WSL删除文件后磁盘空间释放方法" /><published>2025-02-24T00:00:00+08:00</published><updated>2025-02-24T00:00:00+08:00</updated><id>http://blog.zeerd.com/reduce-wsl-disk</id><content type="html" xml:base="http://blog.zeerd.com/reduce-wsl-disk/"><![CDATA[<!--break-->

<p>在 WSL 中删除大量文件后，系统占用的磁盘空间可能不会立即释放。以下是减少真实系统占用的步骤：</p>

<h2 id="压缩-wsl-虚拟硬盘">压缩 WSL 虚拟硬盘</h2>

<p>WSL 使用虚拟硬盘存储文件，删除文件后空间不会自动释放，需手动压缩。</p>

<h3 id="步骤">步骤：</h3>

<ol>
  <li><strong>关闭 WSL 实例</strong>：
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code>wsl <span class="nt">--shutdown</span>
</code></pre></div>    </div>
  </li>
  <li><strong>优化虚拟硬盘</strong>：
    <ul>
      <li>打开 PowerShell 或命令提示符，执行以下命令：
        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code>diskpart
</code></pre></div>        </div>
      </li>
      <li>在 <code class="language-html highlighter-rouge">diskpart</code> 中运行：
        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="k">select </span>vdisk <span class="nv">file</span><span class="o">=</span><span class="s2">"C:</span><span class="se">\U</span><span class="s2">sers</span><span class="se">\&lt;</span><span class="s2">YourUsername&gt;</span><span class="se">\A</span><span class="s2">ppData</span><span class="se">\L</span><span class="s2">ocal</span><span class="se">\P</span><span class="s2">ackages</span><span class="se">\&lt;</span><span class="s2">DistroPackage&gt;</span><span class="se">\L</span><span class="s2">ocalState</span><span class="se">\e</span><span class="s2">xt4.vhdx"</span>
attach vdisk <span class="nb">readonly
</span>compact vdisk
detach vdisk
<span class="nb">exit</span>
</code></pre></div>        </div>
        <p>将路径替换为你的 WSL 发行版的实际路径。</p>
      </li>
    </ul>
  </li>
</ol>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="Windows" /><category term="WSL2" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">在 Windows 系统的 WSL2 中使用 docker 访问显卡</title><link href="http://blog.zeerd.com/use-gpu-docker-wsl2/" rel="alternate" type="text/html" title="在 Windows 系统的 WSL2 中使用 docker 访问显卡" /><published>2025-02-23T00:00:00+08:00</published><updated>2025-02-23T00:00:00+08:00</updated><id>http://blog.zeerd.com/use-gpu-docker-wsl2</id><content type="html" xml:base="http://blog.zeerd.com/use-gpu-docker-wsl2/"><![CDATA[<!--break-->

<p>关键点就是运行<code class="language-html highlighter-rouge">docker</code>时需要额外添加几个参数：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code>docker run <span class="nt">--rm</span>  <span class="nt">--runtime</span><span class="o">=</span>nvidia <span class="nt">--gpus</span> all ubuntu nvidia-smi
</code></pre></div></div>

<p>这条命令用于在 <code class="language-html highlighter-rouge">Docker</code> 容器中运行 <code class="language-html highlighter-rouge">NVIDIA GPU</code> 相关的操作。以下是各参数的含义：</p>

<ol>
  <li><strong><code class="language-html highlighter-rouge">docker run</code></strong>:
    <ul>
      <li>这是 Docker 命令，用于创建并启动一个新容器。</li>
    </ul>
  </li>
  <li><strong><code class="language-html highlighter-rouge">--rm</code></strong>:
    <ul>
      <li>容器停止后自动删除。适用于临时任务，避免容器残留。</li>
    </ul>
  </li>
  <li><strong><code class="language-html highlighter-rouge">--runtime=nvidia</code></strong>:
    <ul>
      <li>指定使用 NVIDIA 容器运行时，以便容器访问 GPU 资源。</li>
    </ul>
  </li>
  <li><strong><code class="language-html highlighter-rouge">--gpus all</code></strong>:
    <ul>
      <li>允许容器使用所有可用的 GPU。也可以指定特定 GPU，如 <code class="language-html highlighter-rouge">--gpus 2</code> 或 <code class="language-html highlighter-rouge">--gpus "device=0,1"</code>。</li>
    </ul>
  </li>
  <li><strong><code class="language-html highlighter-rouge">ubuntu</code></strong>:
    <ul>
      <li>使用的 Docker 镜像名称，这里是一个 Ubuntu 镜像。</li>
    </ul>
  </li>
  <li><strong><code class="language-html highlighter-rouge">nvidia-smi</code></strong>:
    <ul>
      <li>容器启动后执行的命令，用于显示 GPU 状态和相关信息。</li>
    </ul>
  </li>
</ol>

<p>注意：<code class="language-html highlighter-rouge">WSL</code>中使用的显卡驱动其实是在<code class="language-html highlighter-rouge">Windows</code>中已经安装好了的。虽然不知道原理，但是在<code class="language-html highlighter-rouge">Windows</code>上升级显卡驱动就会自动更新<code class="language-html highlighter-rouge">WSL</code>里面的<code class="language-html highlighter-rouge">Linux</code>驱动。</p>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="AI" /><category term="WSL2" /><category term="Docker" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Ubuntu系统自动删除USB网络共享的默认路由</title><link href="http://blog.zeerd.com/ubuntu-disable-usb-route/" rel="alternate" type="text/html" title="Ubuntu系统自动删除USB网络共享的默认路由" /><published>2025-02-07T00:00:00+08:00</published><updated>2025-02-07T00:00:00+08:00</updated><id>http://blog.zeerd.com/ubuntu-disable-usb-route</id><content type="html" xml:base="http://blog.zeerd.com/ubuntu-disable-usb-route/"><![CDATA[<p>我需要使用安卓手机的“USB网络共享”功能来通过<code class="language-html highlighter-rouge">Ubuntu</code>系统访问手机内容。
但是，不需要使用手机网络来联网。
<!--break--></p>

<p>注意：这个方法时间长了会失效。原因暂时未知。但是可以凑合用了。</p>

<p>但是，<code class="language-html highlighter-rouge">Ubuntu 22.04</code>系统每次在检测到USB网络连接成功之后，都会自动创建一个默认的路由。</p>

<p>如下方法可以实现自动删除这个路由。</p>

<p>创建文件<code class="language-html highlighter-rouge">/etc/NetworkManager/dispatcher.d/01-usb-network-share.sh</code>。
内容如下：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="c">#!/bin/bash</span>

<span class="c"># 查找所有网络接口</span>
<span class="nv">interfaces</span><span class="o">=</span><span class="si">$(</span>ip <span class="nt">-o</span> <span class="nb">link </span>show | <span class="nb">awk</span> <span class="nt">-F</span><span class="s1">': '</span> <span class="s1">'{print $2}'</span><span class="si">)</span>

<span class="c"># 查找 USB 网卡接口（以 enx 开头）</span>
<span class="nv">usb_interface</span><span class="o">=</span><span class="s2">""</span>
<span class="k">for </span>iface <span class="k">in</span> <span class="nv">$interfaces</span><span class="p">;</span> <span class="k">do
    if</span> <span class="o">[[</span> <span class="nv">$iface</span> <span class="o">==</span> enx<span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">usb_interface</span><span class="o">=</span><span class="nv">$iface</span>
        <span class="nb">break
    </span><span class="k">fi
done</span>

<span class="c"># 检查是否找到 USB 网卡接口</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$usb_interface</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
<span class="c">#    echo "未找到 USB 网卡接口。"</span>
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># 从默认路由中删除 USB 网卡</span>
<span class="nb">sudo </span>ip route del default dev <span class="nv">$usb_interface</span>
</code></pre></div></div>

<p>为文件添加可执行权限：<code class="language-html highlighter-rouge">sudo chmod +x /etc/NetworkManager/dispatcher.d/01-usb-network-share.sh</code></p>

<p>重启 <code class="language-html highlighter-rouge">NetworkManager</code> 服务：<code class="language-html highlighter-rouge">sudo systemctl restart NetworkManager</code></p>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="Linux" /><category term="Ubuntu" /><category term="Route" /><summary type="html"><![CDATA[我需要使用安卓手机的“USB网络共享”功能来通过Ubuntu系统访问手机内容。 但是，不需要使用手机网络来联网。]]></summary></entry><entry><title type="html">使用 mailio 收取邮件并保存附件</title><link href="http://blog.zeerd.com/pop3s-attachment/" rel="alternate" type="text/html" title="使用 mailio 收取邮件并保存附件" /><published>2024-10-13T00:00:00+08:00</published><updated>2024-10-13T00:00:00+08:00</updated><id>http://blog.zeerd.com/pop3s-attachment</id><content type="html" xml:base="http://blog.zeerd.com/pop3s-attachment/"><![CDATA[<!--break-->

<p>https://github.com/karastojko/mailio</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="syntax"><code><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;fstream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;filesystem&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;mailio/message.hpp&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;mailio/pop3.hpp&gt;</span><span class="cp">
</span>
<span class="k">using</span> <span class="n">mailio</span><span class="o">::</span><span class="n">codec</span><span class="p">;</span>
<span class="k">using</span> <span class="n">mailio</span><span class="o">::</span><span class="n">dialog_error</span><span class="p">;</span>
<span class="k">using</span> <span class="n">mailio</span><span class="o">::</span><span class="n">message</span><span class="p">;</span>
<span class="k">using</span> <span class="n">mailio</span><span class="o">::</span><span class="n">pop3_error</span><span class="p">;</span>
<span class="k">using</span> <span class="n">mailio</span><span class="o">::</span><span class="n">pop3s</span><span class="p">;</span>
<span class="k">using</span> <span class="n">mailio</span><span class="o">::</span><span class="n">string_t</span><span class="p">;</span>
<span class="k">using</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="p">;</span>
<span class="k">using</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">using</span> <span class="n">std</span><span class="o">::</span><span class="n">ofstream</span><span class="p">;</span>
<span class="k">using</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"%s &lt;pop3.server.address&gt; &lt;port&gt; &lt;user&gt; &lt;pass&gt;</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
        <span class="k">return</span> <span class="n">EXIT_FAILURE</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">try</span>
    <span class="p">{</span>
        <span class="c1">// use a server with SSL connectivity</span>
        <span class="n">pop3s</span> <span class="n">conn</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]));</span>
        <span class="c1">// modify to use real account</span>
        <span class="n">conn</span><span class="p">.</span><span class="n">authenticate</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">argv</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span> <span class="n">pop3s</span><span class="o">::</span><span class="n">auth_method_t</span><span class="o">::</span><span class="n">LOGIN</span><span class="p">);</span>

        <span class="n">pop3s</span><span class="o">::</span><span class="n">message_list_t</span> <span class="n">list</span> <span class="o">=</span> <span class="n">conn</span><span class="p">.</span><span class="n">list</span><span class="p">();</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Message count on server: "</span> <span class="o">&lt;&lt;</span> <span class="n">list</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="n">m</span> <span class="o">:</span> <span class="n">list</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Message "</span> <span class="o">&lt;&lt;</span> <span class="n">m</span><span class="p">.</span><span class="n">first</span> <span class="o">&lt;&lt;</span> <span class="s">" size: "</span> <span class="o">&lt;&lt;</span> <span class="n">m</span><span class="p">.</span><span class="n">second</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>

            <span class="c1">// mail message to store the fetched one</span>
            <span class="n">message</span> <span class="n">msg</span><span class="p">;</span>
            <span class="c1">// set the line policy to mandatory, so longer lines could be parsed</span>
            <span class="n">msg</span><span class="p">.</span><span class="n">line_policy</span><span class="p">(</span><span class="n">codec</span><span class="o">::</span><span class="n">line_len_policy_t</span><span class="o">::</span><span class="n">MANDATORY</span><span class="p">);</span>
            <span class="n">conn</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
            <span class="n">string_t</span> <span class="n">sub</span> <span class="o">=</span> <span class="n">msg</span><span class="p">.</span><span class="n">subject_raw</span><span class="p">();</span>
            <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Received message with subject ["</span> <span class="o">&lt;&lt;</span> <span class="n">sub</span><span class="p">.</span><span class="n">charset</span> <span class="o">&lt;&lt;</span> <span class="s">"]"</span>
                 <span class="o">&lt;&lt;</span> <span class="n">sub</span><span class="p">.</span><span class="n">buffer</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
            <span class="kt">size_t</span> <span class="n">i</span><span class="p">,</span> <span class="n">count</span> <span class="o">=</span> <span class="n">msg</span><span class="p">.</span><span class="n">attachments_size</span><span class="p">();</span>
            <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Attachment : "</span> <span class="o">&lt;&lt;</span> <span class="n">count</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
            <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">string</span> <span class="n">tmp</span> <span class="o">=</span> <span class="n">string</span><span class="p">(</span><span class="s">"attachment"</span><span class="p">)</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">+</span> <span class="s">".tmp"</span><span class="p">;</span>
                <span class="n">ofstream</span> <span class="n">ofs</span><span class="p">(</span><span class="n">tmp</span><span class="p">,</span>
                             <span class="n">std</span><span class="o">::</span><span class="n">ios</span><span class="o">::</span><span class="n">binary</span><span class="p">);</span>
                <span class="n">string_t</span> <span class="n">att</span><span class="p">;</span>
                <span class="n">msg</span><span class="p">.</span><span class="n">attachment</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">ofs</span><span class="p">,</span> <span class="n">att</span><span class="p">);</span>
                <span class="n">string</span> <span class="n">saved</span> <span class="o">=</span> <span class="n">sub</span><span class="p">.</span><span class="n">buffer</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">att</span><span class="p">.</span><span class="n">buffer</span><span class="p">;</span>

                <span class="c1">// make dir by subject and move attachment to the dir</span>
                <span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">create_directory</span><span class="p">(</span><span class="n">sub</span><span class="p">.</span><span class="n">buffer</span><span class="p">);</span>
                <span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">rename</span><span class="p">(</span><span class="n">tmp</span><span class="p">,</span> <span class="n">saved</span><span class="p">);</span>
                <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"Received message with subject `"</span> <span class="o">&lt;&lt;</span> <span class="n">msg</span><span class="p">.</span><span class="n">subject</span><span class="p">()</span>
                     <span class="o">&lt;&lt;</span> <span class="s">"` and attached file `"</span> <span class="o">&lt;&lt;</span> <span class="n">att</span>
                     <span class="o">&lt;&lt;</span> <span class="s">"` saved as `"</span> <span class="o">&lt;&lt;</span> <span class="n">saved</span> <span class="o">&lt;&lt;</span> <span class="s">"`."</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">pop3_error</span> <span class="o">&amp;</span><span class="n">exc</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">exc</span><span class="p">.</span><span class="n">what</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">catch</span> <span class="p">(</span><span class="n">dialog_error</span> <span class="o">&amp;</span><span class="n">exc</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">exc</span><span class="p">.</span><span class="n">what</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Charles Chan</name><email>emneg@zeerd.com</email></author><category term="Program" /><category term="mailio" /><summary type="html"><![CDATA[]]></summary></entry></feed>