中立多模态内容部件(content parts)—— provider 无关的图片 / 文件 / 音频表示。
消息的 :content 有两种合法形态:
{:role :user
:content [(content/text-part "这张图里有什么?")
(content/image-part "https://example.com/cat.png")]}
部件形状(对齐 Vercel AI SDK 的 LanguageModelPrompt 部件模型——
图片 / 音频 / PDF 不各立一类,统一是 :file + :media-type,
wire 层按 media type 的顶层类别分派;这样新格式不需要新部件类型):
{:type :text :text "..."}
{:type :file :media-type "image/png" :data "<base64>"} ; 内联数据
{:type :file :media-type "image/png" :url "https://..."} ; 远端 URL
{:type :file :media-type "application/pdf" :data "<base64>" :filename "a.pdf"}
约束与边界:
:validation-error,
不可重试),不静默丢内容。:content 向量里 :type 为字符串的元素(如
Anthropic 的 {:type "text"} / {:type "document"})被视为厂商原生块,
wire 层原样透传——既有那条逃生通道不受影响,两者可混用。(require '[im.ttalk.agent.model.content :as content] '[im.ttalk.agent.model.message :as msg])
(msg/user [(content/text-part "描述这张图") (content/image-part (io/file "cat.png"))])
中立多模态内容部件(content parts)—— provider 无关的图片 / 文件 / 音频表示。
消息的 `:content` 有两种合法形态:
1. **字符串**(既有形态,纯文本,最常用)
2. **部件向量**(多模态):
```clojure
{:role :user
:content [(content/text-part "这张图里有什么?")
(content/image-part "https://example.com/cat.png")]}
```
部件形状(对齐 Vercel AI SDK 的 `LanguageModelPrompt` 部件模型——
图片 / 音频 / PDF **不各立一类**,统一是 `:file` + `:media-type`,
wire 层按 media type 的顶层类别分派;这样新格式不需要新部件类型):
```clojure
{:type :text :text "..."}
{:type :file :media-type "image/png" :data "<base64>"} ; 内联数据
{:type :file :media-type "image/png" :url "https://..."} ; 远端 URL
{:type :file :media-type "application/pdf" :data "<base64>" :filename "a.pdf"}
```
约束与边界:
- **中立层只搬运,不解释**:能不能发(Anthropic 不收音频、OpenAI 的 PDF 只收内联)
由各 provider 的 wire 转换器判定,不支持时抛规范错误(`:validation-error`,
不可重试),**不静默丢内容**。
- **与厂商原生块共存**:`:content` 向量里 `:type` 为**字符串**的元素(如
Anthropic 的 `{:type "text"}` / `{:type "document"}`)被视为厂商原生块,
wire 层**原样透传**——既有那条逃生通道不受影响,两者可混用。
- 部件里的二进制一律以 **base64 字符串**落地:对话历史要经 EDN / JSON 往返
(SQLite store),字节数组过不去。
(require '[im.ttalk.agent.model.content :as content]
'[im.ttalk.agent.model.message :as msg])
(msg/user [(content/text-part "描述这张图")
(content/image-part (io/file "cat.png"))])(audio-part source)(audio-part source opts)音频部件。内联数据须给 :media-type(如 "audio/wav" / "audio/mpeg")。
音频部件。内联数据须给 :media-type(如 "audio/wav" / "audio/mpeg")。
(data-uri {:keys [media-type data]})内联部件 → "data:<media-type>;base64,<data>"(无 :data 返回 nil)
内联部件 → "data:<media-type>;base64,<data>"(无 :data 返回 nil)
(file-part source)(file-part source {:keys [media-type filename]})文件部件(图片 / 音频 / PDF 等统一走这里)。
参数:
返回:{:type :file :media-type ... (:data | :url) ... (:filename)}
文件部件(图片 / 音频 / PDF 等统一走这里)。
参数:
- source: URL 字符串 / data URI / base64 字符串 / byte[] / java.io.File /
java.net.URL / java.net.URI
- opts: {:media-type "image/png" :filename "a.png"}
内联数据(非 URL、非 File)**必须**给 :media-type——猜不出来的东西
不猜,否则错的 media type 会让服务端报一个与真实原因无关的错。
返回:{:type :file :media-type ... (:data | :url) ... (:filename)}(image-part source)(image-part source opts)图片部件。(image-part "https://.../a.png") / (image-part (io/file "a.png")) /
(image-part base64 {:media-type "image/png"})。
URL 来源猜不出 media type 时缺省 "image/*"——OpenAI 的 image_url 用不到它, Anthropic 的 url source 也不带 media_type,留个顶层类别足够 wire 层分派。
图片部件。`(image-part "https://.../a.png")` / `(image-part (io/file "a.png"))` /
`(image-part base64 {:media-type "image/png"})`。
URL 来源猜不出 media type 时缺省 "image/*"——OpenAI 的 image_url 用不到它,
Anthropic 的 url source 也不带 media_type,留个顶层类别足够 wire 层分派。(part? x)是否为中立内容部件(:type 为 keyword)。
:type 为字符串的一律不是——那是厂商原生块,wire 层原样透传。
是否为**中立**内容部件(:type 为 keyword)。 `:type` 为字符串的一律不是——那是厂商原生块,wire 层原样透传。
(parts? content)content 是否为多模态部件向量(含至少一个中立部件)。 纯字符串 content 与纯厂商原生块向量都返回 false。
content 是否为多模态部件向量(含至少一个中立部件)。 纯字符串 content 与纯厂商原生块向量都返回 false。
(text-of content)取 content 的纯文本(字符串原样;部件向量取其中文本部件拼接; 厂商原生块认 :text 键)。多模态消息要做关键词匹配 / 日志时用它。
取 content 的纯文本(字符串原样;部件向量取其中文本部件拼接; 厂商原生块认 :text 键)。多模态消息要做关键词匹配 / 日志时用它。
(text-part text)文本部件 {:type :text :text "..."}
文本部件 {:type :text :text "..."}
(top-level-media-type part)media type 的顶层类别:"image/png" → "image"(无则 nil)
media type 的顶层类别:"image/png" → "image"(无则 nil)
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |