🧠 第二章 模型部署:在本地使用DeepSeek
📄 一句话概括

同一个模型,Page Assist 走 POST /api/chat,Cherry Studio 走 /v1/chat/completions。 连回答文本在响应里的位置都不一样:message.content 对 choices[0].message.content。 「扩展连不上」和「桌面端连不上」是两回事。

📝 本节和前面几节的关系

上一节配的是桌面端,走 OpenAI 兼容接口。 这一节换成浏览器扩展——它的价值是能直接读你正在看的网页,但接口是另一套。

一、Page Assist 解决的是什么问题

桌面端的工作方式是「打开软件、复制内容、粘贴、问」。 浏览器扩展把中间两步省掉了:它直接读你当前标签页的内容,把正文当上下文发给本地模型。 这个差别看着小,用起来差很多。 看一份英文合同、一篇长文、一个报错页面,桌面端要复制粘贴、还可能因为格式乱掉丢内容; 扩展点一下就有,而且有「只取选中部分」这类选项。 它还有一个附带好处:所有内容都在本机流转。网页正文发给 127.0.0.1,不经过任何厂商服务器。 对处理敏感材料的人来说,这一点比功能本身重要。 代价是它要装在你的浏览器里,权限那一关绕不过去——要读网页内容, 就得给「读取和更改您在所有网站上的数据」这个权限。这是它的工作原理决定的,不是它想偷东西。

二、安装:三步,全在浏览器里

📋 实操清单(这些要在你自己的浏览器里点,沙箱里做不了): 第一步,打开浏览器的扩展商店,搜 Page Assist。 Chrome 商店和 Edge 商店都有,选作者是 n4ze3m 的那个,装的人最多。 第二步,点「添加至 Chrome」,浏览器弹权限确认,点允许。 装完地址栏右边会多一个图标,没看到就点拼图图标把它固定出来。 第三步,点图标打开扩展面板。第一次打开会提示设置,选 Ollama,地址填 http://127.0.0.1:11434。 🐍 沙箱等价物(这些能真跑,跑出来的是真结果):扩展装好之后做的第一件事, 是向 Ollama 要模型列表来填下拉框。 Demo 里把这一步真跑了一遍——GET /api/tags 返回 1 个模型,下拉框就 1 项。 所以「装完就卡住」这件事有明确的判据: 下拉框是空的,一定是 /api/tags 没通,不是模型没下载好。

三、它走的是 Ollama 原生接口

这是这一节最要紧的一件事: Page Assist 默认走 /api/chat,不是 OpenAI 兼容的 /v1/chat/completions。 两条路径的差别不只是路径名。返回的结构不一样,文本藏的位置不一样,token 统计的字段名也不一样。 所以拿桌面端的经验去排查扩展的问题,经常会找错方向。

对比项 Ollama 原生 OpenAI 兼容
路径 POST /api/chat /v1/chat/completions
文本位置 message.content choices[0].message.content
token 统计 eval_count 等 usage 里的字段
谁在用 Page Assist、官方 CLI Cherry Studio、多数 SDK
但这两条路是并存的,不是二选一——同一台 Ollama 同时接受两种请求。
所以你完全可以让扩展走原生接口、脚本走兼容接口,互不干扰。

四、装完先做一件事:确认它读到了模型

打开扩展面板,看模型下拉框里有没有东西。这一步比「能不能对话」更早、更准。 下拉框有模型、选了没反应:接口通了,卡在下一节要讲的跨域。 下拉框是空的:/api/tags 没通。回到 2.1.3 查服务,或者查地址端口。 下拉框有模型、也能选,但一问就报错:模型太大加载失败,或者上下文设太大导致显存不够。 这时候去看 Ollama 的日志,里面会写具体的失败原因。 这三条分清楚了,扩展的问题基本就能定位到具体环节。 最怕的是「不行」两个字——它把三种完全不同的病说成了一种。

五、动手 Demo:两条接口都通,但文本藏在响应里不同的位置

在本地起一个同时支持两种接口的假服务,分别往 /api/chat 和 /v1/chat/completions 发同一个问题, 对比两个响应的结构差异;再请求一次 /api/tags,看扩展填下拉框用的数据长什么样。

b = {u'model': u'deepseek-r1:7b', u'stream': False,
     u'messages': [{u'role': u'user', u'content': u'你好'}]}

post(url + u'/api/chat', b)                 # 扩展走这条
post(url + u'/v1/chat/completions', b)      # 桌面端走这条

req = urllib.request.Request(url + u'/api/tags')
models = json.loads(urllib.request.urlopen(req).read())[u'models']

实跑输出:

--------------------------------------------------------------------
装 Page Assist:浏览器扩展走的是另一套接口
--------------------------------------------------------------------
  同一个模型,两种前端,发的接口不一样:
  Ollama 原生              POST /api/chat → 200
  OpenAI 兼容              POST /v1/chat/completions → 200

  对比项       Ollama 原生                     OpenAI 兼容
  -----------------------------------------------------------------------
  路径         POST /api/chat                  /v1/chat/completions
  文本位置     message.content                 choices[0].message.content
  token 统计   eval_count/prompt_eval_count   usage.*_tokens
  谁在用       Page Assist、Ollama 官方 CLI    Cherry Studio、绝大多数 SDK
  所以「扩展连不上」和「桌面端连不上」可能是两回事——
  **先确认它走的是哪条路径**,再看那条路径有没有被打开。

  Page Assist 的安装动作本身在浏览器里点,沙箱复现不了。
  能复现的是它连上之后发的第一个请求,就是上面那行 /api/chat。
  另外它还要读一次模型列表,用来填下拉框:
  GET /api/tags            返回 1 个模型,下拉框就有 1 项
  下拉框是空的,几乎一定是这一条没通——**不是模型没下载好。**

两条路径都回了 200——同一台服务,两种接口并存。 这是 Ollama 的设计:老的 /api/* 给自己的工具用,新的 /v1/* 给兼容 OpenAI 生态的客户端用。 表里那四行差异,只有第一行是路径,后三行都是数据形状。 排查的时候真正麻烦的是后三行:比如你在日志里搜 choices 搜不到,不是没返回, 是这条路径根本不叫这个名字。 GET /api/tags 返回 1 个模型,扩展的下拉框就填 1 项。 这个数对不上,就是「扩展看不到模型」的直接原因。 要诚实说明: 扩展本身的安装动作在浏览器里,沙箱复现不了。这里复现的是它装好之后发的请求—— 而这恰好是排查问题真正需要的部分。 最后那句判断标准的用法:扩展点开发现下拉框空,先别急着说「模型没下好」, 直接 GET /api/tags 看一眼。十次里有八次,问题在这一步。

30 秒实验

  1. 把 /api/chat 的 stream 改成 True,看返回从「一个完整 JSON」变成「每行一个 JSON」。 扩展的打字机效果就是靠这个。
  2. 对比两条路径返回的 token 统计:原生接口用 eval_count、eval_duration, 兼容接口用 usage.completion_tokens。两套名字,同一个意思。
  3. 请求一个不存在的路径(比如 /api/generate2),看返回 404 还是 200。 这能帮你确认「路径拼错」和「服务没起」是两种不同的失败。

常见误区

  1. 「扩展装了就自动连上」。装完只是有了壳,地址、模型、跨域三项还得配。下拉框空就是第一项没通。
  2. 「扩展和桌面端一样」。接口不一样、返回结构不一样、token 字段不一样。 拿一个的经验套另一个,会找错方向。
  3. 「模型列表是空的就是没下模型」。更可能是 /api/tags 没通。 先在浏览器里打开那个地址看一眼,比猜快得多。
自测三问(点开对答案) 1. Page Assist 走的是哪条接口?和 Cherry Studio 有什么不同? —— 走 Ollama 原生的 `/api/chat`;Cherry Studio 走 OpenAI 兼容的 `/v1/chat/completions`。 返回结构、文本位置、token 字段名都不一样。 2. 为什么扩展要申请「读取所有网站数据」的权限? —— 因为它要把当前网页的正文当上下文发给本地模型。不读网页内容,这个功能就不成立。 这是工作原理决定的。 3. 扩展的下拉框是空的,最可能是哪一环断了? —— `GET /api/tags` 没通。可能是服务没起、地址端口不对、或者被跨域拦了。不是「模型没下载好」。

扩展装好、模型也读到了,但一发消息就报错——下一节讲那个卡住九成人的跨域问题。

相关笔记