Koog(7)——模型调用的输入域输出:多模态、参数、流式与结构化 - SharpCJ

Koog(7)——模型调用的输入域输出:多模态、参数、流式与结构化 - SharpCJ

💡 原文中文,约26400字,阅读约需63分钟。
📝

内容提要

本文以 Koog 1.3.0 的 Kotlin API 为例,讲解模型调用的输入与输出。输入方面,user 消息可混排文本与图片、音频、视频、文件等附件,但多模态支持取决于 provider 和模型能力,例如 DeepSeek V4 Flash 不支持 document 附件。参数方面,LLMParams 可在 prompt、子图、会话三层生效,包含 temperature、maxTokens、toolChoice、schema 等字段,并有构造期校验。输出方面,流式调用 requestLLMStreaming 返回 Flow<StreamFrame>,可用 collectText 拼接文本,也可边流式边解析 Markdown 为对象;结构化输出用 data class 加注解生成 schema,requestLLMStructured 返回 Result,解析失败可由 StructureFixingParser 自动修复。文中还提及 Anthropic 与 Bedrock 的提示词缓存配置差异。

🔎

延伸解读

多模态输入:构造成功不等于模型能读

Koog 允许在 user 消息中混排文本与图片、音频、视频、文件等附件,但附件类型是否可用取决于 provider 和具体模型的能力声明。文章实测 DeepSeek V4 Flash 不支持 document 附件,带 file() 的调用会在请求前抛出 IllegalArgumentException。因此接入前必须确认模型声明了对应附件类型,不能把消息构造成功当作多模态识别已完成。

LLMParams 的三层作用域与提交语义

LLMParams 可在创建 prompt、创建子图、会话中动态修改三个层级生效。会话内 changeLLMParams 修改的是 Prompt 对象的一部分,遵循“lambda 正常结束才提交回上下文”的规则,异常传播会导致修改丢失。构造期会校验 temperature、numberOfChoices、speculation、user 和 toolChoice 等字段,但基础 LLMParams 不校验 maxTokens 范围,provider 仍可能做进一步检查。

流式输出:delta 用于显示,complete 用于取数

requestLLMStreaming 返回 Flow<StreamFrame>,其中 TextDelta 等增量适合实时显示,TextComplete 等完整帧用于取最终数据,End 帧带 finishReason 判断结束原因。流式响应不会自动写回 prompt 历史,多轮对话需显式追加最终响应。对冷流重复收集可能重新发起网络请求,不能当作可重复读取的缓存。

结构化输出与修复的边界

requestLLMStructured 从 data class 自动生成 schema 并返回 Result,解析失败时 StructureFixingParser 可对 SerializationException 进行有限次修复。但返回 Result 不代表调用过程不抛异常:schema 准备和首次请求失败仍可能直接抛出,业务方需同时处理 Result 和异常。修复模型补出的值不是恢复已有正确答案,解析出的对象也不代表事实已核实。

❓

Q&A

Koog 中如何在 user 消息里混合发送文本和图片等附件?

在 user { } 块中使用 + 运算符按顺序混排文本和附件函数(如 image()、audio()、video()、file()),附件可传 URL 或本地路径,Koog 按扩展名推断格式和 MIME 类型。例如:user { +"描述这些图片:" image("https://example.com/test.png") +"关注主体。" }。注意多模态支持取决于 provider 和模型能力,校验发生在请求前。

LLMParams 可以在哪些地方设置?修改后如何生效?

LLMParams 可在三个层级生效:创建 prompt 时、创建子图时(subgraphWithTask 的 llmParams 参数)、以及会话中通过 changeLLMParams 动态修改。changeLLMParams 的修改遵循写会话的提交语义:lambda 正常结束才提交回上下文,异常会导致修改丢失。

Koog 的流式调用 requestLLMStreaming 返回什么?如何获取完整文本?

requestLLMStreaming 返回 Flow<StreamFrame>,StreamFrame 分为 TextDelta、ReasoningDelta、ToolCallDelta 等增量帧和对应的 Complete 帧以及 End 帧。可用 collectText() 拼接完整文本,或用 filterTextOnly() 转换为仅文本块流。注意流式响应不会自动写回 prompt 历史,需要显式追加最终响应。

Koog 的结构化输出如何定义和请求?解析失败怎么办?

用 @Serializable data class 描述结构,字段用 @property:LLMDescription 添加说明,然后调用 requestLLMStructured 或 executeStructured 返回 Result<StructuredResponse<T>>。解析失败时可用 StructureFixingParser 自动修复:遇到 SerializationException 会将原内容和异常交给修复模型,最多重试 retries 次。

Koog 中 toolChoice = null、ToolChoice.None 和 requestLLMWithoutTools() 有什么区别?

toolChoice = null 表示不显式指定工具选择策略,并不等于禁用工具;ToolChoice.None 是发送“不调用工具”的选择约束;requestLLMWithoutTools() 则让本次请求不提供工具,并清除相关选择约束。临时不需要工具时用便捷方法更直接。

Koog 中 Anthropic 和 Bedrock 的提示词缓存配置有什么不同?

Anthropic 使用 AnthropicCacheControl,可通过 AnthropicParams.cacheControl 设置请求级自动缓存,或在消息部件上设置块级缓存标记(如 OneHour TTL)。Bedrock Converse API 使用 BedrockCacheControl,将消息部件和工具上的标记转换为 cache point。两者标记类型与客户端不匹配会抛异常,不能混用。

🏷️

标签

➡️

继续阅读