feat(agent): stream admin debug preview

This commit is contained in:
2026-07-17 13:47:05 +08:00
parent 13fed0467a
commit bfaf2ebf67
11 changed files with 620 additions and 75 deletions

View File

@@ -1,17 +1,22 @@
import asyncio
from decimal import Decimal
import json
from unittest.mock import patch
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.api.admin_settings import get_agent_runtime_config, save_agent_runtime_config
from app.api.admin_settings import _debug_agent_stream, get_agent_runtime_config, save_agent_runtime_config
from app.models import Base
from app.models.admin import Admin
from app.models.ai_config import ModelConfig
from app.schemas.admin import AgentRuntimeConfigSaveRequest
from app.services.model_stream_service import _openai_stream_payload, _stream_configured_model_async
from app.schemas.admin import AgentDebugRequest, AgentRuntimeConfigSaveRequest
from app.services.model_stream_service import (
AsyncStreamingModelResponse,
_openai_stream_payload,
_stream_configured_model_async,
)
from app.services.model_service import ModelClientService, _max_output_tokens
from app.services.rag_service import RagResult
@@ -128,6 +133,66 @@ def test_disabled_stream_returns_one_complete_chunk():
assert chunks == [answer]
def test_agent_debug_stream_emits_status_content_and_trace():
async def chunks():
yield "<think>内部思考</think>"
yield "- **第一步**:停一下"
rag_result = RagResult(
question="怎么冷静",
knowledge_scopes=[],
chunks=[],
prompt="怎么冷静",
allow_general_knowledge=True,
retrieval_log_id=9,
tool_trace=[{"tool": "KnowledgeSearch", "status": "success"}],
messages=[{"role": "user", "content": "怎么冷静"}],
)
model_response = AsyncStreamingModelResponse(
model_id=1,
model_name="production-model",
input_token=10,
chunks=chunks(),
)
async def collect(payload, db, admin):
return [event async for event in _debug_agent_stream(payload, db, admin)]
with _database() as db:
admin = _admin()
model = _model()
db.add_all([admin, model])
db.commit()
payload = AgentDebugRequest(
promptContent="你是测试助手",
modelId=model.id,
knowledgeIds=[],
question="怎么冷静",
maxToken=8192,
)
with (
patch(
"app.services.agent_debug_service.KnowledgeAgentService.build_result",
return_value=rag_result,
),
patch(
"app.services.agent_debug_service.ModelStreamService.debug_stream_async",
return_value=model_response,
),
):
events = asyncio.run(collect(payload, db, admin))
decoded = [
json.loads(event.removeprefix("data: "))
for event in events
if event != "data: [DONE]\n\n"
]
assert [event["type"] for event in decoded] == ["status", "content", "content", "complete"]
assert decoded[1]["content"].startswith("<think>")
assert decoded[2]["content"] == "- **第一步**:停一下"
assert decoded[3]["retrievalTrace"][0]["tool"] == "KnowledgeSearch"
def test_agent_debug_does_not_truncate_long_answer(monkeypatch):
monkeypatch.setattr(
"app.services.model_service._call_configured_model",