news 2026/10/3 22:01:33

用仓颉语言写 Coding Agent:cjh 的 Harness 是怎么实现的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用仓颉语言写 Coding Agent:cjh 的 Harness 是怎么实现的

1. 从一次“工具调用跑飞”说起:cjh 的 Harness 到底解决什么问题

如果你最近在折腾仓颉语言,又恰好想给它配一个能自己读代码、改文件、跑测试的 Coding Agent,那你大概率会撞上同一个问题:模型输出的动作,本地执行层接不住。我试过用最朴素的方式拼一个循环——把用户需求丢给模型,模型返回一段“我要读哪个文件、改哪一行”,然后本地照着做。结果第一次跑就翻车:模型返回的 JSON 里字段名少了一个字母,解析直接抛异常,整个会话挂掉,前面攒的上下文全丢。

这就是 Harness 存在的意义。Harness 这个词直译是“马具、缰绳”,在 Coding Agent 语境里,它指的是驾驭大模型的那层工程框架:把模型、工具、项目环境、上下文串成一个可控闭环。模型负责“想”,Harness 负责“让想出来的东西安全落地”。cjh 这个仓颉语言原生的 Coding Agent,核心卖点就是把这层 Harness 用仓颉写出来,吃静态类型和内存安全的红利。

具体来说,一个最小可用的 Harness 要干四件事。第一是上下文管理:当前打开的文件、最近的 diff、依赖关系、历史对话,哪些喂给模型、喂多少、怎么裁剪,都得有策略。第二是工具调用编排:模型说“读文件 A”,Harness 要校验 A 在不在工作区、路径有没有越界、读出来的内容怎么截断。第三是执行循环:模型返回动作序列,Harness 逐个执行,把结果回填给模型,再请求下一轮,直到任务完成或触发终止条件。第四是失败兜底:工具报错、模型超时、参数类型不对,每一种失败都要有明确的回滚或重试路径,而不是让整个进程崩掉。

仓颉在这里的优势很直接。静态类型意味着模型返回的动作结构可以在编译期就定义清楚,字段缺失、类型不匹配这类问题在解析阶段就能拦住,而不是等到执行到一半才炸。内存安全则保证了 Agent 长时间驻留、反复处理大段代码文本时,不会因为一处越界把整个会话搞挂。你可以把 Harness 想象成一条流水线:模型是下单的客户,工具是干活的机器,仓颉这套类型系统就是质检员加保险丝,错单早拦截,短路早跳闸。

这篇文章不聊虚的,直接拆 cjh 的 Harness 层怎么组织工具调用、上下文管理和执行循环,然后给你一份可复制的配置片段,最后跑一次完整任务验证。适合谁看?已经在用仓颉写项目、想给它加一个本地 Agent 辅助的开发者;或者你对 Coding Agent 的 Harness 设计感兴趣,想看看用系统级语言写这层框架长什么样。下面所有配置和命令都以本地最小闭环为目标,不依赖任何云端托管。

2. 前置准备:TaoToken 接入与仓颉环境确认

在写 Harness 之前,得先把模型侧的通路搭好。cjh 本身是仓颉写的 Agent 框架,但它不绑定特定模型供应商,Harness 层通过标准 HTTP 接口请求模型。我这边实测下来,用 TaoToken 做模型接入比较省事,它提供 OpenAI 兼容的接口格式,Base URL 和 Key 拿到就能用,不需要在 Harness 里写一堆供应商适配代码。

先确认你的仓颉开发环境。仓颉的编译器、包管理工具按官方文档装好,能跑通一个hello world级别的项目。然后确认网络能正常访问模型接口。TaoToken 的 API 地址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 Base URL 用。模型对话入口在https://taotoken.net/api-keys可以管理 Key,接入文档在https://taotoken.net/doc有完整的请求示例。

拿到 Key 之后,先在终端里用 curl 验证一下通路,别急着写仓颉代码。这一步能排除掉大部分“以为是 Harness 写错了,其实是 Key 没配对”的问题:

curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "回复两个字:通了"} ], "max_tokens": 32 }'

如果返回里能看到choices数组,说明模型侧通了。注意model字段填你实际要用的模型 ID,TaoToken 支持的模型列表在文档里有,别照搬我这里的示例。Key 建议用环境变量管理,别硬编码进仓颉源码,后面 Harness 配置里会引用这个变量。

仓颉项目这边,建一个最小工程目录,结构大概是这样:

cjh-harness-demo/ ├── src/ │ ├── main.cj │ ├── harness/ │ │ ├── context.cj │ │ ├── tools.cj │ │ └── loop.cj │ └── config/ │ └── agent.json └── cjpm.toml

cjpm.toml是仓颉的包管理配置,声明依赖和编译目标。Harness 的三个核心模块分开写:context.cj管上下文收集与裁剪,tools.cj管工具注册与参数校验,loop.cj管执行循环。配置文件agent.json放模型接入参数和工具白名单。这样拆的好处是,工具调用出问题只改tools.cj,上下文太长只调context.cj,互不干扰。

有一点要提醒:仓颉的包管理工具和标准库 API 以官方文档为准,我这里给的是结构和逻辑,具体语法你按文档来。别把二手教程里的语法直接抄进项目,版本对不上会编译不过。环境确认这一步花十分钟,能省后面两小时的排障。

3. 可复制配置:Harness 的 agent.json 与工具注册片段

Harness 的配置分两块:一块是模型接入和循环控制参数,放agent.json;另一块是工具注册,在tools.cj里用仓颉的类型系统定义。先看配置文件,这个文件 Harness 启动时读取,决定请求哪个模型、循环最多跑几轮、单次工具输出截断多少字符。

{ "model": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "model_id": "claude-sonnet-4-20250514", "max_tokens": 4096, "temperature": 0.2 }, "loop": { "max_turns": 12, "tool_timeout_ms": 30000, "stop_on_test_pass": true }, "context": { "max_file_chars": 8000, "max_history_turns": 6, "include_recent_diff": true }, "tools": { "whitelist": ["read_file", "write_file", "run_command", "list_dir"], "workdir": "./workspace", "deny_paths": ["/etc", "/root", "../"] } }

几个参数值得展开说。max_turns控制执行循环最多跑多少轮,防止模型陷入“改一下、跑一下、再改一下”的死循环,12 轮对大多数小任务够用。tool_timeout_ms是单个工具执行的超时,跑测试命令可能慢,设 30 秒比较稳。stop_on_test_pass是个实用开关:如果任务目标是让测试通过,一旦测试全绿就提前终止循环,不用等模型自己说“我完成了”。deny_paths是安全底线,工具执行前先校验路径,任何试图访问工作区外部的动作直接拒绝。

工具注册这块,仓颉的静态类型优势体现得最明显。每个工具定义成一个结构体,参数类型写死,Harness 在解析模型返回的动作时,先做类型匹配,匹配不上直接返回错误给模型,而不是硬着头皮执行。下面是一个工具注册的示意结构,具体语法按仓颉官方文档调整:

// tools.cj 工具注册示意 public struct ToolCall { public let name: String public let args: HashMap<String, String> } public interface Tool { func name(): String func validate(args: HashMap<String, String>): Bool func execute(args: HashMap<String, String>): String } public class ReadFileTool <: Tool { public func name(): String { "read_file" } public func validate(args: HashMap<String, String>): Bool { // 校验 path 参数存在且不在 deny_paths 内 match (args.get("path")) { case Some(p) => !isDenied(p) case None => false } } public func execute(args: HashMap<String, String>): String { // 读取文件并截断到 max_file_chars } }

validate和execute分开是关键设计。模型返回的动作先过validate,参数缺失、路径越界、工具不在白名单,全部在这一步拦掉,返回结构化错误给模型让它重试。只有校验通过才进execute。这样执行层永远拿到的是合法输入,不会出现“读到一半发现路径不对”的尴尬。

工具白名单在配置里声明,Harness 启动时只注册白名单里的工具。模型如果返回一个没注册的工具名,Harness 直接回“工具不存在”,模型下一轮就会换一个。这套机制配合仓颉的类型校验,能把大部分模型幻觉挡在执行层外面。配置片段可以直接复制到你的项目里,改workdir和deny_paths适配你的目录结构,model_id换成你实际用的模型。

4. 跑通一次完整任务:从需求到测试通过的验证动作

配置就位后,跑一个真实任务验证 Harness 闭环。我选的任务很小但完整:在工作区里有一个仓颉源文件,里面有个函数返回值写错了,让 Agent 读文件、定位问题、改掉、跑测试确认。这个任务覆盖了读、写、执行三类工具,能验证上下文管理、工具调用、执行循环三条链路。

工作区准备一个workspace/calc.cj,内容故意留个 bug:

public func add(a: Int64, b: Int64): Int64 { return a - b // 故意写错,应该是 a + b }

再准备一个测试文件workspace/calc_test.cj,断言add(2, 3) == 5。然后启动 Harness,把任务描述传进去:

任务:workspace/calc.cj 里的 add 函数行为不对,请定位并修复, 修复后运行测试确认通过。

Harness 的执行循环大致这样走。第一轮,上下文层收集calc.cj和calc_test.cj的内容,拼进提示词,请求模型。模型返回一个动作序列:先read_file读calc.cj,再read_file读calc_test.cj。Harness 校验两个动作的 path 参数都在工作区内,执行,把文件内容回填。第二轮,模型看到内容后返回write_file动作,把return a - b改成return a + b。Harness 校验通过,写入文件。第三轮,模型返回run_command,命令是跑测试。Harness 校验命令在白名单内,执行,拿到测试输出。如果测试通过,stop_on_test_pass触发,循环终止,任务完成。

整个过程你能在日志里看到每一轮的动作、校验结果、执行输出。重点观察两个地方:一是模型返回的动作有没有被validate拦下来过,如果拦了,说明类型校验在起作用;二是上下文有没有超限被裁剪,如果calc.cj很大,max_file_chars会截断,模型可能因此看不到关键行,这时候要调大这个值或者改进上下文策略。

验证成功的标志是测试输出里出现通过信息,且 Harness 日志显示循环在max_turns之前正常终止。如果测试没通过但循环跑满了 12 轮,说明模型没找到正确修法,或者上下文喂得不够,这时候去看日志里每轮的动作,定位是读文件没读到关键行,还是写文件写错了位置。这个任务跑通,说明你的 Harness 最小闭环成立了,后面加更多工具、更复杂的上下文策略,都是在这个骨架上扩展。

5. 本篇常见错排查:401、local proxy failed、reading choices 报错对照

Harness 跑不起来,九成问题出在模型接入和工具执行两个环节。下面按真实报错对照排查,每条都给定位思路。

401 Unauthorized。这个最直接,Key 没配对或者没传。检查agent.json里api_key_env指向的环境变量名,和终端里export的是不是同一个。常见坑是 Key 复制时带了空格,或者用了过期的 Key。用第 2 节的 curl 命令单独验证一次,curl 通了再查 Harness 代码。如果 curl 也 401,去https://taotoken.net/api-keys重新生成一个。

local proxy failed / connection refused。Harness 请求模型时连不上。先确认base_url填的是https://taotoken.net/api,没有多余路径或参数。然后确认本机网络能正常访问外网,没有本地防火墙拦截。如果你在容器里跑 Harness,检查容器网络配置。这个报错和 Harness 代码无关,纯粹是网络通路问题,用 curl 验证最快。

reading choices 报错 / choices 字段为空。请求发出去了,返回也拿到了,但解析choices时出错。两种可能:一是模型返回的是错误结构,比如{"error": {...}},你的解析代码没处理错误分支,直接去读choices就崩了。二是max_tokens设得太小,模型还没输出完整动作就被截断,choices里的内容不完整。排查方法是在 Harness 里把原始响应打出来看,别急着解析。max_tokens建议至少 2048,复杂任务给 4096。

OAuth / token 过期类报错。如果你用的是需要 OAuth 的接入方式,token 有有效期,过期后请求会被拒。TaoToken 的 Key 方式不涉及 OAuth 刷新,直接用 Bearer 头。如果你在 Harness 里混用了两种认证方式,检查请求头是不是重复设置了Authorization。

工具执行超时。tool_timeout_ms到了但命令没跑完。跑测试、编译这类命令可能超过 30 秒,把超时调大,或者把长命令拆成后台执行加轮询。注意超时后 Harness 要能正确终止子进程,别留下僵尸进程占着工作区文件。

路径越界被拒。模型想读工作区外的文件,被deny_paths拦了。这是预期行为,不是 bug。如果确实需要访问某个目录,把它加进白名单,但别把deny_paths整个删掉。安全底线不能松。

排查顺序建议:先 curl 验证模型通路,再单独测工具执行,最后跑完整循环。分层定位比一上来就盯着 Harness 代码看快得多。每次改完配置,重启 Harness 让配置重新加载,别在运行中改文件指望热更新。

6. 把 Harness 用起来:接入文档与长期编码方案

Harness 跑通之后,下一步是把它接到你日常的编码流程里。如果你只是偶尔用一下,模型对话入口够用,直接在https://taotoken.net/api-keys管好 Key,配合接入文档https://taotoken.net/doc里的请求示例,手写几个工具调用就能应付。但如果你想让 Agent 长期驻留在项目里,反复处理读代码、改文件、跑测试这类任务,那 Harness 的稳定性和上下文管理就变得关键。

长期编码场景下,建议把 Harness 的执行循环和你的版本控制绑起来。每轮write_file之前自动打一个快照,测试失败就回滚到上一个快照,这样模型改错了也不会污染工作区。stop_on_test_pass配合快照回滚,能形成一个“改-测-回滚”的自动闭环,你只需要在最后 review 通过的改动。

工具白名单别一次开太多。先开read_file、write_file、run_command、list_dir这四个,跑顺了再按需加。每加一个工具,都要在validate里写清楚参数校验和路径限制。工具越多,模型幻觉的破坏面越大,白名单是控制风险的第一道闸。

上下文策略是长期使用中最需要调的地方。max_file_chars和max_history_turns两个参数直接决定模型能看到多少信息。项目大了之后,全量喂文件不现实,得做检索:根据任务描述先定位相关文件,只喂相关的。这一步可以在 Harness 的上下文层加一个简单的关键词匹配或依赖图遍历,不用上向量检索那么重。

如果你想把 Harness 的能力再往上提一层,比如支持多步规划、跨文件重构,那 Coding Plan 这类方案值得看一下,它在任务拆解和长上下文管理上有更完整的封装。接入文档里对这类场景有说明,按你的项目规模选合适的粒度。核心原则就一条:Harness 是你和模型之间的缰绳,缰绳收多紧,取决于你对模型输出的信任程度。从最小闭环开始,跑稳了再放。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 21:41:10

Hive数仓分层架构与万亿级性能优化实战指南

做数仓三年多&#xff0c;我见过太多从“临时表跑数”起步的团队。刚开始只有几十张Hive表&#xff0c;业务方拉个数也没那么复杂&#xff0c;上午提需求下午就能给。等业务线铺开&#xff0c;报表、指标体系、画像标签、财务对账再到运营看板全堆在同一个集群上&#xff0c;问…

作者头像 李华
网站建设 2026/10/3 21:38:00

AI领域信息流自动化系统设计与落地实践

1. 项目概述&#xff1a;这不是一份“新闻简报”&#xff0c;而是一套可复用的AI信息流自动化生产系统 “AI 日报&#xff08;2026年9月26日&#xff09;”——看到这个标题&#xff0c;第一反应不是点开看内容&#xff0c;而是立刻在脑子里拆解&#xff1a;谁在发&#xff1f;…

作者头像 李华
网站建设 2026/10/3 21:37:32

纯Python+SQLite构建可审计的工业级文本相似度系统

简介&#xff1a;本资源是一份面向计算机专业本科生的毕业设计文档&#xff0c;聚焦文本相似度计算这一自然语言处理核心任务&#xff0c;适用于信息检索、推荐系统等实际场景的技术实现与学习参考。文档以Python为主要技术栈&#xff0c;融合Django Web框架、JSP/Java后端模块…

作者头像 李华
网站建设 2026/10/3 21:37:21

非游戏开发者用AI做微信小游戏:5天开发与27天备案实录

1. 一个从没碰过游戏引擎的人&#xff0c;为什么敢接微信小游戏这个活先说背景。我做了七八年后端和运维&#xff0c;写过接口、搭过流水线、处理过线上告警&#xff0c;但游戏开发这件事&#xff0c;跟我一直没什么交集。Unity 没打开过&#xff0c;Cocos 只听过名字&#xff…

作者头像 李华