chore: finalize backend feature scope
This commit is contained in:
+42
-32
@@ -103,7 +103,6 @@ def run_api_contract_tests() -> None:
|
||||
auth_me_operation = openapi_payload["paths"]["/api/v1/auth/me"]["get"]
|
||||
assert any("HTTPBearer" in item for item in auth_me_operation.get("security", []))
|
||||
assert "HTTPBearer" in openapi_payload["components"]["securitySchemes"]
|
||||
assert "delete" not in openapi_payload["paths"]["/api/v1/cases/{case_id}"]
|
||||
assert "/api/v1/training-config/recommended" in openapi_payload["paths"]
|
||||
assert "/api/v1/training-config/options" in openapi_payload["paths"]
|
||||
assert "/api/v1/sessions/{session_id}/hints/stream" in openapi_payload["paths"]
|
||||
@@ -116,16 +115,33 @@ def run_api_contract_tests() -> None:
|
||||
assert "/api/v1/teaching/evaluation" in openapi_payload["paths"]
|
||||
assert "/api/v1/knowledge-admin/documents/upload" in openapi_payload["paths"]
|
||||
assert "/api/v1/learning-assistant/chat" not in openapi_payload["paths"]
|
||||
assert "/api/v1/learning-assistant/chat/stream" in openapi_payload["paths"]
|
||||
assert "/api/v1/learning-assistant/sessions" in openapi_payload["paths"]
|
||||
assert "/api/v1/learning-assistant/sessions/{assistant_session_id}/chat/stream" in openapi_payload["paths"]
|
||||
assistant_session = client.post("/api/v1/learning-assistant/sessions", headers=headers, json={"title": "医学知识学习"})
|
||||
assert assistant_session.status_code == 200
|
||||
assistant_session_id = assistant_session.json()["data"]["assistant_session_id"]
|
||||
assert assistant_session_id.startswith("las_")
|
||||
assert assistant_session.json()["data"]["user_id"] == "api_user_001"
|
||||
assert assistant_session.json()["data"]["institution_id"] == 1
|
||||
|
||||
cross_user_assistant = client.post(
|
||||
f"/api/v1/learning-assistant/sessions/{assistant_session_id}/chat/stream",
|
||||
headers={"Authorization": "Bearer api_user_002_token", "X-Entry-Scene": "api_test"},
|
||||
json={"question": "这个会话是否属于我?", "top_k": 1},
|
||||
)
|
||||
assert cross_user_assistant.status_code == 404
|
||||
assert cross_user_assistant.json()["code"] == "LEARNING_ASSISTANT_SESSION_NOT_FOUND"
|
||||
|
||||
with client.stream(
|
||||
"POST",
|
||||
"/api/v1/learning-assistant/chat/stream",
|
||||
f"/api/v1/learning-assistant/sessions/{assistant_session_id}/chat/stream",
|
||||
headers=headers,
|
||||
json={"question": "支气管肺炎有哪些常见表现?", "top_k": 1},
|
||||
) as no_kb_stream:
|
||||
assert no_kb_stream.status_code == 200
|
||||
no_kb_stream_text = "".join(no_kb_stream.iter_text())
|
||||
assert "event: session_ready" in no_kb_stream_text
|
||||
assert assistant_session_id in no_kb_stream_text
|
||||
assert "event: retrieval_done" in no_kb_stream_text
|
||||
assert '"retrieval_hit": false' in no_kb_stream_text
|
||||
assert "event: answer_delta" in no_kb_stream_text
|
||||
@@ -162,7 +178,7 @@ def run_api_contract_tests() -> None:
|
||||
|
||||
with client.stream(
|
||||
"POST",
|
||||
"/api/v1/learning-assistant/chat/stream",
|
||||
f"/api/v1/learning-assistant/sessions/{assistant_session_id}/chat/stream",
|
||||
headers=headers,
|
||||
json={"question": "血氧下降说明什么?", "top_k": 1},
|
||||
) as rag_stream:
|
||||
@@ -174,9 +190,21 @@ def run_api_contract_tests() -> None:
|
||||
assert "event: answer_delta" in rag_stream_text
|
||||
assert "event: answer_done" in rag_stream_text
|
||||
|
||||
cases = client.get("/api/v1/cases", headers=headers)
|
||||
assert cases.status_code == 200
|
||||
case_id = cases.json()["data"]["items"][0]["id"]
|
||||
with client.stream(
|
||||
"POST",
|
||||
f"/api/v1/learning-assistant/sessions/{assistant_session_id}/chat/stream",
|
||||
headers=headers,
|
||||
json={"question": "结合刚才的问题再解释一下严重程度判断。", "top_k": 1},
|
||||
) as session_rag_stream:
|
||||
assert session_rag_stream.status_code == 200
|
||||
session_rag_stream_text = "".join(session_rag_stream.iter_text())
|
||||
assert "event: session_ready" in session_rag_stream_text
|
||||
assert "event: retrieval_done" in session_rag_stream_text
|
||||
assert "event: answer_delta" in session_rag_stream_text
|
||||
assert "event: answer_done" in session_rag_stream_text
|
||||
assert assistant_session_id in session_rag_stream_text
|
||||
|
||||
case_id = 1
|
||||
|
||||
teaching_items = client.get(f"/api/v1/teaching/cases/{case_id}/items", headers=headers)
|
||||
assert teaching_items.status_code == 200
|
||||
@@ -284,6 +312,13 @@ def run_api_contract_tests() -> None:
|
||||
)
|
||||
assert invalid_config.status_code == 422
|
||||
|
||||
invalid_mode = client.post(
|
||||
"/api/v1/sessions",
|
||||
headers=headers,
|
||||
json={"case_id": case_id, "training_type": "diagnosis_treatment", "mode": "invalid", "score_type": "percentage"},
|
||||
)
|
||||
assert invalid_mode.status_code == 422
|
||||
|
||||
cross_user = client.get(
|
||||
f"/api/v1/sessions/{session_id}/order-items",
|
||||
headers={"Authorization": "Bearer api_user_002_token", "X-Entry-Scene": "api_test"},
|
||||
@@ -388,23 +423,12 @@ def run_api_contract_tests() -> None:
|
||||
assert cross_user_detail.status_code == 404
|
||||
assert cross_user_detail.json()["code"] == "EVALUATION_NOT_FOUND"
|
||||
|
||||
pdf = client.post(f"/api/v1/evaluations/{evaluation_id}/export-pdf", headers=headers)
|
||||
assert pdf.status_code == 200
|
||||
assert pdf.json()["data"]["file_path"]
|
||||
|
||||
pdf_download = client.get(f"/api/v1/evaluations/{evaluation_id}/download-pdf", headers=headers)
|
||||
assert pdf_download.status_code == 200
|
||||
assert pdf_download.headers["content-type"].startswith("application/pdf")
|
||||
assert "attachment" in pdf_download.headers.get("content-disposition", "")
|
||||
assert pdf_download.content.startswith(b"%PDF")
|
||||
|
||||
cross_user_pdf = client.post(
|
||||
f"/api/v1/evaluations/{evaluation_id}/export-pdf",
|
||||
headers={"Authorization": "Bearer api_user_002_token", "X-Entry-Scene": "api_test"},
|
||||
)
|
||||
assert cross_user_pdf.status_code == 404
|
||||
assert cross_user_pdf.json()["code"] == "EVALUATION_NOT_FOUND"
|
||||
|
||||
cross_user_pdf_download = client.get(
|
||||
f"/api/v1/evaluations/{evaluation_id}/download-pdf",
|
||||
headers={"Authorization": "Bearer api_user_002_token", "X-Entry-Scene": "api_test"},
|
||||
@@ -439,20 +463,6 @@ def run_api_contract_tests() -> None:
|
||||
assert "event: hint_delta" in hint_stream_text
|
||||
assert "event: hint_done" in hint_stream_text
|
||||
|
||||
teaching = client.post(
|
||||
"/api/v1/sessions",
|
||||
headers=headers,
|
||||
json={"case_id": case_id, "training_type": "diagnosis_treatment", "mode": "teaching", "score_type": "percentage"},
|
||||
)
|
||||
assert teaching.status_code == 200
|
||||
teaching_hint = client.post(
|
||||
f"/api/v1/sessions/{teaching.json()['data']['session_id']}/hints",
|
||||
headers=headers,
|
||||
json={"scope": "current_conversation"},
|
||||
)
|
||||
assert teaching_hint.status_code == 400
|
||||
assert teaching_hint.json()["code"] == "SESSION_STATUS_INVALID"
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_api_contract_tests()
|
||||
print("api contract tests passed")
|
||||
|
||||
@@ -64,7 +64,7 @@ def test_reasoning_effort_disabled_when_thinking_off() -> None:
|
||||
|
||||
|
||||
def test_hint_agent_invalid_json_fallback() -> None:
|
||||
"""新手提示:验证模型输出结构不匹配时使用稳定 fallback。"""
|
||||
"""练习提示:验证模型输出结构不匹配时使用稳定 fallback。"""
|
||||
agent = HintAgent()
|
||||
payload = {
|
||||
"case": {
|
||||
|
||||
+19
-3
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -37,6 +38,21 @@ from app.services.session_service import SessionService
|
||||
from scripts.init_demo_db import init_database
|
||||
|
||||
|
||||
async def collect_stream_reply(session_service: SessionService, ctx: UserContext, session_id: int, message: str) -> str:
|
||||
"""测试辅助:消费流式问诊 SSE,提取 AI 病人增量文本。"""
|
||||
stream = await session_service.stream_chat(ctx, session_id, message)
|
||||
reply = ""
|
||||
done = False
|
||||
async for event in stream:
|
||||
if event.startswith("event: message_delta"):
|
||||
payload_text = event.split("data:", 1)[1].strip()
|
||||
reply += json.loads(payload_text)["delta"]
|
||||
if event.startswith("event: message_done"):
|
||||
done = True
|
||||
assert done is True
|
||||
return reply
|
||||
|
||||
|
||||
async def run_demo_flow() -> None:
|
||||
"""完整闭环:验证第一版 Demo 的核心训练链路可跑通。"""
|
||||
init_database()
|
||||
@@ -69,9 +85,9 @@ async def run_demo_flow() -> None:
|
||||
assert created.status == "inquiry"
|
||||
assert created.patient_config["labels"]["visit_environment"] == "门诊"
|
||||
|
||||
chat = await session_service.chat(ctx, created.session_id, ChatRequest(message="孩子最高体温多少?").message)
|
||||
chat_reply = await collect_stream_reply(session_service, ctx, created.session_id, ChatRequest(message="孩子最高体温多少?").message)
|
||||
db.commit()
|
||||
assert chat.reply
|
||||
assert chat_reply
|
||||
|
||||
order = order_service.create_order(created.session_id, ctx.user_id, CreateOrderRequest(item_code="chest_xray").item_code)
|
||||
db.commit()
|
||||
@@ -136,7 +152,7 @@ async def run_demo_flow() -> None:
|
||||
assert treatment.status == "evaluating"
|
||||
|
||||
try:
|
||||
await session_service.chat(ctx, created.session_id, "治疗后还能问诊吗?")
|
||||
await session_service.stream_chat(ctx, created.session_id, "治疗后还能问诊吗?")
|
||||
except AppError as exc:
|
||||
assert exc.code == "SESSION_STATUS_INVALID"
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user