182 lines
5.8 KiB
Python
182 lines
5.8 KiB
Python
"""合成流验证模型协议;不发送网络请求,不作为真实模型证据。"""
|
|
|
|
import asyncio
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from muse.任务运行.接口 import 模型协议错误
|
|
from muse.基础设施.模型.HTTP传输 import 解码SSE
|
|
from muse.基础设施.模型.Responses import 解析响应
|
|
|
|
|
|
def 完成事件(*, status="completed", usage=True):
|
|
return {
|
|
"type": "response." + status,
|
|
"response": {
|
|
"id": "response-1",
|
|
"status": status,
|
|
"model": "test-model",
|
|
"output": [
|
|
{
|
|
"type": "message",
|
|
"content": [{"type": "output_text", "text": "中文\u0085保持。"}],
|
|
}
|
|
],
|
|
"usage": {"input_tokens": 10, "output_tokens": 4} if usage else None,
|
|
},
|
|
}
|
|
|
|
|
|
def test_Responses明确终态与UTF8字节边界__a61001() -> None:
|
|
帧 = ("data: " + json.dumps(完成事件(), ensure_ascii=False) + "\r\n\r\n").encode()
|
|
|
|
async def 字节流():
|
|
for b in 帧:
|
|
yield bytes([b])
|
|
|
|
async def 收集():
|
|
return [项 async for 项 in 解码SSE(字节流())]
|
|
|
|
结果 = 解析响应(asyncio.run(收集()))
|
|
assert 结果.状态 == "completed" and 结果.文本 == "中文\u0085保持。"
|
|
assert 结果.实际模型 == "test-model"
|
|
assert 结果.用量.输入token == 10 and 结果.用量.输出token == 4
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"事件,期望",
|
|
[
|
|
([{"type": "response.output_text.delta", "delta": "只到一半"}], "incomplete"),
|
|
([完成事件(status="incomplete")], "incomplete"),
|
|
([完成事件(status="failed")], "failed"),
|
|
([{"type": "error", "code": "server_error"}], "failed"),
|
|
],
|
|
ids=["no-terminal", "incomplete", "failed", "error"],
|
|
)
|
|
def test_断流与失败不计模型完成__a61002(事件, 期望) -> None:
|
|
assert 解析响应(事件).状态 == 期望
|
|
|
|
|
|
def test_完成但用量未知独立保留__a61003() -> None:
|
|
结果 = 解析响应([完成事件(usage=False)])
|
|
assert 结果.状态 == "completed" and 结果.用量 is None
|
|
|
|
|
|
def test_错误SSE帧和UTF8拒绝__a61004() -> None:
|
|
async def 字节流():
|
|
yield b'data: {"text":"\xff"}\n\n'
|
|
|
|
async def 收集():
|
|
return [项 async for 项 in 解码SSE(字节流())]
|
|
|
|
with pytest.raises(模型协议错误):
|
|
asyncio.run(收集())
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"结束,原因,状态",
|
|
[
|
|
(True, "end_turn", "completed"),
|
|
(False, "end_turn", "incomplete"),
|
|
(True, "max_tokens", "incomplete"),
|
|
],
|
|
ids=["complete", "missing-stop", "token-limit"],
|
|
)
|
|
def test_Anthropic结束消息与停止原因共同判定__a61005(结束, 原因, 状态) -> None:
|
|
from muse.基础设施.模型.Messages import 解析响应 as 解析
|
|
|
|
事件 = [
|
|
{
|
|
"type": "message_start",
|
|
"message": {"id": "a1", "model": "opus", "usage": {"input_tokens": 3}},
|
|
},
|
|
{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}},
|
|
{
|
|
"type": "content_block_delta",
|
|
"index": 0,
|
|
"delta": {"type": "text_delta", "text": "甲乙。"},
|
|
},
|
|
{"type": "message_delta", "delta": {"stop_reason": 原因}, "usage": {"output_tokens": 2}},
|
|
]
|
|
if 结束:
|
|
事件.append({"type": "message_stop"})
|
|
结果 = 解析(事件)
|
|
assert 结果.状态 == 状态 and 结果.文本 == "甲乙。"
|
|
assert 结果.用量.输入token == 3
|
|
|
|
|
|
def test_ChatCompletions完成与工具回合分开__a61006() -> None:
|
|
from muse.基础设施.模型.ChatCompletions import 解析响应 as 解析
|
|
|
|
结果 = 解析(
|
|
[
|
|
{
|
|
"id": "c1",
|
|
"model": "test",
|
|
"choices": [{"index": 0, "delta": {"content": "完成。"}, "finish_reason": "stop"}],
|
|
}
|
|
]
|
|
)
|
|
assert 结果.状态 == "completed" and 结果.用量 is None
|
|
工具 = 解析(
|
|
[
|
|
{
|
|
"id": "c2",
|
|
"model": "test",
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"delta": {
|
|
"tool_calls": [
|
|
{
|
|
"index": 0,
|
|
"id": "tool1",
|
|
"function": {"name": "read", "arguments": "{}"},
|
|
}
|
|
]
|
|
},
|
|
"finish_reason": "tool_calls",
|
|
}
|
|
],
|
|
}
|
|
]
|
|
)
|
|
assert 工具.状态 == "tool_calls" and 工具.工具调用[0].名称 == "read"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"模型,文本,合法",
|
|
[
|
|
("test", '{"正文":"中文"}', True),
|
|
("other", '{"正文":"中文"}', False),
|
|
("test", '{"正文":1}', False),
|
|
("test", '{"正文":"中文","state":"confirmed"}', False),
|
|
],
|
|
ids=["valid", "model-drift", "wrong-type", "extra-authority"],
|
|
)
|
|
def test_实际模型与输出合同同时满足才产候选__a61007(模型, 文本, 合法) -> None:
|
|
from muse.任务运行.接口 import 校验模型输出, 模型结果, 模型请求
|
|
|
|
请求 = 模型请求(
|
|
"call",
|
|
"synthetic",
|
|
"test",
|
|
"合成系统说明",
|
|
"合成输入",
|
|
{
|
|
"type": "object",
|
|
"properties": {"正文": {"type": "string"}},
|
|
"required": ["正文"],
|
|
"additionalProperties": False,
|
|
},
|
|
100,
|
|
30,
|
|
)
|
|
结果 = 模型结果("completed", 文本, 模型, None)
|
|
if 合法:
|
|
assert 校验模型输出(请求, 结果) == {"正文": "中文"}
|
|
else:
|
|
with pytest.raises(模型协议错误):
|
|
校验模型输出(请求, 结果)
|