refactor: 将 kimi-k3 多模态内容构建改为纯 Jinja2 模板

- 删除 build_kimi_content() Python 函数
- 在 SQL data 模板中用纯 Jinja2 语法构建 content 数组
- 新增 file_to_b64() 和 file_mime() 辅助函数注册到 ServerEnv
- 保持向后兼容,功能不变
This commit is contained in:
yumoqing 2026-07-20 10:48:22 +08:00
parent ce17c48022
commit e0855b6415
2 changed files with 44 additions and 31 deletions

View File

@ -502,34 +502,23 @@ async def llm_query_price(llmid, config_data):
prices = await env.buffered_charging(llm.ppid, config_data) prices = await env.buffered_charging(llm.ppid, config_data)
return prices return prices
def build_kimi_content(prompt='', image_files=None, video_files=None): import base64 as _base64
"""构造 kimi 多模态 content 数组 JSON注册到 ServerEnv 供 uapi data 模板调用""" import mimetypes as _mimetypes
import base64, json, mimetypes, os import os as _os
fs = FileStorage()
parts = []
if prompt:
parts.append({"type": "text", "text": prompt})
for f in (image_files or []):
fp = fs.realPath(f)
if not os.path.isfile(fp):
continue
mime, _ = mimetypes.guess_type(fp)
if not mime:
mime = 'application/octet-stream'
with open(fp, 'rb') as fh:
b64 = base64.b64encode(fh.read()).decode('utf-8')
parts.append({"type": "image_url", "image_url": f'data:{mime};base64,{b64}'})
for f in (video_files or []):
fp = fs.realPath(f)
if not os.path.isfile(fp):
continue
mime, _ = mimetypes.guess_type(fp)
if not mime:
mime = 'application/octet-stream'
with open(fp, 'rb') as fh:
b64 = base64.b64encode(fh.read()).decode('utf-8')
parts.append({"type": "video_url", "video_url": f'data:{mime};base64,{b64}'})
return json.dumps(parts, ensure_ascii=False)
ServerEnv().build_kimi_content = build_kimi_content def _file_to_b64(filepath):
"""读取文件并返回 base64 字符串"""
with open(filepath, 'rb') as fh:
return _base64.b64encode(fh.read()).decode('utf-8')
def _file_mime(filepath):
"""猜测文件 MIME 类型"""
mime, _ = _mimetypes.guess_type(filepath)
return mime or 'application/octet-stream'
ServerEnv().base64 = _base64
ServerEnv().mimetypes = _mimetypes
ServerEnv().os = _os
ServerEnv().file_to_b64 = _file_to_b64
ServerEnv().file_mime = _file_mime

View File

@ -2,7 +2,7 @@
-- Kimi K3 API 接入 (月之暗面 / Moonshot) -- Kimi K3 API 接入 (月之暗面 / Moonshot)
-- Base URL: https://api.moonshot.cn/v1 -- Base URL: https://api.moonshot.cn/v1
-- 兼容 OpenAI 格式, 文档: https://platform.kimi.com/docs/api/chat -- 兼容 OpenAI 格式, 文档: https://platform.kimi.com/docs/api/chat
-- 多模态: image_files/video_files 通过 build_kimi_content() 转 data URI 数组 -- 多模态: image_files/video_files 在 data 模板中用纯 Jinja2 构造 content 数组
-- ============================================================ -- ============================================================
-- ============================================================ -- ============================================================
@ -41,7 +41,31 @@ VALUES (
{% if sys_prompt %} {% if sys_prompt %}
{"role": "system", "content": {{json.dumps(sys_prompt, ensure_ascii=False)}}}, {"role": "system", "content": {{json.dumps(sys_prompt, ensure_ascii=False)}}},
{% endif %} {% endif %}
{"role": "user", "content": {{build_kimi_content(prompt, image_files, video_files)}}} {% set _parts = [] %}
{% if prompt %}
{% set _ = _parts.append({"type": "text", "text": prompt}) %}
{% endif %}
{% if image_files %}
{% for _f in image_files %}
{% set _fp = FileStorage().realPath(_f) %}
{% if os.path.isfile(_fp) %}
{% set _mime = file_mime(_fp) %}
{% set _b64 = file_to_b64(_fp) %}
{% set _ = _parts.append({"type": "image_url", "image_url": "data:" + _mime + ";base64," + _b64}) %}
{% endif %}
{% endfor %}
{% endif %}
{% if video_files %}
{% for _f in video_files %}
{% set _fp = FileStorage().realPath(_f) %}
{% if os.path.isfile(_fp) %}
{% set _mime = file_mime(_fp) %}
{% set _b64 = file_to_b64(_fp) %}
{% set _ = _parts.append({"type": "video_url", "video_url": "data:" + _mime + ";base64," + _b64}) %}
{% endif %}
{% endfor %}
{% endif %}
{"role": "user", "content": {{json.dumps(_parts, ensure_ascii=False)}}}
], ],
{% endif %} {% endif %}
{% if stream %} {% if stream %}