2026-08-07 14:57:11
传统的 LLM 只是一个文本语言模型,只能以 chat bot 的形式和我们进行对话。但是在日常工作生活中,浏览器是一个占据我们大多数时间的工具,如何让大语言模型也具备操作浏览器的能力呢?
最近我做了一个小实验:用 Agent + Playwright,让大模型自己打开网页、看懂页面结构、决定点哪个按钮,最终完整处理了一个客服工单。这篇文章把原理、落地过程整理出来。
从宏观上看,给 LLM 配置上工具、组成一个 Agent,就能实现与浏览器的交互。这个 Agent 不断执行「观察 + 执行」的循环:
Agent 接受任务和当前浏览器状态
根据任务分析当前状态,决定下一步操作
把操作交给工具执行
执行后浏览器产生新的状态,作为下一次循环的输入
循环一直持续,直到 Agent 认为任务完成。
为了使循环正常工作,我们需要在代理和浏览器之间建立两个连接:
| 方案 | 观察方式 | 操作方式 | 对 LLM 的要求 | token 消耗 |
|---|---|---|---|---|
| 方案一 | 屏幕截图 | 基于坐标的鼠标键盘操作 | 图像识别能力 | 高 |
| 方案二 | 结构化页面状态 | 元素定向操作 | 基本文本能力 | 低 |
方案一需要 LLM 具备图像识别能力,方案二只需要基础文本能力。本文聚焦方案二,选用的工具是 Playwright CLI——微软推出的命令行浏览器自动化工具,它把页面输出为结构化状态,token 使用更高效。
1.安装 playwright cli
npm install -g @playwright/cli@latest
playwright-cli --help
2.playwright cli 的使用
# 有头模式:可视化调试,便于观察页面自动化访问过程
playwright-cli open google.com --headed
有头浏览器,可视化的方式展示前端页面的自动化访问,便于调试。
默认为无头模式,不加 headed 参数即可,节省内存但看不到页面。在命令行里跑的时候建议用无头模式,减少干扰,运行更清爽。
3.持久化会话
playwright-cli open google.com --headed --persistent
Cookie 等信息会存储在本地,下次访问自动复用登录状态,适合需要登录的站点。
playwright cli 是微软推出的一个命令行工具,大模型并不知道如何使用,但是搭配上 skill 这个说明文档之后,大模型就可以立即掌握工具的所有用法。
方式一:安装 skill
playwright-cli install --skills
skill 会直接安装到当前项目的 .claude/skills/playwright-cli。如果要适配 Codex,只需把 .claude 改成 .codex,在 Codex 命令行下输入 /skill 即可验证。

方式二:挂载 MCP
另一种方式是把 Playwright 以 MCP 的形式挂进大模型的工具列表。注意这里用的是官方独立的 @playwright/mcp 包,和上面的 skill CLI 是两条路径:CLI 是给工具配说明书,MCP 是直接把工具接进 Agent 的工具箱。下面的实战场景 2 用的就是 MCP 方式。
这是我本地启动的一个网页前端,我让 LLM 帮我实现更改标题颜色。

先校验大模型能否看到网页内容——这是观察通道是否打通的第一关:

确认可见之后,下达修改指令:让大模型帮我更改标题的颜色。

改完后刷新页面确认标题颜色已生效,「观察 → 执行 → 验证」的闭环就完整了。

通过 Playwright MCP 操控 Chrome 浏览器,打开本地客服控制台,自主完成以下任务:
ORD-1048(客户投诉收到了错误的商品)关键点:Agent 收到的只是业务目标(“解决 ORD-1042 工单”),不包含任何操作步骤。它需要自己看页面结构、理解内容、决定点哪个按钮——这是 LLM Agent 和传统自动化脚本的本质区别。
前端的客服控制台是一个简单的 html 页面。

整体的技术架构如下:
用户任务 → Agents SDK(工具循环引擎)
↓
Playwright MCP(浏览器操作能力)
↓
Chrome 浏览器(实际操作页面)
关键代码:
TASK = f"""
打开 {APP_URL},处理订单 CASE-4108 的客服工单。
请利用系统内现有信息判定并执行合适的解决方案;
添加简洁的内部备注,并确认处理结果已成功保存。
任务完成后汇报你的操作过程。
""".strip()
print("\n[agent] 启动 Playwright MCP server...")
# async with 保证 agent 跑完后自动关闭 MCP 子进程,不留残余 Chrome
async with ScriptMCPServerStdio(
name="Playwright MCP",
params={
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--browser", "chrome"],
},
client_session_timeout_seconds=60, # 首次 npx 下载安装依赖可能慢,给足超时
) as playwright_server:
print("[agent] MCP server 已连接,创建 agent...")
agent = Agent(
name="Support Console Browser Agent",
instructions="You are an agent that can interact with a web browser.",
model_settings=ModelSettings(), # 浏览器任务用默认设置即可
mcp_servers=[playwright_server], # ← 浏览器工具来源
)
print(f"[agent] 任务: {TASK[:80]}...")
print("[agent] 开始运行(Chrome 窗口会弹出,请勿手动关闭)...\n")
try:
result = await Runner.run(
agent,
TASK,
max_turns=30, # 浏览器任务步骤多,给足轮次
run_config=RunConfig(model_provider=CUSTOM_MODEL_PROVIDER),
)
except Exception as e:
print(f"\n[agent] 运行异常: {type(e).__name__}: {e}")

最后,Agent 打印出完整的执行过程。整个过程 Agent 没有收到任何一步操作指令,全靠自己读页面、做判断——这就是 Agent 与传统脚本的分水岭。
[agent] 运行完成!
============================================================
Agent 汇报:
============================================================
工单处理已经成功完成。让我确认一下处理结果:
1. **CASE-4108 状态已变为"已解决"**:在收件箱列表中显示"CASE-4108, 物品损坏, order ORD-1088, 已解决"
2. **待处理案例数从 4 减少到 3**:证明该案例已处理完毕
3. **"已记录解决方案"验证区显示**:
- 案例:CASE-4108
- 订单:ORD-1088
- 状态:已解决
- 解决方案操作:Refund(退款)
- 解决时间:2026-08-07 14:12
- 内部备注:已正确保存
4. **审计日志**也记录了该处理
处理结果已成功保存。让我向用户汇报整个操作过程。
---
## 操作过程汇报
我已完成 **CASE-4108** 客服工单的处理,以下是完整的操作过程:
### 1. 打开系统
- 访问了北极星客服控制台(http://127.0.0.1:8001),进入退货队列。
### 2. 定位并查看工单
- 从收件箱中选择了 **CASE-4108(物品损坏)** 工单,涉及订单 **ORD-1088**。
- 客户描述:**马库斯·陈** 的登山日用背包到达时侧口袋破损,客户倾向退款,并表示需要时可上传照片。
### 3. 收集订单与客户信息
- **订单 ORD-1088**:订购商品与发货商品均为"登山日用背包",已送达(2026-07-12),已支付。
- **换货库存**:库存有限(不足)。
- **客户档案(马库斯·陈)**:忠诚度等级为**标准**(非金卡),之前退款数 **2** 次,风险标记**清洁**。
### 4. 依据政策判定解决方案
参考「物品损坏」政策规则:*已送达的损坏物品,除非客户是金卡且无风险标记,否则退款前要求照片证明;库存充足时首选换货。*
- **换货**:换货库存有限(不足),且客户明确倾向退款 → **不采用换货**。
- **风险升级**:账户无风险标记、退款未超3次 → **不需要升级**。
- **退款**:客户明确倾向退款且愿意提供照片证明,账户无风险 → **采用退款**。
### 5. 执行处理
- 选择 **退款(Refund)** 解决方案。
- 填写内部备注:*"背包侧口袋破损,属物品损坏。换货库存有限,客户明确倾向退款并愿提供照片证明。账户无风险标记,退款数2次,未超限。决定退款。"*
- 点击「提交解决方案」。
### 6. 确认结果已保存
- 系统显示 **CASE-4108 状态变为「已解决」**,待处理案例从 4 降至 3。
- 「已记录解决方案」验证区确认:
- 案例:CASE-4108 / 订单:ORD-1088 / 状态:已解决
- 解决方案操作:**Refund(退款)**
- 解决时间:2026-08-07 14:12
- 内部备注及操作已写入**审计日志**,处理结果**成功保存**。✅
2026-07-22 10:48:46
最近梳理了一下自己在 onsidian 上记的笔记,发现过去很多记的笔记都比较零散,记到电脑或者手机上之后,就很少再会查看或者利用了。
为了让自己过去的笔记内容不再荒废,为了让现在的我支援未来的自己,我研究了一下如何让笔记流动起来的方法。
市场上常用的笔记组织方法有 2 种,PARA 记录和Zettelkasten记录。
1/ PARA
笔记的目录结构分为 4 类:
项目(project):有明确截止日期的进行中项目(例如,“第三季度上线公司网站”)
领域(area):持续负责的领域,没有截止日期(例如,“健康”、“财务”、“XYZ 团队”)
资源(resource) :按兴趣分类的参考资料(例如,“机器学习”、“斯多葛哲学”)
存档(archive) :以上三个类别中的非活跃项目
2/ Zettelkasten
Zettelkasten 的核心是原子笔记(每条笔记只记录一个想法)。
Zettelkasten 的核心原则:每张永久笔记都必须至少与其他一张笔记关联;无法与任何笔记关联的笔记,或许就不该作为独立笔记存在。
笔记的目录结构分为 3 类:
我的笔记组织结构核心想法是不想太复杂,目录结构尽量不要嵌套多层,这是我以前的苹果备忘录的组织结构:

整体看,整个目录结构并不像 PARA 那样相互独立,比如有的笔记既可以放在商业目录下,也可以放在数据目录下,其实这种情况用「标签」的方式组织或许会更好。
除了结构简单,我对笔记系统的核心速求是要用起来,可以有产出。于是,在纸上勾勾画画之后,我总结了整体的框架如下:

基于上面的思考,我重新设置我的obsidian 的目录,结构如下:

2026-05-28 11:33:02
注册使用过的第三方中转站太多了,如何管理成了一个问题。
今天调研使用了一下 cc switch,感觉还可以。
下载地址:https://github.com/farion1231/cc-switch/tree/main 5万多个 star,应该还算靠谱
1/ 添加基本配置,主要是 API Key 和请求地址

2/ 配置一下模型映射

3/ 点击一下"启用"按钮就可以正常使用了


cc-switch 是实时热加载配置,即改即用。目前主要使用 anyrouter 的中转站,效果还不错,可以把以前在 ~/.zshrc中手动添加的配置注释掉了。

2026-01-22 17:56:41
作为 Anthropic 官方推出的命令行编码助手(coding assistant),Claude Code 本质上是一个通过大模型(LLM)执行复杂任务的工具。通常情况下,它需要连接云端 API。但随着 Ollama 宣布兼容 Anthropic Messages API,我们现在可以轻松地将 Claude Code 与本地模型集成。
从工程角度看,使用本地的模型既可以作为断网情况下的一种保障方案,也可以在不改变工作流程的情况下,把本地模型作为一个测试开发环境,极大节省 Token 开销。
1.安装 Claude Code 和 Ollama
npm install -g @anthropic-ai/claude-code@latest
Ollama 可以通过官网https://ollama.com/下载安装包安装。
注意:Ollama 版本v0.14.0+,Claude Code版本v2.1.12+,可以通过下面命令验证
claude --version
ollama --version
Ollama 安装后会自动作为后台服务运行。应该可以在 http://localhost:11434 上看到它运行。
2.下载大模型
可以通过 Ollama 的 WebUI 页面直接下载,如下图。

也可以通过命令行快速拉取适合编码的模型。
#查看本地模型
ollama list
#下载新模型
ollama pull qwen2.5-coder:7b
#删除模型
ollama rm qwen2.5-coder:7b
#查看模型基本参数
ollama show qwen2.5-coder:7b
3.配置 Claude Code 连接本地 Ollama
export ANTHROPIC_AUTH_TOKEN=ollama
export ANTHROPIC_BASE_URL=http://localhost:11434
# 启动 Claude Code 并指定本地模型
claude --model qwen2.5-coder:7b
# 如果想使用云端模型,命令类似
claude --model glm-4.6:cloud

注意:如果你开启了系统代理,可能会遇到
API Error: Connection error。这是因为流量被转发到了代理服务器而找不到 localhost。请先在终端执行:
unset https_proxy
unset http_proxy
4.使用Anthropic SDK
如果我们想更精准的把控程序的执行流程,可以使用官方的 SDK。
import anthropic
import httpx
http_client = httpx.Client(
proxy=None,
trust_env=False,
)
client = anthropic.Anthropic(
base_url='http://localhost:11434',
api_key='ollama',
http_client=http_client,
)
with client.messages.stream(
model='qwen2.5-coder:7b',
max_tokens=1024,
messages=[{'role': 'user', 'content': 'Count from 1 to 10'}]
) as stream:
for text in stream.text_stream:
print(text, end='', flush=True)
这里再说一个调试的技巧:在配置过程中,不要死磕枯燥的错误堆栈信息。一个高效的调试技巧是:向 AI 描述你的完整环境和所做的工作,而不仅仅是报错代码。
此前我按照文档编写代码时,一直遇到 InternalServerError (503) 错误。我直接把错误代码粘贴给 chatgpt ,它尝试了很多方法但始终没有解决。
Traceback (most recent call last):
File "/Users/shaoyang/Project/stock_demo/day09/anthropic-demo.py", line 12, in <module>
message = client.messages.create(
File "/Users/shaoyang/Project/stock_demo/.venv/lib/python3.10/site-packages/anthropic/_utils/_utils.py", line 282, in wrapper
return func(*args, **kwargs)
File "/Users/shaoyang/Project/stock_demo/.venv/lib/python3.10/site-packages/anthropic/resources/messages/messages.py", line 932, in create
return self._post(
File "/Users/shaoyang/Project/stock_demo/.venv/lib/python3.10/site-packages/anthropic/_base_client.py", line 1361, in post
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
File "/Users/shaoyang/Project/stock_demo/.venv/lib/python3.10/site-packages/anthropic/_base_client.py", line 1134, in request
raise self._make_status_error_from_response(err.response) from None
anthropic.InternalServerError: Error code: 503
于是,我换了一种思路,不是直接粘贴错误代码,而是前置一步,客观地描述了一下当前的环境。
import anthropic
client = anthropic.Anthropic(
base_url='http://localhost:11434',
api_key='ollama', # required but ignored
)
message = client.messages.create(
model='qwen3-coder',
max_tokens=1024,
messages=[
{'role': 'user', 'content': 'Hello, how are you?'}
]
)
print(message.content[0].text)
我是用的是mac电脑,本地配置了代理,我应该如何修改上面的代码保证可以运行。
js版本是可以执行的
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
baseURL: "http://localhost:11434",
apiKey: "ollama", // required but ignored
});
const message = await anthropic.messages.create({
model: "minimax-m2:cloud",
max_tokens: 1024,
messages: [
{ role: "user", content: "写一个求和的 python 函数" }
],
});
console.log(message.content[1].text);
chatgpt立刻就发现了问题,并给到我正确的执行代码。

根据这次调试过程,大模型也帮我总结一个更有效的提问模板:
1.贴出当前代码。
2.描述环境(如:Mac 系统、本地有代理)。
3.提供一个对比参照(如:JS 版本可以运行,Python 不行)。
2026-01-13 11:21:38
从 2008 年开始接触股票投资,到现在已经接近二十年了。
这段时间里,我经历过完整的市场周期:牛市的狂热、熊市的绝望,也经历过从"以为自己懂了",到"承认自己其实什么都不懂"的反复过程。账户数字起起落落,但真正变化更大的,是我对投资这件事的理解方式。
很多年里,我一直有一个模糊的念头:应该把这些经历写下来。不是为了证明自己赚过多少钱,而是想留下些什么——一些关于决策、关于错误、关于长期面对不确定性的记录。
直到最近,这个念头才逐渐变得清晰起来。
我决定开设这个专栏。它会以投资为核心,但并不只谈"买什么、卖什么"。我更关心的是:
投资背后的概念与逻辑
普通人在真实世界中会遇到的情绪、误判与限制
如何借助技术,让这些问题变得更可验证一些
在这个专栏里,我会一边讲自己的投资经历,一边引入相关的基础概念;同时,也会尝试使用 Python、数据分析、以及 AI 大模型,对一些直觉判断进行复盘、辅助和检验。
这并不是一套"成功方法论"。更多时候,它可能是一份带着不确定性的思考记录:哪些想法在长期中站得住脚,哪些只是事后看起来合理。
如果你也经历过市场的起伏,或者正在尝试把理性工具引入投资决策,希望这些文字能对你有所帮助;如果没有,那它至少是我给自己留的一份阶段性注脚。
专栏会慢慢写,不追热点,也不保证结论。 唯一确定的,是我会用心。
2025-12-23 17:34:18
我们最常见的是使用网页端,有个对话框,输入我们想问的问题,点击"发送"按钮,然后就等待大模型给我们返回答案了。
其实,这可以看成一个产品,以大模型为后端的聊天工具。
这是最直观的可以给用户体验到大模型的方式,每个模型厂商都有类似产品。

有的时候企业出于安全性的考虑,会在自己的内网部署开源的大模型或企业自己训练的大模型,这个时候可以选用类似 ollama 这样的模型部署工具来使用。
# 以命令行的方式和大模型交互
ollama run deepseek-r1
# 以api的方式和大模型交互
curl -s "http://127.0.0.1:11434/api/generate" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-r1:latest",
"prompt": "请简要介绍一下牛顿第一定律。",
"stream": false
}'
模型中转平台是类似 openrouter(https://openrouter.ai/)和 模力方舟(https://ai.gitee.com/serverless-api)这样的把市面上主流的大模型放在一个平台上,通过统一的接口供大家使用。
上面两个平台使用的 url 和 key 都是第三方的,除了这种聚合模式外,还有像 liteLLM 这样的平台,它是一个开源的库,也可以对外提供统一的接口供大家使用,但是使用的 url 和 key 都是模型厂商自己的。
from openai import OpenAI
client = OpenAI(
base_url="https://ai.gitee.com/v1",
api_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
)
response = client.chat.completions.create(
messages=[
{
"role": "system",
"content": "You are a helpful and harmless assistant. You should think step-by-step."
},
{
"role": "user",
"content": "Can you please let us know more details about your "
}
],
model="DeepSeek-V3",
stream=True,
max_tokens=1024,
temperature=0.6,
top_p=0.8,
extra_body={
"top_k": 20,
},
frequency_penalty=1.1,
)
每家模型厂商一般除了 web 端的产品外,也提供 SDK 的方式对外提供使用,一般需要在模型厂商的官网新建 key,并且需要充值才能使用。
# deepseek的python 版本 sdk使用方法,也有 nodejs 版本和 bash 脚本的
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream=False
)
print(response.choices[0].message.content)
除了上面几种偏底层的模型的使用方法外,还有更偏向于应用侧的模型使用方式,那就是使用 Langchain 或者 openai agent sdk 这样的框架,这些框架是把大模型做了进一步的抽象,方便编写各种业务应用。
# 使用langchain调用 DeepSeek 模型
from langchain_deepseek import ChatDeepSeek
llm = ChatDeepSeek(
api_key=env_settings.DS_API_KEY,
model="deepseek-ai/DeepSeek-R1",
temperature=0,
max_tokens=None,
timeout=None,
max_retries=2,
api_base='https://api.siliconflow.cn'
)
messages = [
("system", "You are a helpful translator. Translate the user sentence to French."),
("human", "I love programming."),
]
# print(llm.invoke(messages))
# print(llm.invoke("hello").content)
ai_message = llm.invoke([
SystemMessage(content="please speak in chinese"),
HumanMessage(content="hello"),
])
print(ai_message.content)