<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Edge on Wilson Wu</title><link>https://wilsonwu.me/zh/tags/edge/</link><description>Recent content in Edge on Wilson Wu</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Mon, 15 Dec 2025 00:00:00 -0500</lastBuildDate><atom:link href="https://wilsonwu.me/zh/tags/edge/index.xml" rel="self" type="application/rss+xml"/><item><title>基于 Azure OpenAI 构建智能地址解析 Chrome / Edge 浏览器扩展插件</title><link>https://wilsonwu.me/zh/blog/2025/edge-extension-with-azure-ai/</link><pubDate>Mon, 15 Dec 2025 00:00:00 -0500</pubDate><guid>https://wilsonwu.me/zh/blog/2025/edge-extension-with-azure-ai/</guid><description>&lt;p&gt;在电商和物流领域，有一个不起眼但极其耗时的痛点：&lt;strong&gt;将复杂的地址格式正确的输入到物流系统固定的字段中&lt;/strong&gt;。客户发来的地址格式千奇百怪，有时是逗号分隔，有时连成一行，顺序也完全随机，经常还缺少省份或州等关键信息。&lt;/p&gt;</description><content:encoded><![CDATA[<p>在电商和物流领域，有一个不起眼但极其耗时的痛点：<strong>将复杂的地址格式正确的输入到物流系统固定的字段中</strong>。客户发来的地址格式千奇百怪，有时是逗号分隔，有时连成一行，顺序也完全随机，经常还缺少省份或州等关键信息。</p>
<p>本文我想分享我是如何构建 <strong>Auto Address</strong> 的，这是一个利用 <strong>Azure OpenAI</strong> 的强大能力来解决这个问题的 Chrome 及 Edge 浏览器扩展插件，其界面和使用方式及其简单。</p>
<p><img alt="主界面" loading="lazy" src="/zh/blog/2025/edge-extension-with-azure-ai/1-main-interface.png"></p>
<h2 id="问题非结构化数据">问题：非结构化数据</h2>
<p>想象一下收到这段文本：</p>

<blockquote>
  <p>&ldquo;张三 13800138000 广东省深圳市南山区科技园南区R2-B三楼&rdquo;</p>

</blockquote>
<p>为了发货，你需要将其拆解为：</p>
<ul>
<li><strong>姓名</strong>: 张三</li>
<li><strong>电话</strong>: 13800138000</li>
<li><strong>地址</strong>: 科技园南区R2-B三楼</li>
<li><strong>城市</strong>: 深圳市</li>
<li><strong>省份</strong>: 广东省</li>
<li><strong>国家</strong>: 中国</li>
</ul>
<p>手动处理成百上千个订单既繁琐又容易出错。正则表达式（Regex）可以处理某些情况，但当格式变化或用户有错别字时，它们往往无能为力。</p>
<h2 id="解决方案llm-作为解析器">解决方案：LLM 作为解析器</h2>
<p><img alt="Auto Address" loading="lazy" src="/zh/blog/2025/edge-extension-with-azure-ai/2-auto-address.png"></p>
<p>像 Azure OpenAI（如：GPT-4 或 GPT-5）这样的大语言模型在理解上下文方面表现出色。它们能区分街道名称和城市名称，不是因为死板的规则，而是因为它们理解地址的<strong>语义</strong>。</p>
<p>我选择 <strong>Azure OpenAI</strong> 进行此项目主要有两个原因：</p>
<ol>
<li><strong>企业级隐私</strong>：发送到 Azure OpenAI 的数据不会用于训练公共模型。</li>
<li><strong>可靠性</strong>：稳定的正常运行时间和性能。</li>
</ol>
<h2 id="技术揭秘它是如何工作的">技术揭秘：它是如何工作的</h2>
<p>插件的核心逻辑出奇地简单。它获取用户的输入，并将其连同一组非常具体的指令发送到 Azure OpenAI API。</p>
<h3 id="1-system-prompt-系统提示词">1. System Prompt (系统提示词)</h3>
<p>“魔法”在于系统提示词。我们需要确切地告诉模型要做什么，更重要的是，如何格式化输出。</p>
<p>这是代码中实际使用的提示词：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">role</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;system&#34;</span>, 
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are an address parser. Return ONLY a raw JSON object (no markdown code blocks) with keys: name, province, city, address, zip_code, country. If province/state is missing in the input, infer it from the city. Do not separate house number, include it in the address field.&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>这里使用的关键技巧：</strong></p>
<ul>
<li><strong>角色定义</strong>: &ldquo;You are an address parser.&rdquo; (你是一个地址解析器)</li>
<li><strong>输出约束</strong>: &ldquo;Return ONLY a raw JSON object&rdquo;. (仅返回原始 JSON 对象)。这防止模型添加像 &ldquo;这是您的 JSON：&rdquo; 这样的废话。</li>
<li><strong>Schema 定义</strong>: 我们明确列出了我们想要的键 (<code>name</code>, <code>province</code> 等)。</li>
<li><strong>逻辑注入</strong>: &ldquo;If province/state is missing&hellip; infer it.&rdquo; (如果省/州缺失&hellip;推断它)。这将逻辑处理卸载给了 AI。</li>
</ul>
<h3 id="2-api-调用">2. API 调用</h3>
<p>扩展使用标准的 <code>fetch</code> API 与您的 Azure 部署进行通信。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">baseUrl</span><span style="color:#e6db74">}</span><span style="color:#e6db74">openai/deployments/</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">azureDeployment</span><span style="color:#e6db74">}</span><span style="color:#e6db74">/chat/completions?api-version=2024-05-01-preview`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">response</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;Content-Type&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;application/json&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;api-key&#39;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">azureApiKey</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span><span style="color:#f92672">:</span> [ <span style="color:#75715e">/* ... prompt ... */</span> ],
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">temperature</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">0.1</span> <span style="color:#75715e">// 以获得一致、确定的结果
</span></span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>我们使用低 <code>temperature</code> (0.1)，因为我们不需要创造力；我们需要精确度。</p>
<h3 id="3-智能省份推断双重检查">3. 智能省份推断（“双重检查”）</h3>
<p>有时，即使是最好的模型也会遗漏细节，或者输入太模糊。一个常见的问题是缺少省/州信息。</p>
<p>我在 <code>popup.js</code> 中实现了一个“回退”机制。如果解析结果有城市但没有省份，扩展程序会发起<strong>第二次定向 API 调用</strong>：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ((<span style="color:#f92672">!</span><span style="color:#a6e22e">addr</span>.<span style="color:#a6e22e">province</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">addr</span>.<span style="color:#a6e22e">province</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;&#39;</span>) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">addr</span>.<span style="color:#a6e22e">city</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ... 二次调用 ...
</span></span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">messages</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">role</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;system&#34;</span>, 
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a geography helper. Return ONLY the province/state name for the given city. Do not return JSON, just the name.&#34;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">role</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;user&#34;</span>, 
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Which province/state is the city &#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">addr</span>.<span style="color:#a6e22e">city</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">countryContext</span><span style="color:#e6db74">}</span><span style="color:#e6db74">?`</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>这确保了在用户看到数据之前，数据尽可能完整。</p>
<h2 id="chrome--edge-扩展架构">Chrome / Edge 扩展架构</h2>
<h3 id="manifest-v3-与-side-panel-侧边栏">Manifest V3 与 Side Panel (侧边栏)</h3>
<p>该扩展基于 <strong>Manifest V3</strong> 构建，这是 Chrome / Edge 扩展平台的最新迭代。</p>
<p>我没有使用点击后就会关闭的传统弹出窗口，而是使用了 <strong>Side Panel API</strong> (<code>&quot;permissions&quot;: [&quot;sidePanel&quot;]</code>)。这允许扩展程序停留在浏览器窗口的一侧。这对工作流程至关重要：用户可以在左侧查看发货单页面，同时在右侧查看解析出的地址数据。</p>
<h3 id="隐私优先">隐私优先</h3>
<p>首先使用前需要通过配置界面设置您要使用的 Azure OpenAI 信息：</p>
<p><img alt="配置界面" loading="lazy" src="/zh/blog/2025/edge-extension-with-azure-ai/3-options-interface.png"></p>
<p>主要的设计决策是<strong>零中间服务器</strong>。</p>
<ul>
<li>扩展程序使用 <code>chrome.storage.sync</code> 将您的 API 密钥本地存储在浏览器中。</li>
<li>请求直接从<strong>您的浏览器</strong> -&gt; <strong>Azure OpenAI</strong>。</li>
<li>扩展开发者不收集任何数据。</li>
</ul>
<h2 id="结语">结语</h2>
<p>通过结合 Chrome / Edge 扩展的灵活性和 Azure OpenAI 的智能，我们可以将繁琐的数据录入任务转化为一键操作。</p>
<p>代码已开源。欢迎查看、Fork 并构建您自己的 AI 驱动工具！</p>
<p><strong>GitHub Repo</strong>: <a href="https://github.com/wilsonwu/autoaddr">https://github.com/wilsonwu/autoaddr</a></p>
<p>如果您使用的是 Microsoft Edge 浏览器，可以通过扩展插件市场安装本插件的最新版本：<a href="https://microsoftedge.microsoft.com/addons/detail/auto-address-smart-ship/ieaphpecggbaiineilddheaghcolkbba">Edge 浏览器扩展插件</a></p>
]]></content:encoded></item></channel></rss>