资讯动态

Canon:用受控英语让提示词与Agent通信更可解析

发布时间:2026/8/28 17:54:38 来源:尧图企业网站定制
大模型的提示词写多了以后很多人会有一种感觉同一个任务换一种说法结果就完全变了。更麻烦的是当多个模型 Agent 互相调用时A 发出的消息 B 不一定能理解因为两边都在用自由自然语言。Canon 这个词在这里与打印机、相机品牌无关它指的是一套面向模型提示model prompting和 Agent 间通信agent-to-agent communication的受控英语Controlled English。受控英语不是让机器学会自然语言而是反过来让人和 Agent 在用词、句式和语义上先收敛到一个可预测的子集里。本文会围绕 Canon 的设计思路先解释受控英语解决什么问题再给出一个可落地的 Canon 语法子集然后用它分别编写模型提示和 Agent 通信消息最后提供一个轻量校验模块、运行验证方法和常见问题排查清单。学完后你可以把同一套语法应用到自己的提示词工程和多 Agent 协作场景中让提示更可复现让消息更容易被解析和校验。1. 先理解受控英语为什么提示和 Agent 通信需要受限语言1.1 什么是受控英语受控英语Controlled English是自然语言的一个受限子集它在词汇、语法、句式上做了明确规定只允许使用者按规则写出符合要求的句子。它在工业界已经有成熟的先例例如航空航天领域的简化技术英语 ASD-STE以及自然语言处理研究中的 Attempto Controlled EnglishACE。这些语言的共同点是看起来像英语读起来像英语但机器可以用规则精确解析不会因为歧义产生多种理解。放在大模型场景里受控英语的价值会被放大。模型提示词本质上是一段自然语言指令Agent 之间的消息本质上是自然语言请求和响应。自由自然语言有丰富的表达力但也带来歧义、漂移和不可验证的问题。Canon 的思路就是给提示和消息定一套规则让内容在可控的表达范围内流动。1.2 自由自然语言提示的三类问题第一类是歧义。同一个句子在不同上下文里有不同含义例如“请处理数据”到底是清洗、转换、删除还是统计模型只能靠猜测。第二类是漂移。同一个任务用不同措辞表达模型的输出格式、粒度和风格可能完全不同这导致下游解析困难。第三类是不可验证。自由文本没有强约束你很难在请求发出前检查它是否完整、是否包含禁忌内容、是否指定了输出格式。这三类问题在单模型提示中就已经存在在 Agent 通信中会被放大。Agent A 用一段含混的话请求 Agent B 查询库存Agent B 返回的结果可能缺少关键字段A 又得再问一遍。最终整个系统的行为是概率性的很难定位问题出在哪个环节。问题典型现象Canon 的应对方式歧义同一指令被模型理解成不同任务限定动词集合和固定句型漂移输出格式、粒度每次不同显式声明 output_format 约束不可验证消息发出前无法检查是否合规词法与句法层做机器校验意图不明Agent 收到消息不知道要做什么强制使用 action 字段声明动作1.3 Canon 的核心定位提示模板与消息契约的统一语言Canon 的一个关键设计是把“模型提示”和“Agent 通信”放在同一套语言框架下。提示词可以看作人给模型的一条 Canon 消息Agent 之间的请求响应也可以看作 Agent 之间交换的 Canon 消息。这样有一个直接好处提示词校验器、消息解析器、模板渲染器可以共用一套代码。Canon 的设计目标可以概括为四点受限词表、固定句型、显式参数、机器可查。受限词表控制动词和修饰词的范围固定句型限制表达方式显式参数把变量从自然语言中抽出来避免模型自由发挥机器可查则保证任何一段 Canon 文本都可以在发送前被程序校验。2. Canon 语法设计一套可解析的受限英语子集这里给出的 Canon 语法是围绕标题所述目标整理的一个可实践版本不是唯一的官方定义。实际项目落地时需要根据所用模型的能力和业务场景调整词表和句型。2.1 词法层动词、参数与禁用表达词法层负责定义哪些词可以用。动词是 Canon 的核心因为动词决定了消息的意图。下面是一组最常用的 Canon 动作动词。动词语义典型场景request请求对方执行动作Agent 之间发起任务confirm确认信息或任务已收到响应请求reject拒绝请求并给出原因无法执行时report返回事实或结果任务完成后的结果上报query查询某个属性信息检索场景notify主动通知事件发生事件驱动协作retry要求对方重试失败恢复cancel取消已发出的请求任务撤销complete声明任务已完成任务收尾fail声明任务失败异常上报除了动词词法层还定义参数占位符、数据类型和修饰词。参数统一用[参数名]表示数据类型用类型表示例如int、float、bool、date、path、enum:a|b|c。修饰词则限制语义例如within 30 seconds表示时限must与must not表示义务和禁止。2.2 句法层Canon 的七种基本句型句法层定义消息可以长成什么样。Canon 把消息分为请求、报告、查询、决策、通知、确认、错误七类每一类都对应一种固定句型。固定句型的作用是让解析器可以用规则或正则直接判断消息类型而不需要依赖模型理解。请求request TARGET with PARAMS within TIME报告report: SUBJECT is VALUE查询query: ATTRIBUTE of SUBJECT决策decision: OPTION selected通知notice: EVENT occurred确认ack: MESSAGE_ID received错误error: ACTION failed because REASON例如一个库存查询请求可以写成request inventory_check with skuA1001 quantity2 within 30 seconds。解析器不需要理解业务只需要匹配request开头、随后是目标、with后跟参数对、within后跟时间限制就能得到结构化结果。2.3 语义约束层否定、条件、时限与角色语义约束层回答“这句话在什么条件下成立、对谁成立”。Canon 对否定做了严格要求只能使用must not和cannot禁止使用双重否定和含混否定词。条件表达使用固定形式if CONDITION then ACTION并且条件本身要写成可判定的表达式例如if stock_level 10 then notify restock。时限统一使用within加时长或绝对时间戳避免“尽快”“稍后”这类模糊表达。角色则通过from、to、reply_to字段显式指定消息发送方、接收方和回复对象不能隐藏在自然语言上下文里。这样做的原因是Agent 之间协作时上下文窗口有限不能靠“记住上一句话”来推断角色。3. 用 Canon 编写可复现的模型提示3.1 一个 Canon Prompt 的最小结构模型提示可以看作发送给模型的一条 Canon 元消息。推荐的最小结构包含版本号、角色、任务、输入、输出格式和安全约束。以下是一个 YAML 风格的 Canon Prompt 示例。canon-prompt: v1.0 role: assistant task: summarize subject: meeting_notes input: text: text max_words: 150 output_format: heading: summary bullets: true include_fields: [结论, 待办] guardrail: must_not_include: [人名, 报价, 情绪化用语] must_include: [结论, 待办] language: chinese这个结构有几个关键点。role和task是必填的告诉模型身份和任务类型input里把自由文本作为参数传入而不是拼接在指令里output_format明确输出结构guardrail声明哪些内容必须出现、哪些必须禁止。这样写的好处是模型即使出现输出漂移校验程序也能根据guardrail判断输出是否合格。3.2 从自由文本提示改写为 Canon 提示很多人的第一版提示词是这样写的你是一个有帮助的助手。请阅读这段很长的会议记录给我一个简洁的摘要。 不要包含太多数字尽量短一些。语气要专业一点。这段提示至少有四个问题没有说明摘要包含哪些字段、没有指定“短”的具体长度、没有定义“专业”的量化标准、“不要包含太多数字”也没有明确数字上限。用 Canon 改写后每个约束都变成可检查的规则。canon-prompt: v1.0 role: assistant task: summarize input: text: text max_words: 150 max_numbers: 3 style: professional output_format: headings: [结论, 待办] bullets: true guardrail: must_not_include: [情绪化用语] must_include: [结论, 待办]改写背后的逻辑是把人类的模糊期望翻译成程序可以校验的规则。max_words: 150替代“尽量短”max_numbers: 3替代“不要太多数字”style: professional作为模型风格参数而不是自然语言修饰词。3.3 Canon 提示验证脚本与预期输出编写 Canon 提示后可以在发送给模型前先做一次静态校验。校验脚本检查必需字段是否齐全、参数类型是否合法、guardrail 是否有冲突。例如max_words: 150和min_words: 200同时出现就是冲突脚本应当直接报错。校验通过后模型的输出也应该进入同一套校验体系。如果模型返回的摘要包含人名而guardrail.must_not_include中声明了人名则该输出应该被拒绝并要求重试。这样提示词就不再是一次性文本而是可测试的配置。4. 用 Canon 设计 Agent 到 Agent 的通信协议4.1 为什么只有 JSON 还不够多 Agent 系统常见的做法是定义 JSON 消息结构例如{ action: query, params: {...} }。JSON 解决了字段结构化的问题但没有解决语义结构化的问题。action的值到底可以取哪些枚举params里哪些字段必填query和report的语义边界在哪里这些问题 JSON Schema 能约束一部分但无法约束“这句话表达的真实意图”。Agent 之间真正需要的是语言级契约。Canon 消息就是这种契约的载体它规定了动词集合、句型结构、参数绑定和时序规则。接收方不必依赖模型猜测意图而是通过解析规则直接得到意图和参数。4.2 Canon Message 的结构与字段说明一条完整的 Canon Message 由信封部分和业务部分构成。信封部分负责路由、标识、时限和回复关系业务部分负责具体动作。canon-message: v1.0 from: agent_a to: agent_b msg_id: msg-20250101-001 action: request subject: inventory_check payload: sku: A1001 quantity: 2 within: 30 seconds reply_to: msg-20250101-001字段含义如下字段必填含义canon-message是协议版本决定解析规则from是发送方标识to是接收方标识msg_id是消息唯一标识action是动作动词必须是词表中的枚举subject是业务主题决定 payload 的语义payload条件必填动作参数结构由 subject 决定within否超时时间表示要求在多久内响应reply_to否回复的目标消息 IDif否执行条件注意if字段是可选但重要的它把条件从自然语言里抽出来变成显式表达式。例如if: stock_level 10表示只有当库存低于 10 时才执行该动作。4.3 最小双 Agent 通信示例下面模拟两个 Agent 之前的协作。Agent A 请求 Agent B 检查库存Agent B 返回报告如果参数不合法则返回错误消息。Agent A 发出的请求canon-message: v1.0 from: agent_a to: agent_b msg_id: msg-20250101-001 action: request subject: inventory_check payload: sku: A1001 quantity: 2 within: 30 secondsAgent B 库存充足返回报告canon-message: v1.0 from: agent_b to: agent_a msg_id: msg-20250101-002 action: report subject: inventory_check payload: sku: A1001 available: 5 fulfilled: true reply_to: msg-20250101-001Agent B 库存不足或参数非法返回错误canon-message: v1.0 from: agent_b to: agent_a msg_id: msg-20250101-003 action: fail subject: inventory_check payload: reason: quantity_negative reply_to: msg-20250101-001在这个示例中双方不需要对话历史就能理解彼此。action决定了消息类型subject决定了业务含义payload的字段由subject对应的 schema 决定。整个协议是可解析、可校验、可追溯的。5. 落地实现一个轻量 Canon 解析与校验模块下面用 Python 实现一个最小可运行的 Canon 解析与校验模块。它的目标是验证一段 Canon 消息是否符合语法并输出结构化结果或错误列表。5.1 项目结构与依赖canon/ __init__.py lexicon.py grammar.py validator.py renderer.py examples/ prompt_validator_demo.py agent_chat_demo.py依赖只需要 Python 3.8 以上标准库不需要第三方包。lexicon.py定义动词表和数据类型grammar.py定义句型规则validator.py负责解析和校验renderer.py负责把模板渲染成最终消息。5.2 词法与句型规则实现lexicon.py核心代码如下# canon/lexicon.py CANON_VERBS { request: REQUEST, confirm: CONFIRM, reject: REJECT, report: REPORT, query: QUERY, notify: NOTIFY, retry: RETRY, cancel: CANCEL, complete: COMPLETE, fail: FAIL, } ALLOWED_TYPES {int, float, bool, date, path, text} def is_valid_verb(word: str) - bool: return word.lower() in CANON_VERBS def is_valid_type(value: str) - bool: return value in ALLOWED_TYPESgrammar.py中定义动作与句型的映射关系# canon/grammar.py ACTION_PATTERNS { request: r^request\s[a-z_](\swith\s.)?(\swithin\s.)?$, report: r^report:\s*[a-z_] is .$, query: r^query:\s*[a-z_] of [a-z_]$, fail: r^fail:\s*[a-z_] because [a-z_]$, } def match_action(line: str) - str | None: for action, pattern in ACTION_PATTERNS.items(): if re.match(pattern, line.strip(), re.IGNORECASE): return action return None这里的关键点是规则先于模型。Canon 不要求模型理解消息而是要求消息匹配规则。match_action返回的动作就是后续路由和处理的依据。5.3 模板渲染与参数绑定实际使用中Canon 消息通常由模板渲染生成而不是手写。renderer.py负责把模板中的占位符替换为实际参数# canon/renderer.py from string import Template def render_canon_prompt(template: str, params: dict) - str: tpl Template(template) return tpl.safe_substitute(params) def render_canon_message(verb: str, subject: str, payload: dict, meta: dict) - str: lines [ canon-message: v1.0, ffrom: {meta[from]}, fto: {meta[to]}, fmsg_id: {meta[msg_id]}, faction: {verb}, fsubject: {subject}, ] if payload: for key, value in payload.items(): lines.append(f {key}: {value}) if meta.get(within): lines.append(fwithin: {meta[within]}) if meta.get(reply_to): lines.append(freply_to: {meta[reply_to]}) return \n.join(lines)渲染和解析是同一个语言规则的两面。渲染时按照规则生成文本解析时按照规则拆回结构。只要规则一致生成器和解析器就不会出现“说的人以为说清楚了听的人以为没听懂”的问题。5.4 消息校验与错误返回validator.py提供两条入口校验提示词配置校验消息文本。# canon/validator.py import re def validate_message(raw: str) - dict: errors [] required_fields [canon-message, from, to, msg_id, action, subject] lines [line.strip() for line in raw.strip().splitlines() if line.strip()] parsed {} for line in lines: if line.startswith(canon-message): parsed[version] line.split(:)[1].strip() elif : in line and not line.startswith( ): key, _, value line.partition(:) parsed[key.strip()] value.strip() else: # 处理 payload 缩进字段 pass for field in required_fields: if field not in parsed: errors.append(fmissing field: {field}) if parsed.get(action) not in CANON_VERBS: errors.append(finvalid action: {parsed.get(action)}) if parsed.get(within): if not re.match(r^\d\s(seconds|minutes|hours)$, parsed[within]): errors.append(invalid within format) return { valid: len(errors) 0, parsed: parsed, errors: errors, }调用示例如下raw canon-message: v1.0 from: agent_a to: agent_b msg_id: msg-20250101-001 action: request subject: inventory_check within: 30 seconds result validate_message(raw) print(result[valid]) print(result[errors])正常输出是True和空列表。如果漏掉subject或把within写成as soon as possible就会得到对应的错误信息。这就是“机器可查”的落地形态。6. 运行验证从提示执行到 Agent 通信的完整链路6.1 学习环境验证步骤先在一个最小环境里跑通 Canon 的生成、校验、解析三个环节。python -m canon.examples.prompt_validator_demo python -m canon.examples.agent_chat_demo验证时观察三件事。第一模板渲染出的文本是否符合 Canon 语法第二校验器对合法消息返回valid: True对非法消息返回具体错误第三两个 Agent 的示例在交换消息后接收方能否正确解析出action、subject和payload。不要只验证程序能启动还要故意构造几条非法消息确认校验器真的能拦截。6.2 开发与测试环境需要观察的指标进入开发测试阶段建议记录以下指标。指标含义健康值参考解析成功率合法消息中被正确解析的比例接近 100%校验命中率非法消息被拦截的比例接近 100%模型输出合规率模型输出符合 Canon 约束的比例越高越好重试次数因输出不合规而重试的次数越低越好端到端时延请求发出到结果返回的时间满足业务要求如果解析成功率低先检查消息生成器大概率是渲染模板写错了。如果模型输出合规率低说明 Canon 约束超出了模型的理解能力需要简化句型或补充示例。6.3 生产环境需要的额外保障生产环境不能只在代码层面做校验。至少还要补齐配置外置化、日志追踪、版本兼容、权限隔离和回滚方案。Canon 语法本身是版本化的消息中携带v1.0就是为了做多版本兼容。生产环境建议采用“新旧版本并存、逐步迁移”的策略先让所有 Agent 按新语法生成同时接受旧语法的流量解析再逐步下线旧规则。日志方面每条消息的msg_id应当贯穿请求链路方便在问题出现时按 ID 检索。7. 常见问题排查提示走样、Agent 互相听不懂、校验失效7.1 问题一模型输出不再符合 Canon 约束现象明明在提示里写了must_not_include模型还是输出了禁止内容。可能原因有三个。第一模型上下文太长尾部约束被稀释第二约束表达不够突出被淹没在一大段指令里第三约束本身与模型能力不匹配例如要求模型严格输出 YAML但模型常常夹带解释性文字。检查方式把最终发送给模型的完整提示打出来确认约束确实在上下文窗口内用小样本测试不同位置放置约束的效果查看模型输出日志确认不合规的具体内容是什么。解决方案把guardrail放在提示词的最后一段这是模型输出前最容易被注意到的位置如果有多个约束只保留最关键的三四个必要时增加结构化输出或 JSON Mode 作为兜底。7.2 问题二Agent 之间消息无法理解现象A 收到 B 的消息后解析出action为空或者字段对不上。常见原因是双方使用了不同版本的 Canon 词表。A 的词汇表里有notifyB 的词汇表里没有B 发送的 payload 字段名是sku_idA 的 schema 要求的是sku。另一个常见原因是消息里混入了自由文本例如 B 在 payload 里写了note: please check carefully这会让解析器难以判定字段边界。检查方式打开双方的消息日志逐条核对canon-message版本号对比双方法册的动词表、字段表和枚举值。解决方案统一用一个共享的 schema 仓库管理词表和字段定义消息发送前发送方先做一次本地校验遇到无法识别的字段时接收方返回fail消息而不是静默忽略。7.3 问题三校验规则莫名其妙失效现象规则看起来没问题但校验器就是报错或者漏过了非法消息。常见的原因是文本格式问题。YAML 风格的 Canon 文本中字段名和冒号之间多了空格、payload 缩进不一致、消息末尾残留了换行符都可能干扰正则匹配。另一个原因是正则表达式写得太宽松例如.匹配了不该匹配的内容导致非法格式也通过校验。检查方式打印消息文本的原始字节确认没有不可见字符用最小的合法消息逐步增加字段找到触发错误的那一项。解决方案不要把校验逻辑写得太复杂优先使用固定模板渲染加字段级校验处理 payload 时用独立的数据结构而不是依赖缩进解析给每条规则写一组正反用例防止正则回归。7.4 排查顺序按以下顺序排查能覆盖绝大部分问题消息是否是按模板渲染生成的而不是手工拼写。消息的canon-message版本双方是否一致。动词、字段名、枚举值是否在共享词表中注册。字段缩进、冒号、换行是否符合语法规则。正则与校验脚本是否有正反用例覆盖。模型输出侧是否有额外的解释性文字混入。是否已经查看日志中的完整消息原文。8. Canon 语法设计的最佳实践与扩展方向8.1 受控英语设计检查清单设计一套属于自己的受控英语时建议逐项核对以下清单。动词表是否控制在 10 到 20 个之间是否覆盖了业务主要动作。每种动作是否有唯一对应的固定句型。参数是否使用显式占位符而不是嵌在自然语言里。是否明确定义了数值单位、时间格式、枚举范围。是否声明了禁止表达例如否定词、模糊时间词、情绪化用语。是否提供模板渲染器和解析校验器两个配套工具。是否维护了版本号并且允许新旧版本并存。是否为正反用例写了自动化测试。8.2 自由文本与 Canon 的取舍受控英语不是要取代自由文本而是在需要确定性的时候提供约束。两者各有适用场景。维度自由自然语言Canon 受控英语表达力高适合开放生成低只覆盖预定义语义可解析性低依赖模型理解高规则可确定性解析可验证性弱难以预检强发送前可校验上手成本低中高需要维护词表和模板适用场景创意生成、开放对话任务调度、多 Agent 协作、结构化输出实际项目中推荐采用分层的做法外部用户可以用自由语言输入内部先经语义解析映射到 Canon 消息再交给 Agent 执行。这样既保留了用户体验又获得了内部通信的确定性。8.3 扩展方向Canon 可以从三个方向扩展。一是领域词表定制在电商、物流、客服、运维等场景中增加领域动词和字段二是生成式 DSL在 Canon 合法语句的基础上自动生成 SQL、Jinja 模板或 API 调用代码三是多语言受控语言在中文、日文等语言上实现与 Canon 等价的受限子集再通过标准格式互相转换。对于刚接触受控英语的开发者建议先从一个最小的双 Agent 协作场景开始定义 5 个动词、3 个句型、10 个字段跑通生成、校验、解析、执行、返回的完整链路再逐步扩展。这样既能理解 Canon 的核心价值也不会在初期被复杂的词表维护拖住。受控英语不是限制模型的想象力而是把需要确定性的部分锁住把需要创造力的部分留给模型。这是提示工程从“拼运气”走向“可工程化”的关键一步。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价