- 人工智能
- 大模型
- AI Agent
- RAG
- 工具调用
- MCP 服务
- MCP Clients
- 多智能体
【免费下载链接】CangjieMagic
基于仓颉编程语言构建的 LLM Agent DSL,其主要特点包括:声明式 DSL、支持 MCP 协议,支持任务智能规划等。
magic.jsonable是 CangjieMagic 中负责"数据与 JSON 双向转换、类型模式描述与语义约束校验"的基础包。它通过Jsonable<T>接口、TypeSchema类型模式体系和@jsonable宏,把任意class/struct/enum声明式地变成"可序列化、可反序列化、可校验、可被 LLM 结构化输出解析"的数据类型。读完本文,你将掌握该包全部公开 API 的语义、@jsonable/@field/@alias/@constraint宏的组合用法,以及它在 Agent 结构化输出(chatGet<T>)中的约束修复闭环原理。
一、包定位:LLM 结构化数据的"地基"
在 CangjieMagic 的架构里,magic.jsonable位于最底层,被多个上层模块依赖:
- Agent 的
chatGet<T>(abs_agent.cj)通过T.getTypeSchema()拿到类型模式,要求模型按此输出 JSON,再用T.fromJsonValue解析成强类型对象; - Agent Executor 会把
outputSchema注入一个answer工具(opertors.cj),引导模型以指定结构作答; - 工具参数定义
ToolParameter直接以TypeSchema作为参数类型模式(tool_parameter.cj); @agent的 DSL 层在生成 AI 函数声明时,也用getTypeSchema()描述返回值(ai.cj)。
也就是说,只要理解jsonable,就理解了 CangjieMagic 中"如何让模型输出可信的结构化数据"的完整链路。
二、核心 API 全景:六大构件
文档定义的六个公开类型全部位于 src/jsonable/jsonable.cj 与 src/jsonable/type_schema.cj 中,先看一张全景:
| 类型 | 作用 |
|---|---|
ToJsonValue | 将实现类型转换为JsonValue(序列化方向) |
FromJsonValue<T> | 从JsonValue还原出T(反序列化方向) |
Jsonable<T> | 同时继承上述两者,并补充类型模式与语义校验 |
ConstraintValidation | 校验结果:Pass或携带失败消息的Fail(String) |
JsonableException | 序列化/反序列化/校验失败时抛出的异常 |
FieldSchema/TypeSchema | 描述字段与对象/数组/枚举的结构化类型模式 |
2.1 序列化接口 ToJsonValue
public interface ToJsonValue { func toJsonValue(): JsonValue }任何实现了toJsonValue()的类型都可以把自己变成一个JsonValue(JsonString、JsonInt、JsonArray、JsonObject等,来自stdx.encoding.json)。后续可继续调用toJsonString()输出标准 JSON 文本。
2.2 反序列化接口 FromJsonValue
public interface FromJsonValue<T> { static func fromJsonValue(json: JsonValue): T }注意它是静态方法:由 JSON 值构造一个T。实现方必须处理类型不匹配的情况——通常抛JsonableException。
2.3 综合接口 Jsonable
public interface Jsonable<T> <: FromJsonValue<T> & ToJsonValue { static func getTypeSchema(): TypeSchema func validate(): ConstraintValidation }Jsonable<T>把三个能力合并在一起:
getTypeSchema():返回T的类型模式;validate():对当前值执行语义约束校验,返回Pass或Fail(消息);- 继承的
fromJsonValue/toJsonValue:完成双向转换。
文档特别强调了三点实现约定(在 jsonable.cj 中均有对应):
@jsonable宏生成的类型会自动实现该接口;@constraint[...]声明的约束会在validate()中被求值;fromJsonValue在反序列化后必须调用validate(),校验失败要拒绝该值(抛异常)——这正是"LLM 输出不可信"问题在数据层的兜底。
另外源码中注释了一处平台限制(jsonable.cj):由于 cjc 0.53.x 存在 bug,Jsonable暂时无法提供fromJsonStr(String)便捷静态方法,反序列化需先JsonValue.fromStr(...)再走fromJsonValue。
2.4 校验结果 ConstraintValidation
public enum ConstraintValidation { | Pass | Fail(String) }Pass表示校验通过;Fail携带一条人类可读的失败消息(例如Constraint violation in ConstrainedData.number: number > 0),该消息会原样出现在后续的修复提示语中。
2.5 异常类型 JsonableException
public class JsonableException <: Exception { public init(msg: String) { super(msg) } }所有 JSON 转换失败统一抛此异常,例如:
Convert to Int error. Value: ...(类型不匹配)Get JSON object error.(非对象)Get JSON object field \score` error`(必填字段缺失)Constraint violation in ...(约束校验失败)
三、FieldSchema 与 TypeSchema:类型模式的完整描述
3.1 struct FieldSchema
public struct FieldSchema { public FieldSchema ( public let name: String, public let description: String, public let typeSchema: TypeSchema, public let required!: Bool = true, public let constraints!: Array<String> = [] ) { } }一个字段包含五个要素(type_schema.cj):
name:JSON 中的字段名(可能不同于仓颉成员名,如@alias之后);description:人类可读的字段说明(由@field注入),会写入 schema 供 LLM 参考;typeSchema:字段值的类型模式;required:是否必填,默认 true;Option<T>字段会自动置为 false;constraints:挂在字段上的语义约束表达式字符串数组(由@constraint注入)。
3.2 enum TypeSchema
public enum TypeSchema <: ToJsonValue & ToString { | Str | Int | Float | Boolean | Arr(TypeSchema) | Enum(Array<String>) | Obj(Array<FieldSchema>) }七种模式完整覆盖了 JSON 的基本类型(type_schema.cj):
| 变体 | 含义 | 序列化结果 |
|---|---|---|
Str | 字符串 | {"type": "string"} |
Int | 整数 | {"type": "integer"} |
Float | 浮点数 | {"type": "number"} |
Boolean | 布尔 | {"type": "boolean"} |
Arr(TypeSchema) | 数组,元素为指定模式 | {"type": "array", "items": ...} |
Enum(Array<String>) | 枚举,限定可选字符串 | {"type": "string", "enum": [...]} |
Obj(Array<FieldSchema>) | 对象,含字段清单 | {"type": "object", "properties": ..., "required": [...]} |
3.3 关键实现:toJsonValue 生成 JSON Schema
TypeSchema的toJsonValue()实现(type_schema.cj)将自身递归转换为近似 JSON Schema 的结构,这正是喂给 LLM 的"输出格式说明书":
- 对象模式会为每个字段生成
properties条目,并合并description与constraints两个扩展键(当它们非空时); - 只有必填字段(
field.required == true)进入required数组; - 若对象没有字段(如
TypeSchema.Obj([])),则不输出properties/required键。
toString()直接复用toJsonValue().toJsonString(),因此TypeSchema也可以被当作字符串直接拼进提示语。
四、@jsonable 宏:从声明到实现的代码变换
@jsonable是 CangjieMagic DSL 层(宏包magic.dsl)提供的声明式入口(jsonable.cj),支持class、struct和穷举enum(jsonable.cj),还支持dump属性用于打印生成代码调试:
@jsonable[dump: true] class Foo { ... }4.1 宏生成了什么
transformJsonableType(jsonable.cj)把:
@jsonable class F { let a: String let b: Int64 }变换为等价于:
class F <: Jsonable<F> { let a: String let b: Int64 public init(...) { ... } // 全字段构造器 static func getTypeSchema(): TypeSchema { ... } // 收集 FieldSchema,返回 TypeSchema.Obj static func fromJsonValue(json: JsonValue): F { ... } // 逐字段反序列化 + validate func toJsonValue(): JsonValue { ... } // 逐字段序列化 func validate(): ConstraintValidation { ... } // 逐约束求值 }各生成方法的实现要点:
- 构造器(L215-L246):为每个成员变量生成带
!的命名参数;有默认初始化表达式的成员参数带默认值,可选参数排在最后。 - getTypeSchema(L248-L285):为每个字段构造
FieldSchema,其中required = !isOption(fieldType),并把@field描述、@alias别名、@constraint约束字符串都填入。 - fromJsonValue(L287-L355):按三种情况取字段值——有默认值则缺失时用默认值;
Option<T>缺失时用JsonNull();其余必填字段缺失时抛JsonableException(消息带字段的 JSON 名,别名也会出现在消息里)。构造完成后立刻value.validate(),失败则抛JsonableException。 - toJsonValue(L357-L393):
Option<T>字段若序列化为JsonNull则跳过不输出;其余字段全部写入。 - validate(L395-L414):按字段声明顺序、字段内约束声明顺序逐个求值,一旦某个约束为假立即返回
Fail,消息格式为Constraint violation in <类型>.<字段>: <约束表达式>——所以"返回第一个失败的约束"是有意设计(测试 testConstraintValidateReturnsFirstFailureInDeclarationOrder 专门验证了这一点)。
4.2 枚举的自动支持
@jsonable也支持穷举枚举(L416-L507):
getTypeSchema()返回TypeSchema.Enum([ctorName, ...]);fromJsonValue要求输入必须是 JSON 字符串且匹配某个构造器名,否则抛异常;toJsonValue把枚举值映射回其名字符串;- 带类型参数的构造器与 ellipsis 枚举会被宏拒绝(抛
DslException)。
五、配套宏:@field、@alias、@constraint
三个字段级宏都通过assertParentContext("jsonable")强制只能用在@jsonable类型内部。
5.1 @field:给字段写说明
field.cj 接受一个字符串字面量作为字段的人类可读描述,会进入FieldSchema.description,最终写入 schema 供 LLM 理解字段含义(支持多段字符串,拼接时以换行连接):
@jsonable class MyDateTime { @field["Year of the foundation"] let year: Int64 }5.2 @alias:JSON 字段别名
alias.cj 把仓颉成员名映射为 JSON 中实际使用的键名。别名同时作用于序列化、反序列化和 schema 输出:
@jsonable class AliasedOptionalData { @alias["user_name"] let name: String }测试 testAliasAndOptionalRoundTrip 证实:序列化结果含user_name而不含name;反序列化时也按user_name取键;schema 中required列表输出的是别名。
5.3 @constraint:语义约束
constraint.cj 接收一个布尔表达式,支持任意合法的仓颉表达式,包括字段间引用(this指向当前对象):
@jsonable class ConstrainedData { @constraint[number > 0] @constraint[number < limit] let number: Int64 let limit: Int64 @constraint[name.size < 10] @constraint[this.alias.size < this.name.size] let name: String let alias: String }同一字段可以叠加多条约束,validate()按声明顺序求值并返回第一个失败项。测试 testConstraintValidate 验证了合法值返回Pass、非法值返回Fail("Constraint violation in ConstrainedData.number: number > 0");testConstraintSchema 还验证了约束表达式会原样写入 schema 的constraints键,供 LLM 在生成时提前规避。
六、内置类型适配:开箱即用的 Jsonable
magic.jsonable为常用类型提供了现成的Jsonable扩展(jsonable.cj),无需任何额外声明:
| 类型 | 类型模式 | 反序列化规则 | 备注 |
|---|---|---|---|
String | Str | JSON 字符串;null视为"" | L52-L70 |
Int64 | Int | JSON 整数;null视为0 | L72-L90 |
Float64 | Float | JSON 浮点或整数;null视为0.0 | L92-L110 |
Bool | Boolean | JSON 布尔;null视为false | L112-L130 |
Array<T> | Arr(T的schema) | JSON 数组,逐元素转换 | L132-L159 |
ArrayList<T> | Arr(T的schema) | 同上 | L163-L193 |
Option<T> | 与T相同 | JSONnull→None,否则Some(T) | L197-L226 |
HashMap<String, T> | 无(抛异常) | JSON 对象,键值转换 | L228-L261 |
JsonObject | Obj([]) | 直接透传 | L263-L279 |
几个值得注意的设计:
Option<T>的类型模式就是T本身,因为"可选"是通过required=false表达的(见 3.1),而不是引入新的模式变体;HashMap<String, T>拒绝提供 schema(getTypeSchema()直接throw JsonableException("HashMap has no type schema")),因为它字段不确定、无法用固定 schema 描述——但它仍可序列化/反序列化为 JSON 对象;- 所有内置类型的
validate()都返回Pass,语义约束只对@jsonable声明的类型生效。
七、实战:chatGet 与约束修复闭环
jsonable 在 Agent 中最典型的应用是chatGet<T>(abs_agent.cj):
public func chatGet<T>(request: AgentRequest): Option<T> where T <: Jsonable<T> { request.outputSchema = T.getTypeSchema() // 1. 注入类型模式 ... }调用流程如下:
- 将
T.getTypeSchema()设为request.outputSchema,Executor 据此注入answer工具、要求模型按 schema 输出; - 模型返回文本后,用
ParserUtils.extractFirstValue<T>解析成T; - 若解析失败(
ParserException),把Output does not follow the type schema: ${schema}追加进对话并让模型重试修复; - 若
fromJsonValue内部约束校验失败(抛JsonableException),把Output violates semantic constraints. Validation error: ${ex.message}追加进对话重试修复; - 重试次数受
Config.outputRepairRetryNumber控制,默认 3(config.cj);耗尽后chatGet返回None,而不是抛出异常。
仓库中的 jsonable_constraint_verifier 示例完整演示了这一闭环:
@jsonable private class ScoreCard { @constraint[score > 0] @constraint[score <= maxScore] let score: Int64 let maxScore: Int64 } let scoreCard = agent.chatGet<ScoreCard>("generate a score card").getOrThrow()该示例覆盖了"约束失败触发修复并记录校验提示""非 JSON 输出触发 schema 修复""混合修复""必填字段缺失修复""重试耗尽返回 None""上游模型失败原样抛出"等场景(main.cj),并可通过LLM_API_KEY、LLM_MODEL、LLM_BASE_URL环境变量对接真实模型跑端到端验证(L289-L452)。配套的 json_output_test.cj 还用chatGet<MyDateTime>实测了模型返回结构化年份数据。
八、使用建议与注意事项
- 把业务数据模型声明为
@jsonable,即可同时获得序列化、类型模式与约束校验,无需手写样板代码; - 用
@field写清字段语义、用@constraint表达跨字段业务规则,两者都会进入 schema,让 LLM 在生成阶段就尽量合规,减少修复轮次; - 用
Option<T>表达可选字段,序列化时None会被省略,反序列化时缺失或显式null都会还原为None; HashMap可作容器但不能作chatGet<T>的返回类型(无 schema);需要结构化输出时请定义@jsonable类;- 重试次数可调:
Config.outputRepairRetryNumber默认 3,对强约束场景可适当调大,对延迟敏感场景可调小; - 涉及枚举时确保是穷举枚举,否则
@jsonable宏会拒绝编译。
九、总结
magic.jsonable以"接口 + 枚举模式 + 宏变换"三层结构,把 JSON 序列化、类型模式生成与语义约束校验统一到Jsonable<T>一个契约之下。它既是chatGet<T>结构化输出的数据契约来源,也是 LLM 输出可靠性兜底的第一道防线:schema 指导生成、约束拦截非法值、修复闭环让模型自纠。理解这一层,你就能在 CangjieMagic 中轻松构建"模型输出 → 强类型可信数据"的完整管线。
延伸阅读:包级 API 总览见 package_docs/jsonable.md;DSL 宏定义见 package_docs/dsl.md;完整测试见 jsonable_test.cj;端到端示例见 jsonable_constraint_verifier。
- 人工智能
- 大模型
- AI Agent
- RAG
- 工具调用
- MCP 服务
- MCP Clients
- 多智能体
【免费下载链接】CangjieMagic
基于仓颉编程语言构建的 LLM Agent DSL,其主要特点包括:声明式 DSL、支持 MCP 协议,支持任务智能规划等。
相关推荐
API Star 类型系统(Type System)完全指南:用声明式约束搞定接口输入验证与输出序列化
API Star 类型系统(Type System)完全指南:用声明式约束搞定接口输入验证与输出序列化 API Star(当前仓库版本 0.7.2)自带一套类型
开发工具API设计Todo Tree终极指南:VSCode中高效管理TODO标签的完整解决方案
Todo Tree终极指南:VSCode中高效管理TODO标签的完整解决方案 Todo Tree是一款基于ripgrep的VSCode扩展,能够快速扫描项目中的
开发工具CangjieMagic 声明式 DSL 宏指南:magic.dsl 包全解析
CangjieMagic 声明式 DSL 宏指南:magic.dsl 包全解析 magic.dsl 是 CangjieMagic 中基于仓颉(Cangjie)宏
人工智能大模型AI AgentRAG工具调用MCP 服务MCP Clients多智能体
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考