feat: auto committed
This commit is contained in:
194
services/ai/tests/test_services.py
Normal file
194
services/ai/tests/test_services.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""服务层测试(ChatService / QuestionService / ExpressionService)."""
|
||||
|
||||
import json
|
||||
|
||||
from src.ai.models.question import GenerateQuestionRequest
|
||||
from src.ai.providers import ProviderFailoverChain
|
||||
from src.ai.providers.circuit_breaker import CircuitBreaker
|
||||
from src.ai.services.chat_service import ChatService
|
||||
from src.ai.services.evaluation import QualityGate, RuleValidator
|
||||
from src.ai.services.expression_service import ExpressionService
|
||||
from src.ai.services.question_service import QuestionService
|
||||
|
||||
from .conftest import MockProvider
|
||||
|
||||
|
||||
def _make_chain(provider: MockProvider) -> ProviderFailoverChain:
|
||||
return ProviderFailoverChain([provider], CircuitBreaker())
|
||||
|
||||
|
||||
class TestChatService:
|
||||
"""ChatService 测试."""
|
||||
|
||||
async def test_chat_success(self) -> None:
|
||||
"""聊天成功."""
|
||||
chain = _make_chain(MockProvider(response_content="你好"))
|
||||
svc = ChatService(failover_chain=chain, default_model="gpt-4o")
|
||||
data = await svc.chat(messages=[{"role": "user", "content": "hi"}])
|
||||
assert data.content == "你好"
|
||||
assert data.degraded is False
|
||||
assert data.usage.total_tokens == 30
|
||||
|
||||
async def test_chat_degraded(self) -> None:
|
||||
"""LLM 不可用时降级."""
|
||||
chain = _make_chain(MockProvider(fail=True))
|
||||
svc = ChatService(failover_chain=chain)
|
||||
data = await svc.chat(messages=[{"role": "user", "content": "hi"}])
|
||||
assert data.degraded is True
|
||||
assert "degraded" in data.content
|
||||
|
||||
async def test_stream_chat(self) -> None:
|
||||
"""流式聊天."""
|
||||
chain = _make_chain(MockProvider(stream_chunks=["hello", " world"]))
|
||||
svc = ChatService(failover_chain=chain)
|
||||
chunks = []
|
||||
async for chunk in svc.stream_chat(messages=[{"role": "user", "content": "hi"}]):
|
||||
chunks.append(chunk)
|
||||
assert len(chunks) == 2
|
||||
assert chunks[-1].done is True
|
||||
|
||||
async def test_stream_chat_degraded(self) -> None:
|
||||
"""流式降级."""
|
||||
chain = _make_chain(MockProvider(fail=True))
|
||||
svc = ChatService(failover_chain=chain)
|
||||
chunks = []
|
||||
async for chunk in svc.stream_chat(messages=[{"role": "user", "content": "hi"}]):
|
||||
chunks.append(chunk)
|
||||
assert chunks[-1].done is True
|
||||
assert "degraded" in chunks[-1].content
|
||||
|
||||
def test_build_system_prompt_no_template(self) -> None:
|
||||
"""无模板服务时返回默认 prompt."""
|
||||
chain = _make_chain(MockProvider())
|
||||
svc = ChatService(failover_chain=chain)
|
||||
prompt = svc._build_system_prompt({})
|
||||
assert "educational assistant" in prompt.lower()
|
||||
|
||||
|
||||
class TestQuestionService:
|
||||
"""QuestionService 测试."""
|
||||
|
||||
async def test_generate_success(self) -> None:
|
||||
"""生成题目成功."""
|
||||
output = json.dumps({
|
||||
"question": "1+1等于几?",
|
||||
"answer": "2",
|
||||
"explanation": "基础加法",
|
||||
"difficulty": "easy",
|
||||
"question_type": "short_answer",
|
||||
})
|
||||
chain = _make_chain(MockProvider(response_content=output))
|
||||
gate = QualityGate(rule_validator=RuleValidator())
|
||||
svc = QuestionService(
|
||||
failover_chain=chain,
|
||||
quality_gate=gate,
|
||||
default_model="gpt-4o",
|
||||
)
|
||||
request = GenerateQuestionRequest(
|
||||
prompt="生成加法题",
|
||||
subject="数学",
|
||||
difficulty="easy",
|
||||
)
|
||||
data = await svc.generate(request)
|
||||
assert data.question == "1+1等于几?"
|
||||
assert data.answer == "2"
|
||||
assert data.degraded is False
|
||||
|
||||
async def test_generate_degraded_llm_fail(self) -> None:
|
||||
"""LLM 失败降级."""
|
||||
chain = _make_chain(MockProvider(fail=True))
|
||||
svc = QuestionService(failover_chain=chain)
|
||||
request = GenerateQuestionRequest(prompt="生成题目", subject="数学")
|
||||
data = await svc.generate(request)
|
||||
assert data.degraded is True
|
||||
assert "degraded" in data.explanation
|
||||
|
||||
async def test_generate_invalid_json(self) -> None:
|
||||
"""LLM 输出非 JSON 降级."""
|
||||
chain = _make_chain(MockProvider(response_content="这不是JSON"))
|
||||
svc = QuestionService(failover_chain=chain)
|
||||
request = GenerateQuestionRequest(prompt="生成题目", subject="数学")
|
||||
data = await svc.generate(request)
|
||||
# 非JSON → 规则校验失败 → degraded
|
||||
assert data.degraded is True
|
||||
|
||||
async def test_stream_generate(self) -> None:
|
||||
"""流式生成题目."""
|
||||
output = json.dumps({"question": "题", "answer": "答"})
|
||||
chain = _make_chain(MockProvider(stream_chunks=[output]))
|
||||
svc = QuestionService(failover_chain=chain)
|
||||
request = GenerateQuestionRequest(prompt="生成题目", subject="数学")
|
||||
chunks = []
|
||||
async for chunk in svc.stream_generate(request):
|
||||
chunks.append(chunk)
|
||||
assert chunks[-1].done is True
|
||||
assert chunks[-1].complete_question is not None
|
||||
|
||||
async def test_stream_generate_degraded(self) -> None:
|
||||
"""流式生成降级."""
|
||||
chain = _make_chain(MockProvider(fail=True))
|
||||
svc = QuestionService(failover_chain=chain)
|
||||
request = GenerateQuestionRequest(prompt="生成题目", subject="数学")
|
||||
chunks = []
|
||||
async for chunk in svc.stream_generate(request):
|
||||
chunks.append(chunk)
|
||||
assert chunks[-1].done is True
|
||||
assert chunks[-1].complete_question is not None
|
||||
assert chunks[-1].complete_question.degraded is True
|
||||
|
||||
def test_fallback_prompt(self) -> None:
|
||||
"""降级 prompt."""
|
||||
chain = _make_chain(MockProvider())
|
||||
svc = QuestionService(failover_chain=chain)
|
||||
request = GenerateQuestionRequest(prompt="测试", subject="语文")
|
||||
prompt = svc._fallback_prompt(request)
|
||||
assert "语文" in prompt
|
||||
|
||||
|
||||
class TestExpressionService:
|
||||
"""ExpressionService 测试."""
|
||||
|
||||
async def test_optimize_success(self) -> None:
|
||||
"""优化成功."""
|
||||
output = json.dumps({
|
||||
"optimized": "优化后的文字",
|
||||
"suggestions": ["建议1"],
|
||||
})
|
||||
chain = _make_chain(MockProvider(response_content=output))
|
||||
svc = ExpressionService(failover_chain=chain)
|
||||
data = await svc.optimize(text="原始文字")
|
||||
assert data.optimized == "优化后的文字"
|
||||
assert data.suggestions == ["建议1"]
|
||||
assert data.degraded is False
|
||||
|
||||
async def test_optimize_degraded(self) -> None:
|
||||
"""LLM 失败降级."""
|
||||
chain = _make_chain(MockProvider(fail=True))
|
||||
svc = ExpressionService(failover_chain=chain)
|
||||
data = await svc.optimize(text="原始文字")
|
||||
assert data.degraded is True
|
||||
assert data.optimized == "原始文字"
|
||||
|
||||
async def test_optimize_invalid_json(self) -> None:
|
||||
"""非 JSON 输出降级."""
|
||||
chain = _make_chain(MockProvider(response_content="纯文本"))
|
||||
svc = ExpressionService(failover_chain=chain)
|
||||
data = await svc.optimize(text="原始文字")
|
||||
assert data.degraded is True
|
||||
assert "json" in data.degraded_reason.lower()
|
||||
|
||||
async def test_optimize_markdown_json(self) -> None:
|
||||
"""markdown 包裹 JSON 能解析."""
|
||||
output = '```json\n{"optimized": "ok", "suggestions": []}\n```'
|
||||
chain = _make_chain(MockProvider(response_content=output))
|
||||
svc = ExpressionService(failover_chain=chain)
|
||||
data = await svc.optimize(text="原始文字")
|
||||
assert data.optimized == "ok"
|
||||
|
||||
def test_fallback_prompt(self) -> None:
|
||||
"""降级 prompt."""
|
||||
chain = _make_chain(MockProvider())
|
||||
svc = ExpressionService(failover_chain=chain)
|
||||
prompt = svc._fallback_prompt("文字", "上下文")
|
||||
assert "文字" in prompt
|
||||
assert "上下文" in prompt
|
||||
Reference in New Issue
Block a user