ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

让编码智能体看得见屏幕:识屏插件全链路实现

2026/8/31 11:43:09 拓冰建站 浏览量
让编码智能体看得见屏幕:识屏插件全链路实现 给 DeepSeek Harness 写一个识屏插件是为了解决一个很具体的痛点智能体在终端里跑得再快也看不见屏幕左边那半张设计稿更读不到你 IDE 里被弹窗遮住的红色报错。Harness 本身承担的是本地运行环境、模型路由、上下文管理和工具调用的职责它能帮编码智能体连接代码、命令和文件但屏幕这种非结构化信息默认并不在它的输入范围内。识屏插件要做的就是把“屏幕上有什么”转换成“模型能理解的文本上下文”再回到 Harness 的会话流里。下面这套流程会从插件设计、模块拆分、核心实现、运行验证到问题排查完整走一遍。读完以后你自己也能按同样思路给本地编码智能体扩展截图、OCR、窗口识别一类能力。需要先说明不同 Harness 版本的插件 API 和配置字段会有差异文中的代码和配置属于通用实现思路落到自己项目时要重新核对版本、模型名和插件规范。1. 先搞清楚Harness 为什么需要识屏插件1.1 Harness 到底解决了什么问题DeepSeek Harness 这类工具本质上是一个负责承载编码智能体的本地运行框架。你可以把它理解成智能体的“驾驶舱”它负责加载模型配置、管理多轮会话、调用本地命令、读取文件、执行测试再把这些结果统一交给模型继续推理。Harness 与单纯的 Agent 是两层概念。Agent 强调的是“决策循环”看到问题、规划步骤、调用工具、观察结果、继续推进。Harness 强调的是“运行环境”让 Agent 能稳定访问代码库、命令、沙箱、日志和配置。两者不是互斥关系而是承载关系。没有 HarnessAgent 只是一个会思考但手伸不出去的循环没有 AgentHarness 也只是个空壳框架。在接入 DeepSeek 模型的场景里Harness 通常还要承担配置下发和请求路由的工作。开发者会配置模型名称、接口地址、API Key、超时时间等参数Harness 再把这些参数应用到整个智能体会话里。很多报错都发生在这一层比如模型名配错、接口地址不匹配、多轮消息结构不对都会导致上游返回 400 或 422。1.2 智能体“看不见屏幕”的真实场景文字类工具能读文件、能执行命令但屏幕上的内容对它来说是盲区。实际开发里至少这四类信息经常只能靠屏幕传递IDE 右下角弹出的报错弹窗内容短暂日志文件里不一定有完整记录。设计稿、原型图、UI 标注智能体拿到的只是文件路径看不到视觉布局。终端里正在运行的服务输出用户想让智能体帮忙分析但智能体没有实时读取终端画面的权限。视频会议、演示文稿、远程桌面里的信息用户看到但无法直接复制成文本。这些场景的共同点是信息以像素形式存在而不是以字符串形式存在。识屏插件要做的就是把像素转成结构化文本再作为上下文交给模型。1.3 插件要覆盖的完整链路一个完整的识屏插件至少要覆盖六段链路捕获从操作系统获取屏幕图像。裁剪只保留用户关心的区域减少无关信息。预处理调整图像清晰度提升 OCR 识别率。识别用 OCR 引擎把图像转成文本。结构化给识别结果加来源、时间、区域、置信度字段。注入把结果交给 Harness让它出现在智能体的上下文里。这六段链路里最容易被低估的是第 5 步。很多人把 OCR 文本直接拼接进提示词结果模型分不清哪些是代码、哪些是屏幕文字上下文乱成一团。正确做法是给屏幕文本打上清晰的标记让模型知道“这段内容来自屏幕识别可能包含排版噪声”。1.4 插件与 Harness 的边界写插件之前要划清边界哪些逻辑放插件里哪些放 Harness 里。插件只负责“感知屏幕”不负责“决定下一步做什么”。是否调用识屏工具、什么时候调用、识别结果是否可信这些决策应该交给模型和 Harness 的调度逻辑。插件也不应该直接修改系统文件或执行高危操作它只输出一段上下文最终行为仍然由智能体的主循环控制。这样做的好处是职责单一、易排查。如果识屏结果有问题只需要看插件日志如果模型没有使用识屏结果只需要看 Harness 的会话日志不会互相污染。2. 识屏插件的设计目标与模块拆分2.1 功能基线一个最小可用的识屏插件必须包含什么我最初的想法并不是做一个完整的截图工具而是让 Harness 里的智能体能看到屏幕上最关键的几类信息终端报错、IDE 诊断、设计稿、弹窗提示。围绕这个目标插件被拆成四个模块。模块职责最小实现要求capture获取屏幕图像支持全屏和指定区域截图preprocess图像增强至少能做灰度化和放大ocr文本识别能输出带坐标的文字块context上下文打包生成带来源标记的结构化 JSON这四个模块串起来以后插件对外只暴露一个入口。这个入口接收一个参数截图区域或者截图模式返回一段结构化文本。Harness 里的智能体只需要调用这个入口不需要关心底层是 Tesseract 还是别的 OCR 引擎。2.2 三种接入方式命令调用、工具注册、上下文注入Harness 插件常见的接入方式有三种识屏插件可以根据使用习惯选择。第一种是命令调用。用户在终端里执行一条命令比如screen-reader --region 0 0 800 600插件把识别结果打印到标准输出再手动复制进对话。这种方式实现最简单但自动化程度低适合验证阶段。第二种是工具注册。把识屏能力注册成 Harness 可调用的工具模型在需要看屏幕时自己发起调用。这是最推荐的方式因为模型可以根据任务决定是否使用屏幕信息而不是每次对话都强行注入大段文本。第三种是上下文注入。通过 hook 在会话开始或每轮对话结束时把最近一次识屏结果自动追加到上下文。这种方式会让模型持续感知屏幕状态但 Token 消耗也最大适合需要连续监控屏幕的场景。实际项目里我建议先把第一种跑通再做成第二种。工具注册方式能更好地控制识别时机也更容易做权限和频率限制。2.3 隐私边界先想清楚再写代码屏幕截图是高度敏感的数据。插件一旦跑起来它能看到用户正在看的一切包括代码里的密钥、聊天窗口、邮箱、内部系统地址。写代码之前必须先定义隐私边界。我的做法是三条规则默认只识别用户显式指定的区域不做无差别全屏后台扫描。尽可能在本地完成识别不把原始截图直接上传到模型服务。上下文里过滤邮箱、手机号、密钥路径等敏感模式必要时用脱敏串替换。这里要注意OCR 识别出的文本仍然会随对话请求发送给模型所以“本地识别”只解决了一部分隐私问题。真正敏感的信息应该在注入上下文之前就过滤掉。注意把屏幕内容转换成文本再交给模型并不等于数据就安全了。识别结果会进入模型上下文敏感信息脱敏必须发生在注入之前而不是模型返回之后。3. 环境准备与项目骨架3.1 依赖清单识屏插件的核心依赖并不复杂主要分三块截图库、OCR 引擎、Python 运行时。依赖用途备注Python 3.9插件主语言建议用虚拟环境隔离Pillow截图和图像预处理跨平台常用pytesseract调用 Tesseract OCR需要安装 Tesseract 本体Tesseract OCR文字识别引擎必须安装语言包Node.js 18Harness 插件入口以目标 Harness 要求为准安装时建议先装 OCR 引擎本体再装 Python 依赖。因为 pytesseract 只是调用外部命令的封装如果系统里没有 Tesseract即使 pip 安装成功运行时报错也会让人误以为是 Python 依赖问题。Ubuntu 环境可以用下面命令安装 Tesseract 和中文语言包sudo apt update sudo apt install tesseract-ocr tesseract-ocr-chi-sim tesseract-ocr-engmacOS 环境可以这样装brew install tesseract tesseract-langWindows 环境需要从官方发布页下载安装包安装时勾选中文语言并把 Tesseract 的安装目录加入 PATH。Python 依赖用虚拟环境管理python3 -m venv .venv source .venv/bin/activate pip install pillow pytesseract安装完成后用下面命令确认 OCR 引擎和语言包可用tesseract --version tesseract --list-langs--list-langs输出里应该能看到eng和chi_sim否则后续中文识别会直接返回空结果。3.2 项目目录结构插件项目结构尽量保持简单核心代码集中在src/screen_plugin下screen-helper/ ├── pyproject.toml ├── config/ │ └── plugin.yaml ├── scripts/ │ ├── capture.py │ ├── ocr.py │ └── build_context.py ├── src/ │ └── screen_plugin/ │ ├── __init__.py │ ├── cli.py │ ├── capture.py │ ├── preprocess.py │ ├── ocr.py │ └── context.py ├── tests/ │ └── test_ocr.py └── README.mdscripts目录里的脚本是最初的验证脚本src里是正式模块。这样拆分的好处是先写临时脚本把链路跑通再抽象成可复用模块避免一开始就陷入类和接口设计里。3.3 最小配置示例Harness 所在项目的配置通常包含 provider、model、API Key 等字段。识屏插件本身可以独立配置用 YAML 挂到 Harness 的插件配置里。plugin: name: screen-reader enabled: true command: python3 scripts/capture_and_ocr.py default_region: enabled: false x: 0 y: 0 width: 1920 height: 1080 ocr_lang: chi_simeng context_tags: - screen - /screen privacy_filter: email: true phone: true这里的command是插件给 Harness 的调用入口context_tags标记注入的屏幕文本范围privacy_filter决定是否启用脱敏。不同 Harness 的插件规范会有差异落地前要换成你所用版本的实际字段名。4. 核心实现从屏幕像素到模型上下文4.1 截图模块支持全屏、区域和活动窗口截图模块要处理一个核心问题截哪里。最简单的是使用 Pillow 的ImageGrab它在 Windows 和 macOS 上体验较好在 Linux 上会受限。下面是支持全屏和指定区域截图的实现# src/screen_plugin/capture.py from __future__ import annotations import argparse from pathlib import Path from PIL import Image, ImageGrab def capture_all_screens(output: Path) - Path: image ImageGrab.grab(all_screensTrue) image.save(output, PNG) return output def capture_region(x0: int, y0: int, x1: int, y1: int, output: Path) - Path: if x1 x0 or y1 y0: raise ValueError(invalid region: right/bottom must be greater than left/top) image ImageGrab.grab(bbox(x0, y0, x1, y1), all_screensTrue) image.save(output, PNG) return output if __name__ __main__: parser argparse.ArgumentParser(descriptioncapture screen) parser.add_argument(--x0, typeint, default0) parser.add_argument(--y0, typeint, default0) parser.add_argument(--x1, typeint, default1920) parser.add_argument(--y1, typeint, default1080) args parser.parse_args() capture_region(args.x0, args.y0, args.x1, args.y1, Path(capture.png))关键点是all_screensTrue。多显示器环境下如果漏掉这个参数只会截到主屏副屏坐标会偏移。注意bbox的坐标是绝对屏幕坐标不是相对窗口坐标跨屏截图时最容易踩这个坑。4.2 图像预处理识别率低的问题一半出在这里OCR 识别率低一半是图像质量问题一半是语言包问题。直接从屏幕截下来的图很少是理想的字体太小、背景复杂、反色文字、DPI 缩放都会影响识别结果。预处理模块至少要做三件事转灰度、放大、对比度增强。# src/screen_plugin/preprocess.py from PIL import Image, ImageEnhance, ImageOps def prepare_for_ocr(image: Image.Image, scale: float 2.0) - Image.Image: # 转灰度 gray ImageOps.grayscale(image) # 放大避免小字号文字识别失败 if scale ! 1.0: new_size (int(gray.width * scale), int(gray.height * scale)) gray gray.resize(new_size, Image.LANCZOS) # 增强对比度减少背景噪声 enhancer ImageEnhance.Contrast(gray) return enhancer.enhance(1.8)为什么这里用灰度而不是二值化因为终端和 IDE 里经常有彩色字体、代码高亮、深色背景。直接二值化会让信息大量丢失灰度加对比度增强更稳妥。如果灰度图识别效果还是不好再尝试局部阈值二值化。4.3 OCR 模块Tesseract 的安装与语言包OCR 模块负责把处理后的图像转成文本同时尽量保留文字的位置信息。# src/screen_plugin/ocr.py from __future__ import annotations from PIL import Image import pytesseract def ocr_image(image: Image.Image, lang: str chi_simeng) - str: return pytesseract.image_to_string(image, langlang) def ocr_image_with_data(image: Image.Image, lang: str chi_simeng) - dict: data pytesseract.image_to_data(image, langlang, output_typepytesseract.Output.DICT) lines [] current_line [] last_block None for i, text in enumerate(data[text]): block data[block_num][i] if block ! last_block and current_line: lines.append( .join(current_line)) current_line [] last_block block if text.strip(): current_line.append(text.strip()) if current_line: lines.append( .join(current_line)) return {text: \n.join(lines), line_count: len(lines)}image_to_string适合快速验证image_to_data能拿到每个文字块的位置信息。后续如果要让模型读终端日志、分析报错顺序按块排列的文本会比整段文本更可用。4.4 上下文打包给模型结构化的屏幕文本识别出的文本不能直接丢给模型。屏幕文本充满断行、噪点和上下文缺失直接拼接会让模型误以为是用户输入或者代码。结构化上下文的标准输入输出如下# src/screen_plugin/context.py import json import time from dataclasses import asdict, dataclass dataclass class ScreenContext: source: str region: str captured_at: float ocr_lang: str text: str def build_context(text: str, region: tuple[int, int, int, int], lang: str) - str: ctx ScreenContext( sourcescreen_ocr, region,.join(str(v) for v in region), captured_attime.time(), ocr_langlang, texttext, ) return json.dumps(asdict(ctx), ensure_asciiFalse)打包完成后注入时最好加上明确的区域标记screen sourcescreen_ocr region0,0,800,600 langchi_simeng error: failed to load module at /home/user/src/main.ts:12 /screen这样模型能明确区分这是屏幕识别文本不是用户消息也不是代码文件。上下文结构越清晰模型越不会把它和真实指令混在一起。4.5 与 Harness 的集成入口由于不同 Harness 的插件 API 不同这里给出一个通用参考结构。思路是Harness 调用插件入口插件内部执行 Python 命令再把结果转成工具可读的返回值。// integration/screenPlugin.ts import { execFileSync } from node:child_process; export interface ScreenInput { region?: [number, number, number, number]; scale?: number; lang?: string; } export function runScreenReader(input: ScreenInput) { const args [scripts/capture_and_ocr.py]; if (input.region) { const [x0, y0, x1, y1] input.region; args.push(--x0, String(x0), --y0, String(y0)); args.push(--x1, String(x1), --y1, String(y1)); } const stdout execFileSync(python3, args, { encoding: utf-8, timeout: 15_000, }); return { type: text, content: stdout.trim() }; }这段代码的关键点是超时控制。OCR 是 CPU 密集操作中文识别在低配机器上可能要几秒甚至十几秒。如果 Harness 对工具调用有超时限制插件必须把单次识别控制在限制时间内或者把识别结果缓存起来避免重复识别。5. 运行验证从命令行到 Harness 日志5.1 先验证 CLI 本身集成到 Harness 之前先确认命令行自己能跑通。先截取屏幕左上角一个区域python3 scripts/capture_and_ocr.py --x0 0 --y0 0 --x1 800 --y1 600正常输出应该是一个 JSON 对象里面包含text和line_count字段而不是把 OCR 文本直接打到屏上。{ text: error: cannot find module lodash\n, line_count: 1 }这一步验证的目标是截图没有黑屏、OCR 识别出了内容、JSON 结构符合预期。只要输出稳定插件主体就完成了。5.2 再验证 Harness 能拿到上下文CLI 验证通过后再进入 Harness 集成验证。具体方式取决于你选择的接入模式。如果是命令调用模式就在 Harness 允许的命令列表里直接调用python3 scripts/capture_and_ocr.py观察返回结果是否进入对话记录。如果是工具注册模式先查看 Harness 的日志确认工具已注册成功。触发一次调用后在会话日志里能看到类似这样的工具结果记录tool_call: screen-reader tool_result: {source: screen_ocr, text: ...}如果日志里出现tool not found或command not found说明插件没有被正确注册需要回到配置和命令路径检查。5.3 端到端指标延迟、Token、识别准确率端到端验证阶段要关注三个指标。第一是单次识屏延迟。从发起调用到上下文返回值理想情况下应该在 2 到 10 秒之间。超过 20 秒就要检查是不是 OCR 处理了过大的截图区域或者 CPU 资源不足。第二是 Token 消耗。一次 OCR 结果可能上千字如果 Harness 在每轮对话都注入这段文本Token 消耗会快速增长。用 Harness 的 token 计数功能或者 API 返回的 usage 字段监控。第三是识别准确率。可以准备一组固定样张比如终端报错、IDE 诊断、中文弹窗、英文文档分别测试。用字段缺失率来判断这里指的是关键信息是否完整比如报错行号、文件名、错误码而不是要求一个字都不差。6. 常见问题与排查链路6.1 截图黑屏、区域偏移、多屏坐标不对现象是图片生成成功但内容是黑屏或者截图区域不在预期位置。先检查系统权限。Windows 和 macOS 对屏幕录制有独立授权程序没有屏幕录制权限时截图内容通常是黑屏或桌面壁纸而不是应用程序内容。到系统设置里给终端或 Python 进程授予屏幕录制权限。再检查多屏坐标。多显示器时副屏坐标可能是负数比如-1920,0。如果你用固定0,0作为左上角永远截不到副屏。使用ImageGrab.grab(all_screensTrue)可以获取包含所有屏幕的完整画布再根据相对坐标裁剪。最后检查 Linux 桌面环境。Wayland 会话对全局截图的权限限制比 X11 严格Pillow 的ImageGrab在很多 Wayland 环境下直接拿不到图像。这种情况需要改用桌面环境提供的截图能力比如gnome-screenshot或grim。6.2 OCR 识别为空、中文乱码、识别错字OCR 返回空字符串最常见的三个原因是语言包没装、图像区域太小、文字背景复杂。检查顺序如下tesseract --list-langs如果输出里没有chi_sim中文就识别不出来。如果语言包没问题就检查截图的文字是否足够大。屏幕上常见的 DPI 缩放会让实际字体很小OCR 之前先对图像做 2 倍放大。中文乱码还有一个常见原因是语言包安装不完整或者使用了eng单语言去识别中文。推荐用chi_simeng混合语言这样中英文混排的报错信息都能覆盖。6.3 Harness 不识别插件、命令路径找不到现象是 Harness 日志里提示命令不存在或者工具调用失败。优先检查两点。第一command配置里写的是相对路径还是绝对路径。Harness 的工作目录可能和你执行命令的目录不同相对路径很容易失效。建议用项目根目录下的绝对路径或者脚本入口避免依赖工作目录。第二检查 Python 进程是否能找到依赖包。Harness 执行python3时使用的可能是系统 Python而不是你创建虚拟环境里的 Python。建议在插件入口脚本里显式指定虚拟环境#!/usr/bin/env bash source /path/to/screen-helper/.venv/bin/activate python /path/to/screen-helper/scripts/capture_and_ocr.py $这样可以避免因环境 PATH 不同导致导入PIL或pytesseract失败。6.4 thinking 模式报 reasoning_content 400这是接入 DeepSeek 模型时非常典型的报错。多轮对话进行到第二轮或第三轮时上游返回 400关键日志是upstream_status: http 400 cause: the reasoning_content in the thinking mode must be passed back to the api.原因是服务开启了 thinking 模式第一轮响应里会返回reasoning_content字段下一轮请求要求把上一轮的reasoning_content原样回传。插件在给 Harness 注入上下文时如果重新组装的请求消息数组丢掉了这个字段就会触发 400。排查路径按下面顺序走查看请求日志确认是不是从第二轮开始失败。查看 Harness 是否在会话持久化里保留了reasoning_content。查看注入上下文是否重建了整个消息数组覆盖了原始消息字段。如果不需要思考过程在配置里关闭 thinking 模式或让它保持透传。这个问题的根源通常不在识屏插件本身而是插件追加上下文消息时破坏了原有多轮消息结构。修改时要注意保留原始消息字段而不是只追加一条新消息。注意给 Harness 增加上下文时不要直接重建整段消息数组。应该以“追加一条用户侧上下文”的方式保留原有消息的role、content、reasoning_content等字段。6.5 上下文超长和 Token 成本问题屏幕 OCR 结果动辄几百上千字如果每轮对话都注入很容易超出模型上下文窗口造成请求被截断或者费用上涨。解决思路是给识屏上下文加“有效期”。只在用户主动触发、或者模型明确请求读取屏幕时才注入本次识屏结果。注入时还可以做文本精简去掉重复的空白行和装饰性字符。只保留包含关键字error、warning、fail、module、line的行。对长文本做摘要而不是全量注入。Harness 启动时如果执行pnpm dsh web卡住通常也是依赖安装或端口占用问题这一类属于环境问题不在屏幕插件的职责范围内但会直接影响插件开发调试。先确认node_modules完整、目标端口未被占用、日志里没有EADDRINUSE再继续调试插件避免把环境问题误判成插件问题。7. 生产环境建议与可复用清单7.1 生产环境必须做的五件事如果这个插件不只是自己电脑上用而是要放进团队工具链下面五件事不能省。第一权限最小化。插件默认只读用户显式指定区域不启动后台全屏扫描。需要监听屏幕变化时必须经过用户确认。第二敏感信息脱敏。注入上下文前过滤 email、手机号、路径、云凭证等模式。脱敏规则放到独立配置不要硬编码在插件代码里。第三日志审计。识别操作要记录触发时间、截图区域、输出长度但不记录完整截图内容。日志字段需要经过脱敏检查才能入库。第四资源限制。OCR 是 CPU 密集操作要限制最大分辨率、单次识别超时、并发调用数。给 Harness 的调用入口设置 15 秒到 30 秒超时避免拖垮主线程。第五可回滚。插件配置变更前保留上一份可用版本。Harness 升级后先在小样本里回归验证插件命令是否还能被正确调用。学习环境与生产环境的差异如下关注点学习/本地调试生产/团队使用截图范围全屏、任意区域显式白名单区域隐私处理手工判断自动脱敏 审计日志OCR 引擎本机 Tesseract统一服务版本便于调参超时控制可长时间等待15 秒内必须返回参数管理直接改代码配置外置化回归验证手动跑几条命令固定样张自动测试7.2 发布前检查清单这个清单可以在接入 Harness 之前逐项勾选避免上线后反复调试[ ] 截图命令在 Harness 工作目录下能直接执行不依赖当前终端目录。[ ] 截图区域坐标覆盖多显示器场景不出现负坐标或黑屏。[ ] Tesseract 语言包已安装tesseract --list-langs输出包含目标语言。[ ] OCR 结果以结构化 JSON 返回而不是裸文本。[ ] 注入上下文的文本带screen来源标记模型可区分来源。[ ] 敏感信息过滤规则已生效JSON 输出里看不到明显密钥或手机号。[ ] 单次调用延迟在预期范围内。[ ] 多轮对话场景下没有丢失reasoning_content导致 400。[ ] Harness 升级后插件命令仍然在配置的插件列表里被识别。7.3 扩展方向多模态、窗口事件、监控模式识屏插件做到这一步已经能解决“智能体看不见屏幕”的基础问题。继续扩展有四个方向比较实用。第一个方向是接入多模态模型。OCR 的瓶颈在于它只能提取文本丢失了布局、颜色、图表信息。如果使用的模型支持视觉输入可以直接把截图区域以图片形式传给模型让模型自己理解界面布局。这样识别精度更高但 Token 和费用也会更高。第二个方向是活动窗口识别。通过操作系统 API 获取当前前台窗口的标题、进程名和位置自动判断用户在看编辑器还是终端再把识别区域对准目标窗口。第三个方向是屏幕变化监控。定时截取同一区域的图像对比像素差异只在发生变化时触发 OCR。这对开发调试场景很有用可以实时捕获一闪而过的报错弹窗。第四个方向是区域收藏与命令绑定。把常用的识别区域存成命名区域比如terminal、design、browser插件入口直接收--preset terminal减少每次输入坐标的成本。这些扩展的核心判断是识屏插件只是一个感知层它的价值不在于 OCR 算法多强而在于能不能用最小的成本把屏幕信息准确、安全地变成模型可用的上下文。把基础链路做稳再按需扩展是这条路最务实的走法。