☰
CangjieMagic 模型层(magic.core.model)深度解析:Chat / Embedding / Image 模型抽象与接入实战
2026/10/14 8:09:25 网站建设 项目流程
  • 人工智能
  • 大模型
  • AI Agent
  • RAG
  • 工具调用
  • MCP 服务
  • MCP Clients
  • 多智能体

【免费下载链接】CangjieMagic

基于仓颉编程语言构建的 LLM Agent DSL,其主要特点包括:声明式 DSL、支持 MCP 协议,支持任务智能规划等。

项目地址:https://gitcode.com/Cangjie-TPC/CangjieMagic
点击查看免费下载

导读

magic.core.model是 CangjieMagic(仓颉 LLM Agent 框架)的模型抽象层,统一封装了对话(Chat)、向量(Embedding)、文生图(Image)三类模型能力,并通过Model、ChatModel、EmbeddingModel、ImageModel四个接口及配套的请求/响应类型,向上层 Agent DSL、执行器与 RAG 组件提供一致的数据契约。本文基于 docs/package_docs/core.model.md 的 API 参考,结合 src/core/model 下的源码实现与 src/tests/model_test.cj 测试用例,系统讲解每个类型的设计意图、字段语义、同步/异步调用方式与多服务商接入机制,帮助读者在自有项目中正确选用并扩展模型能力。

1. 模型层全景:一个 provider + name 的世界观

在 CangjieMagic 中,任何模型都由两部分标识构成:服务商名称(provider)与模型名称(name)。这一约定定义在Model接口中,位于 src/core/model/model.cj:

public interface Model { prop provider: String prop name: String prop fullName: String { get() { "${provider}:${name}" } } }
  • provider:服务商标识,例如openai、deepseek、dashscope、ollama;
  • name:具体模型名,例如gpt-4o、deepseek-chat;
  • fullName:由接口默认实现拼接出的"provider:name"完整标识,例如deepseek:deepseek-chat。

框架内所有模型配置统一采用<provider>:<model>格式(详见 docs/tutorial.md 的「模型配置」章节)。这一设计使得上层只需传递一个字符串即可完成模型选型,而解析、校验与实例化工作全部交由ModelManager完成。

1.1 三类模型子接口

Model之下按能力划分为三个子接口:

接口定义文件能力核心方法
ChatModelchat_model.cj文本对话(含工具调用)create、asyncCreate
EmbeddingModelembedding_model.cj文本向量化create
ImageModelimage_model.cj文生图create

三者都继承Model,因此天然具备provider/name/fullName属性。源码注释指出未来还可能扩展音频(audio)等模型类型,接口设计为可扩展的开放形态。

2. ChatModel:对话模型的同步与异步双通道

ChatModel是框架中最重要的模型接口,定义于 src/core/model/chat_model.cj:

public interface ChatModel <: Model { prop contextLength: Int64 mut prop maxTokens: Option<Int64> func create(request: ChatRequest): ChatResponse func asyncCreate(request: ChatRequest): AsyncChatResponse }

成员语义如下:

成员类型说明
contextLengthInt64模型的上下文长度(用于提示词裁剪、Token 预算等场景)
maxTokensmut Option<Int64>可动态设置/读取的最大输出 Token 数
create(request)ChatResponse同步 API,阻塞等待完整响应
asyncCreate(request)AsyncChatResponse异步 API,返回可流式消费的响应对象

源码中还保留了被注释掉的便捷重载create(messages: Array<Message>),注释说明因 cjc 0.53.x 存在编译缺陷暂缓加入——这提示开发者:若你的仓颉编译器版本支持重载,可在自定义ChatModel中补充该便捷方法。

2.1 contextLength 与 maxTokens 的默认值

  • contextLength默认取Config.defaultContextLen(在 src/config/config.cj 中定义为32000),并可通过环境变量CHAT_MODEL_CONTEXT_LEN覆盖,解析逻辑见 src/model/model_manager.cj;
  • maxTokens默认None(表示不限制),可在ChatRequest中按请求级覆盖,也可通过模型的maxTokens属性全局设置,最终取请求级优先。

3. ChatRequest:一次对话请求的完整载体

ChatRequest定义于 src/core/model/chat_request.cj,实现ToString & ToJsonValue,可序列化为 OpenAI 兼容的 JSON 请求体。

3.1 字段一览

字段类型说明
messageListMessageList请求携带的完整消息列表
temperatureOption<Float64>采样温度,None表示不向服务端下发该字段
stopOption<Array<String>>停止词列表
toolsArray<Tool>关联的工具列表(用于 Function Calling)
maxTokensOption<Int64>本次请求的最大输出 Token 数

3.2 四种构造器(原文档完整继承)

ChatRequest提供了四种重载构造器,覆盖从单字符串到完整消息列表的各类使用场景,全部参数均可用命名参数(!后缀)按需传入:

// 1. 单条用户消息字符串 init(message: String, temperature!: Option<Float64> = None, stop!: Option<Array<String>> = None, tools!: Array<Tool> = [], maxTokens!: Option<Int64> = None) // 2. 单条 Message 对象 init(message: Message, temperature!: Option<Float64> = None, stop!: Option<Array<String>> = None, tools!: Array<Tool> = [], maxTokens!: Option<Int64> = None) // 3. 消息数组 init(messages: Array<Message>, temperature!: Option<Float64> = None, stop!: Option<Array<String>> = None, tools!: Array<Tool> = [], maxTokens!: Option<Int64> = None) // 4. MessageList 对象 init(messageList: MessageList, temperature!: Option<Float64> = None, stop!: Option<Array<String>> = None, tools!: Array<Tool> = [], maxTokens!: Option<Int64> = None)

其中「单字符串」构造器内部等价于MessageList([Message.user(message)]),即自动包装为一条 user 角色消息。messageList字段直接引用构造时传入的列表,不做拷贝。

3.3 序列化行为(toJsonValue)

toJsonValue()产出的 JSON 结构(对应 OpenAI/chat/completions请求格式):

  • messages:始终输出,由每条消息的toJsonValue()映射而来;
  • temperature:仅当为Some时输出;
  • stop:仅当为Some时输出;
  • tools:仅当非空时输出,每个工具序列化为{"type":"function","function":...};
  • max_tokens:仅当为Some时输出。

toString()直接返回toJsonValue().toJsonString()的结果,便于日志打印与调试。

4. ChatResponse:同步响应的统一封装

ChatResponse定义于 src/core/model/chat_response.cj,是一个open类(可被继承,AsyncChatResponse即继承它),同样实现ToString & ToJsonValue。

open public class ChatResponse <: ToString & ToJsonValue { public init( model: String, message: Message, toolRequests!: Array<ToolRequest> = [], usage!: Option<ChatUsage> = None ) prop model: String // 实际响应的模型名 prop message: Message // 助手消息内容 prop toolRequests: Array<ToolRequest> // 模型请求调用的工具列表 prop usage: Option<ChatUsage> // Token 用量统计 }

字段语义:

属性说明
model产生该响应的模型标识(服务端返回的 model 字段)
message助手角色消息,包含content与可选的reason(思维链内容)
toolRequests当模型决定调用工具时,以ToolRequest数组返回工具名与参数;为空表示纯文本回复
usage本请求的 Token 消耗统计,服务端未返回时为None

toJsonValue()会输出model、message,并条件性输出toolRequests与usage;toString()返回其 JSON 字符串。

4.1 ChatUsage:Token 用量与耗时统计

ChatUsage定义于 src/core/model/chat_usage.cj:

public class ChatUsage <: ToString & ToJsonValue { public let promptTokens: Int64 public let completionTokens: Int64 public let totalTokens: Int64 public let timeCost: Option<Duration> public init(promptTokens!: Int64, completionTokens!: Int64, timeCost!: Option<Duration>) }
  • promptTokens:提示词消耗 Token 数;
  • completionTokens:生成内容消耗 Token 数;
  • totalTokens:构造器内直接计算为promptTokens + completionTokens,不可单独传入;
  • timeCost:请求耗时(Duration),可用于成本与性能观测。

toJsonValue()输出promptTokens/completionTokens/totalTokens,并条件性输出timeCost(JSON 键为timeTost,为源码中已有的拼写,使用时注意);toString()输出形如ChatUsage[completionTokens 100, promptTokens 50, totalTokens 150, timeCost ...]的易读文本。

4.2 同步调用的重试与校验(源码佐证)

在实际使用中,同步调用通常经由 src/model/model_utils.cj 的makeChatImpl完成,它:

  1. 调用model.create(request);
  2. 通过parseChatResponse校验响应:要求消息角色为Assistant,且content、reason、toolRequests至少一项非空,否则视为无效回复;
  3. 无效回复时休眠 500ms 后重试,最多重试Config.modelRetryNumber(默认 3,见 src/config/config.cj)次。

这一层校验解释了为何上层 Agent 能稳定拿到合法响应——空回复、角色异常等情况不会直接冒泡到业务代码。

5. 异步对话:AsyncChatResponse 与 AsyncChatChunk

流式输出是 LLM 对话的标配能力。ChatModel.asyncCreate返回AsyncChatResponse,定义于 src/core/model/async_chat_response.cj。

5.1 AsyncChatChunk:流式分片

public struct AsyncChatChunk <: ToString { public let message!: Message, // 本片段的增量消息 public let done!: Bool = false, // 仅最后一个分片为 true public let usage!: Option<ChatUsage> = None }
  • message:本分片的增量内容(含可选的reason推理内容);
  • done:标记流结束,仅最后一个分片为true;
  • usage:仅在最后一个分片携带完整用量统计。

toString()会同时输出 usage 与 message 内容。

5.2 AsyncChatResponse:聚合与迭代

AsyncChatResponse <: ChatResponse是同步响应的异步版本,关键成员:

成员说明
chunks: Iterator<AsyncChatChunk>底层分片迭代器,供流式消费
iter(withReason!: Bool = true): Iterator<String>返回字符串迭代器;withReason=true时优先取推理内容reason,否则取content
prop message: Message同步阻塞获取完整消息:若流未结束,会先消费全部 chunk 再返回聚合结果
prop usage: Option<ChatUsage>流未结束时返回None,结束后返回携带的用量

内部实现要点(async_chat_response.cj):

  • 流式消费时逐 chunk 追加content,并通过AtomicBool标记finished保证线程安全;
  • 若某个 chunk 的role与首个 chunk 不一致,抛出ModelException("Inconsistent message role");
  • 遇到done=true的分片时,将聚合文本构造成Message(role, content),记录usage并结束。

5.3 流式测试用例

src/tests/model_test.cj 展示了异步调用的标准用法:

let model = ModelManager.createChatModel(OPENAI_CHAT_MODEL) let msgList = MessageList([Message.user("hello")]) let asyncResp = model.asyncCreate(ChatRequest(msgList)) let content: Array<String> = asyncResp.iter() |> collectArray @Assert(content.size > 0) @Assert(asyncResp.message.content.size > 0)

先用iter()消费全部流式分片,再访问message属性确认聚合完整——这正是「先流式、后聚合」的推荐用法。

6. EmbeddingModel:文本向量化

向量模型用于 RAG、语义检索等场景。EmbeddingModel定义于 src/core/model/embedding_model.cj,仅一个方法:

public interface EmbeddingModel <: Model { func create(request: EmbeddingRequest): EmbeddingResponse }

6.1 EmbeddingRequest

定义于 src/core/model/embedding_request.cj:

public struct EmbeddingRequest { public let prompt: String public let dimensions: Option<Int64> public init(prompt: String, dimensions!: Option<Int> = None) }
  • prompt:待向量化的输入文本(必填);
  • dimensions:输出向量维度,None时使用服务端默认维度。注意构造器参数类型为Option<Int>,存储字段为Option<Int64>,赋值时会发生隐式类型适配。

6.2 EmbeddingResponse

定义于 src/core/model/embedding_response.cj:

public struct EmbeddingResponse <: ToString { public let data: Array<Float64> public init(data: Array<Float64>) override public func toString(): String // 输出 "Embedding: [...数值...]" }

data即最终向量。以 OpenAI 实现为例(src/model/openai/embedding.cj),请求体会带input、model、encoding_format: "float",并在dimensions为Some时附加dimensions字段,响应解析后取data[0].embedding构造EmbeddingResponse。

7. ImageModel:文生图能力

ImageModel定义于 src/core/model/image_model.cj,用于文生图场景(当前已接入 OpenAI、SiliconFlow、智谱 AI,见 model_manager.cj)。

7.1 ImageRequest 与 ImageResponse

image_request.cj:

public struct ImageRequest { public ImageRequest( protected let prompt: String, protected let size!: String = "1024x1024") { } }
  • prompt:图像描述文本(protected字段,外部读取需通过扩展接口);
  • size:输出尺寸,默认"1024x1024"。

image_response.cj:

public struct ImageResponse { public ImageResponse( public let url!: String = "", public let revisedPrompt!: String = "") { } }
  • url:生成图像的访问地址(本地模型可能为空串);
  • revisedPrompt:服务端改写后的提示词,可反映模型对输入的润色结果。

8. ModelException:统一的模型异常

所有模型层的错误统一抛ModelException,定义于 src/core/model/model_exception.cj:

public class ModelException <: Exception { public ModelException(public let error: String) { super(error) } }

异常消息场景包括(源码可查):HTTP 请求失败、响应体为空、响应角色不一致、输出被截断(finish_reason == "length")、模型标识非法等(见 src/model/openai/chat.cj、src/model/model_manager.cj)。业务代码可通过catch(ex: ModelException)集中处理模型侧故障。

9. 模型实例化:ModelManager 与多服务商配置

虽然core.model只定义抽象,但实际使用时离不开 src/model/model_manager.cj 提供的ModelManager与ModelConfig。

9.1 ModelConfig 与环境变量约定

ModelConfig(model_manager.cj)是模型构造参数的载体:

public init(provider!: String, kind!: String, name!: String, apiKey!: String = "", baseURL!: String = "", contextLength!: ?Int64 = None)
  • kind取值"chat"/"embedding"/"image";
  • apiKey为空时自动读取<PROVIDER>_API_KEY环境变量;
  • baseURL为空时自动读取<PROVIDER>_BASE_URL环境变量,未设置则使用内置默认值。

内置服务商默认配置表(源码DEFAULT_PROVIDER_MAP,model_manager.cj):

provider默认 Base URLAPI Key 环境变量
openaihttps://api.openai.com/v1OPENAI_API_KEY
anthropichttps://api.anthropic.com/v1ANTHROPIC_API_KEY
dashscopehttps://dashscope.aliyuncs.com/compatible-mode/v1DASHSCOPE_API_KEY
arkhttps://ark.cn-beijing.volces.com/api/v3ARK_API_KEY
deepseekhttps://api.deepseek.comDEEPSEEK_API_KEY
siliconflowhttps://api.siliconflow.cn/v1SILICONFLOW_API_KEY
zhipuaihttps://open.bigmodel.cn/api/paas/v4ZHIPUAI_API_KEY
maas必须由MAAS_BASE_URL提供MAAS_API_KEY
googlehttps://generativelanguage.googleapis.com/v1beta/openaiGOOGLE_API_KEY
moonshothttps://api.moonshot.cn/v1MOONSHOT_API_KEY
openrouterhttps://openrouter.ai/api/v1OPENROUTER_API_KEY
ollamahttp://localhost:11434无需
llamacpphttp://localhost:8080无需

各服务商的 Chat / Embedding / Image 能力矩阵与示例模型名(如deepseek:deepseek-chat、openai:gpt-4o、dashscope:qwen-plus、ollama:phi-3)详见 docs/tutorial.md 的「模型配置」章节。

9.2 ModelManager 工厂方法

ModelManager提供三组静态工厂方法,并支持注册自定义模型:

// 按 "provider:name" 字符串创建 static func createChatModel(modelName: String): ChatModel static func createEmbeddingModel(modelName: String): EmbeddingModel static func createImageModel(modelName: String): ImageModel // 按 ModelConfig 创建 static func createChatModel(modelConfig: ModelConfig): ChatModel static func createEmbeddingModel(modelConfig: ModelConfig): EmbeddingModel static func createImageModel(modelConfig: ModelConfig): ImageModel // 注册自定义实现(工厂函数) static func registerChatModel(modelName: String, buildFn: () -> ChatModel): Unit static func registerEmbeddingModel(modelName: String, buildFn: () -> EmbeddingModel): Unit static func registerImageModel(modelName: String, buildFn: () -> ImageModel): Unit

实现细节:

  • 字符串形式先查注册表(*_BUILD_FN_MAP),命中则直接调用工厂函数;
  • 未命中则通过parseModel按:拆分 provider 与 name 并校验服务商合法性(model_manager.cj),llamacpp特殊处理为无需模型名;
  • 大多数 OpenAI 兼容服务商(dashscope、ark、deepseek、siliconflow、zhipuai、maas、google、moonshot、openrouter)统一复用OpenAIChatModel,大幅降低接入成本(见 src/model/openai/chat.cj)。

9.3 实际调用示例

以 src/examples/quick_start/main.cj 为参照,最简接入方式是通过 DSL 宏:

import magic.dsl.* import magic.prelude.* import magic.config.Config @agent[model: "deepseek:deepseek-chat"] class BlackCatAssistant { @prompt("你是黑猫警长的助手") } main() { Config.env["DEEPSEEK_API_KEY"] = "<your api key>" let agent = BlackCatAssistant() let result = agent.chat("一只耳来啦") println(result) }

底层@agent宏会将model属性解析为ModelManager.createChatModel("deepseek:deepseek-chat")并挂载到agent.model(mut prop model: Option<ChatModel>)。也可以在代码中直接构造模型后调用核心 API:

let model = ModelManager.createChatModel("openai:gpt-4o") let resp = model.create(ChatRequest("你好")) println(resp.message.content) let embed = ModelManager.createEmbeddingModel("openai:text-embedding-ada-002") let vecResp = embed.create(EmbeddingRequest("hello")) println(vecResp.data.size)

10. 扩展自定义模型:从接口到注册

core.model的开放设计允许开发者接入任意模型服务商,标准路径是:

  1. 实现接口:新建类实现ChatModel/EmbeddingModel/ImageModel(继承Model后须实现provider、name、contextLength、maxTokens及create/asyncCreate),协议转换逻辑可参考OpenAIChatModel的请求组装与流式解析实现;
  2. 注册工厂:调用ModelManager.registerChatModel("myProvider:myModel", { => MyChatModel(...) }),此后即可用"myProvider:myModel"字符串在@agent[model: ...]与ModelManager.createChatModel中统一使用;
  3. 直接赋值:也可不经注册,直接构造实例后设置agent.model = MyChatModel(...)(参考 docs/tutorial.md 自定义模型章节)。

11. 与上层组件的协作关系

  • Agent:Agent接口的mut prop model: Option<ChatModel>是每个 Agent 执行对话的入口;
  • 执行器:naive/react/tool-loop执行器均通过ChatModel.create获取回复;Config.enableModelToolCall控制是否启用模型原生 Function Calling(见 src/config/config.cj);
  • RAG:VectorBuilder接收EmbeddingModel(VectorBuilder(model: model),见 docs/tutorial.md RAG 章节),配合向量数据库完成语义检索;
  • 对话记忆:Conversation等组件消费ChatRequest/ChatResponse完成多轮上下文传递。

总结

magic.core.model以「provider + name」的统一模型标识为起点,通过Model及其三个子接口定义了同步/异步对话、向量化、文生图三类能力的数据契约,配合ChatRequest、ChatResponse、ChatUsage、AsyncChatResponse等请求响应类型,以及ModelManager的服务商注册与解析机制,让上层 Agent 代码无需关心各家 API 差异。开发者既可开箱即用地接入 OpenAI 兼容服务、Ollama、Anthropic 等十余家服务商,也可实现并注册自有模型实现,是理解 CangjieMagic 全部上层能力(Agent、RAG、执行器)的基础模块。建议读者进一步阅读 src/core/model 源码与 src/tests/model_test.cj 中的完整测试用例,掌握每种接口的调用细节。

  • 人工智能
  • 大模型
  • AI Agent
  • RAG
  • 工具调用
  • MCP 服务
  • MCP Clients
  • 多智能体

【免费下载链接】CangjieMagic

基于仓颉编程语言构建的 LLM Agent DSL,其主要特点包括:声明式 DSL、支持 MCP 协议,支持任务智能规划等。

项目地址:https://gitcode.com/Cangjie-TPC/CangjieMagic
点击查看免费下载

相关推荐

上一篇:LeetCode 1964 障碍物赛道逐位最长有效长度:非严格 LIS 的三种 DP 解法与二分边界详解
下一篇:astron-rpa AI Service 文档摘要 Prompt 模板解析:从模板设计到 /v1/chat/prompt 调用实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询