mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-30 11:54:50 +08:00
152 lines
4.6 KiB
Python
152 lines
4.6 KiB
Python
"""
|
|
验证 OpenAI 与 Anthropic 兼容接口的多模态消息转换。
|
|
"""
|
|
|
|
from app.api.protocol import (
|
|
build_anthropic_messages,
|
|
build_completion_payload,
|
|
build_prompt,
|
|
build_responses_input,
|
|
build_session_id,
|
|
extract_text_and_images,
|
|
)
|
|
|
|
|
|
def test_extract_text_and_images() -> None:
|
|
"""
|
|
多模态消息按顺序提取文本并收集图片 URL。
|
|
"""
|
|
text, images = extract_text_and_images(
|
|
[
|
|
{"type": "text", "text": "你好"},
|
|
{"type": "image_url", "image_url": {"url": "https://example.com/a.png"}},
|
|
{"type": "text", "text": "世界"},
|
|
]
|
|
)
|
|
assert text == "你好\n世界"
|
|
assert images == ["https://example.com/a.png"]
|
|
|
|
|
|
def test_extract_text_and_images_with_input_image_and_base64_image() -> None:
|
|
"""
|
|
兼容输入图片与 Base64 图片两种载荷形式。
|
|
"""
|
|
text, images = extract_text_and_images(
|
|
[
|
|
{"type": "input_text", "text": "看图"},
|
|
{"type": "input_image", "image_url": "https://example.com/b.png"},
|
|
{
|
|
"type": "image",
|
|
"source": {
|
|
"type": "base64",
|
|
"media_type": "image/png",
|
|
"data": "YWJj",
|
|
},
|
|
},
|
|
]
|
|
)
|
|
assert text == "看图"
|
|
assert images == ["https://example.com/b.png", "data:image/png;base64,YWJj"]
|
|
|
|
|
|
def test_build_prompt_without_server_session_keeps_recent_history() -> None:
|
|
"""
|
|
没有服务端会话时在提示词中保留近期历史。
|
|
"""
|
|
prompt, images = build_prompt(
|
|
[
|
|
{"role": "system", "content": "回答简短"},
|
|
{"role": "user", "content": "第一句"},
|
|
{"role": "assistant", "content": "第一答"},
|
|
{"role": "user", "content": "第二句"},
|
|
],
|
|
use_server_session=False,
|
|
)
|
|
assert "系统要求:\n回答简短" in prompt
|
|
assert "对话上下文:\nuser: 第一句\nassistant: 第一答" in prompt
|
|
assert "当前用户消息:\n第二句" in prompt
|
|
assert images == []
|
|
|
|
|
|
def test_build_prompt_with_server_session_ignores_history_block() -> None:
|
|
"""
|
|
已有服务端会话时不重复附加历史消息块。
|
|
"""
|
|
prompt, _ = build_prompt(
|
|
[
|
|
{"role": "user", "content": "历史问题"},
|
|
{"role": "assistant", "content": "历史回答"},
|
|
{"role": "user", "content": "当前问题"},
|
|
],
|
|
use_server_session=True,
|
|
)
|
|
assert "对话上下文:" not in prompt
|
|
assert "当前用户消息:\n当前问题" in prompt
|
|
|
|
|
|
def test_build_prompt_accepts_image_only_user_message() -> None:
|
|
"""
|
|
只有图片的用户消息仍能构造有效提示词。
|
|
"""
|
|
prompt, images = build_prompt(
|
|
[
|
|
{
|
|
"role": "user",
|
|
"content": [{"type": "image_url", "image_url": {"url": "https://example.com/a.png"}}],
|
|
}
|
|
],
|
|
use_server_session=True,
|
|
)
|
|
assert "请结合图片内容回复" in prompt
|
|
assert images == ["https://example.com/a.png"]
|
|
|
|
|
|
def test_build_session_id_is_stable() -> None:
|
|
"""
|
|
相同用户和会话信息生成稳定会话标识。
|
|
"""
|
|
session_id = build_session_id("user-1", "openai:")
|
|
assert session_id.startswith("openai:")
|
|
assert session_id == build_session_id("user-1", "openai:")
|
|
assert session_id != build_session_id("user-2", "openai:")
|
|
|
|
|
|
def test_build_completion_payload() -> None:
|
|
"""
|
|
补全接口输出包含预期模型、消息和用量字段。
|
|
"""
|
|
payload = build_completion_payload("你好", "moviepilot-agent")
|
|
assert payload["model"] == "moviepilot-agent"
|
|
assert payload["choices"][0]["message"]["content"] == "你好"
|
|
assert payload["choices"][0]["finish_reason"] == "stop"
|
|
|
|
|
|
def test_build_responses_input() -> None:
|
|
"""
|
|
Responses 请求正确转换文本和图片输入。
|
|
"""
|
|
messages = build_responses_input(
|
|
[
|
|
{
|
|
"type": "message",
|
|
"role": "user",
|
|
"content": [{"type": "input_text", "text": "你好"}],
|
|
}
|
|
],
|
|
instructions="你要简短回答",
|
|
)
|
|
assert messages[0]["role"] == "system"
|
|
assert messages[1]["role"] == "user"
|
|
|
|
|
|
def test_build_anthropic_messages() -> None:
|
|
"""
|
|
Anthropic 请求正确转换系统指令及用户多模态消息。
|
|
"""
|
|
messages = build_anthropic_messages(
|
|
system=[{"type": "text", "text": "你是助手"}],
|
|
messages=[{"role": "user", "content": "你好"}],
|
|
)
|
|
assert messages[0]["role"] == "system"
|
|
assert messages[1]["role"] == "user"
|