fix: align agent no-hit handling
This commit is contained in:
@@ -74,7 +74,7 @@ const selectedModelName = computed(() => {
|
|||||||
return model?.displayName || model?.modelName || "未选择模型";
|
return model?.displayName || model?.modelName || "未选择模型";
|
||||||
});
|
});
|
||||||
const selectedKnowledgeSummary = computed(() => {
|
const selectedKnowledgeSummary = computed(() => {
|
||||||
if (!agentForm.knowledgeIds.length) return "未选择知识库";
|
if (!agentForm.knowledgeIds.length) return "默认使用全部正式开放知识库";
|
||||||
if (agentForm.knowledgeIds.length === 1) {
|
if (agentForm.knowledgeIds.length === 1) {
|
||||||
return knowledge.value.find((item) => item.id === agentForm.knowledgeIds[0])?.name || "1 个知识库";
|
return knowledge.value.find((item) => item.id === agentForm.knowledgeIds[0])?.name || "1 个知识库";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ class AgentDebugService:
|
|||||||
SimpleNamespace(id=index + 1, role=item.role, content=item.content)
|
SimpleNamespace(id=index + 1, role=item.role, content=item.content)
|
||||||
for index, item in enumerate(payload.history)
|
for index, item in enumerate(payload.history)
|
||||||
]
|
]
|
||||||
|
preview_knowledge_ids = payload.knowledgeIds or None
|
||||||
|
version_overrides = payload.knowledgeVersions or None
|
||||||
rag_result = await KnowledgeAgentService.build_result(
|
rag_result = await KnowledgeAgentService.build_result(
|
||||||
db,
|
db,
|
||||||
question=payload.question,
|
question=payload.question,
|
||||||
history=history,
|
history=history,
|
||||||
version_overrides=payload.knowledgeVersions,
|
version_overrides=version_overrides,
|
||||||
preview_knowledge_ids=payload.knowledgeIds,
|
preview_knowledge_ids=preview_knowledge_ids,
|
||||||
prompt_override=payload.promptContent,
|
prompt_override=payload.promptContent,
|
||||||
response_depth=payload.responseDepth,
|
response_depth=payload.responseDepth,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class ModelClientService:
|
|||||||
if model is None:
|
if model is None:
|
||||||
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
||||||
model_name = model.model_name
|
model_name = model.model_name
|
||||||
answer = _call_configured_model(model, rag_result, allow_no_hit=rag_result.allow_general_knowledge)
|
answer = _call_configured_model(model, rag_result, allow_no_hit=True)
|
||||||
return ModelCompletion(
|
return ModelCompletion(
|
||||||
answer=answer,
|
answer=answer,
|
||||||
model_id=model.id if model is not None else None,
|
model_id=model.id if model is not None else None,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from app.services.model_service import (
|
|||||||
_system_and_turn_messages,
|
_system_and_turn_messages,
|
||||||
_system_config_bool,
|
_system_config_bool,
|
||||||
)
|
)
|
||||||
from app.services.rag_service import NO_HIT_ANSWER, RagResult
|
from app.services.rag_service import RagResult
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -64,14 +64,6 @@ class ModelStreamService:
|
|||||||
chunks=_display_chunks(model, _mock_answer(rag_result)),
|
chunks=_display_chunks(model, _mock_answer(rag_result)),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not rag_result.is_hit and not rag_result.allow_general_knowledge:
|
|
||||||
return StreamingModelResponse(
|
|
||||||
model_id=model.id if model is not None else None,
|
|
||||||
model_name=model.model_name if model is not None else "no-hit",
|
|
||||||
input_token=_rough_token_count(rag_result.prompt),
|
|
||||||
chunks=_display_chunks(model, NO_HIT_ANSWER),
|
|
||||||
)
|
|
||||||
|
|
||||||
if model is None:
|
if model is None:
|
||||||
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
||||||
if not (model.api_url or model.base_url) or not model.api_key:
|
if not (model.api_url or model.base_url) or not model.api_key:
|
||||||
@@ -98,14 +90,6 @@ class ModelStreamService:
|
|||||||
chunks=_async_display_chunks(model, _mock_answer(rag_result)),
|
chunks=_async_display_chunks(model, _mock_answer(rag_result)),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not rag_result.is_hit and not rag_result.allow_general_knowledge:
|
|
||||||
return AsyncStreamingModelResponse(
|
|
||||||
model_id=model.id if model is not None else None,
|
|
||||||
model_name=model.model_name if model is not None else "no-hit",
|
|
||||||
input_token=_rough_token_count(rag_result.prompt),
|
|
||||||
chunks=_async_display_chunks(model, NO_HIT_ANSWER),
|
|
||||||
)
|
|
||||||
|
|
||||||
if model is None:
|
if model is None:
|
||||||
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
||||||
if not (model.api_url or model.base_url) or not model.api_key:
|
if not (model.api_url or model.base_url) or not model.api_key:
|
||||||
@@ -147,19 +131,19 @@ def _get_enabled_model(db: Session) -> ModelConfig | None:
|
|||||||
def _stream_configured_model(model: ModelConfig, rag_result: RagResult) -> Iterator[str]:
|
def _stream_configured_model(model: ModelConfig, rag_result: RagResult) -> Iterator[str]:
|
||||||
api_type = model.api_type or "openai_compatible"
|
api_type = model.api_type or "openai_compatible"
|
||||||
if model.stream_enabled != 1:
|
if model.stream_enabled != 1:
|
||||||
return iter((_call_configured_model(model, rag_result, allow_no_hit=rag_result.allow_general_knowledge),))
|
return iter((_call_configured_model(model, rag_result, allow_no_hit=True),))
|
||||||
if api_type == "anthropic_messages":
|
if api_type == "anthropic_messages":
|
||||||
return _stream_anthropic_messages(model, rag_result)
|
return _stream_anthropic_messages(model, rag_result)
|
||||||
if api_type == "openai_compatible":
|
if api_type == "openai_compatible":
|
||||||
return _stream_openai_compatible_model(model, rag_result)
|
return _stream_openai_compatible_model(model, rag_result)
|
||||||
return iter((_call_configured_model(model, rag_result, allow_no_hit=rag_result.allow_general_knowledge),))
|
return iter((_call_configured_model(model, rag_result, allow_no_hit=True),))
|
||||||
|
|
||||||
|
|
||||||
async def _stream_configured_model_async(model: ModelConfig, rag_result: RagResult) -> AsyncIterator[str]:
|
async def _stream_configured_model_async(model: ModelConfig, rag_result: RagResult) -> AsyncIterator[str]:
|
||||||
api_type = model.api_type or "openai_compatible"
|
api_type = model.api_type or "openai_compatible"
|
||||||
if model.stream_enabled != 1:
|
if model.stream_enabled != 1:
|
||||||
answer = await asyncio.to_thread(
|
answer = await asyncio.to_thread(
|
||||||
_call_configured_model, model, rag_result, allow_no_hit=rag_result.allow_general_knowledge
|
_call_configured_model, model, rag_result, allow_no_hit=True
|
||||||
)
|
)
|
||||||
yield answer
|
yield answer
|
||||||
return
|
return
|
||||||
@@ -175,7 +159,7 @@ async def _stream_configured_model_async(model: ModelConfig, rag_result: RagResu
|
|||||||
return
|
return
|
||||||
|
|
||||||
answer = await asyncio.to_thread(
|
answer = await asyncio.to_thread(
|
||||||
_call_configured_model, model, rag_result, allow_no_hit=rag_result.allow_general_knowledge
|
_call_configured_model, model, rag_result, allow_no_hit=True
|
||||||
)
|
)
|
||||||
yield answer
|
yield answer
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from app.schemas.admin import AgentDebugRequest, AgentRuntimeConfigSaveRequest
|
|||||||
from app.services.agent_debug_service import AgentDebugService
|
from app.services.agent_debug_service import AgentDebugService
|
||||||
from app.services.model_stream_service import (
|
from app.services.model_stream_service import (
|
||||||
AsyncStreamingModelResponse,
|
AsyncStreamingModelResponse,
|
||||||
|
ModelStreamService,
|
||||||
_openai_stream_payload,
|
_openai_stream_payload,
|
||||||
_stream_configured_model_async,
|
_stream_configured_model_async,
|
||||||
)
|
)
|
||||||
@@ -194,12 +195,71 @@ def test_debug_preview_passes_conversation_history_and_replaces_saved_prompt():
|
|||||||
|
|
||||||
kwargs = build_result.await_args.kwargs
|
kwargs = build_result.await_args.kwargs
|
||||||
assert [item.content for item in kwargs["history"]] == ["最开始的问题", "第一次回答"]
|
assert [item.content for item in kwargs["history"]] == ["最开始的问题", "第一次回答"]
|
||||||
|
assert kwargs["preview_knowledge_ids"] is None
|
||||||
|
assert kwargs["version_overrides"] is None
|
||||||
assert kwargs["prompt_override"] == "调试主提示词"
|
assert kwargs["prompt_override"] == "调试主提示词"
|
||||||
assert kwargs["response_depth"] == 35
|
assert kwargs["response_depth"] == 35
|
||||||
assert result.messages == rag_result.messages
|
assert result.messages == rag_result.messages
|
||||||
assert result.prompt == "调试提示词渲染结果"
|
assert result.prompt == "调试提示词渲染结果"
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_debug_empty_knowledge_selection_uses_formal_open_catalog():
|
||||||
|
payload = AgentDebugRequest(
|
||||||
|
promptContent="调试主提示词",
|
||||||
|
modelId=1,
|
||||||
|
knowledgeIds=[],
|
||||||
|
knowledgeVersions={},
|
||||||
|
question="测试问题",
|
||||||
|
)
|
||||||
|
rag_result = RagResult(
|
||||||
|
question="测试问题",
|
||||||
|
knowledge_scopes=[],
|
||||||
|
chunks=[],
|
||||||
|
prompt="测试",
|
||||||
|
allow_general_knowledge=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with _database() as db:
|
||||||
|
build_result = AsyncMock(return_value=rag_result)
|
||||||
|
with patch("app.services.agent_debug_service.KnowledgeAgentService.build_result", build_result):
|
||||||
|
asyncio.run(AgentDebugService.build_result(db, payload))
|
||||||
|
|
||||||
|
kwargs = build_result.await_args.kwargs
|
||||||
|
assert kwargs["preview_knowledge_ids"] is None
|
||||||
|
assert kwargs["version_overrides"] is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_stream_no_hit_still_calls_model_for_cautious_answer():
|
||||||
|
async def configured_chunks(_model, _rag_result):
|
||||||
|
yield "我暂时没有可靠课程依据,但可以先帮你整理需要确认的点。"
|
||||||
|
|
||||||
|
async def collect():
|
||||||
|
with _database() as db:
|
||||||
|
model = _model()
|
||||||
|
db.add_all([
|
||||||
|
model,
|
||||||
|
SystemConfig(config_key="mock_model_enabled", config_value="false"),
|
||||||
|
])
|
||||||
|
db.commit()
|
||||||
|
rag_result = RagResult(
|
||||||
|
question="课程里的特殊练习怎么做?",
|
||||||
|
knowledge_scopes=[],
|
||||||
|
chunks=[],
|
||||||
|
prompt="本轮没有可靠的正式知识章节。",
|
||||||
|
allow_general_knowledge=False,
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"app.services.model_stream_service._stream_configured_model_async",
|
||||||
|
configured_chunks,
|
||||||
|
):
|
||||||
|
response = ModelStreamService.stream_async(db, rag_result)
|
||||||
|
return [chunk async for chunk in response.chunks]
|
||||||
|
|
||||||
|
chunks = asyncio.run(collect())
|
||||||
|
|
||||||
|
assert chunks == ["我暂时没有可靠课程依据,但可以先帮你整理需要确认的点。"]
|
||||||
|
|
||||||
|
|
||||||
def test_prompt_includes_response_depth_instruction():
|
def test_prompt_includes_response_depth_instruction():
|
||||||
with _database() as db:
|
with _database() as db:
|
||||||
db.add(SystemConfig(config_key="agent_response_depth", config_value="20"))
|
db.add(SystemConfig(config_key="agent_response_depth", config_value="20"))
|
||||||
|
|||||||
Reference in New Issue
Block a user