generated from kgod/ai-review-template
feat: builder 简历生成 + 轻度优化 + 简历导入交付副本
自内部仓库剥离深度优化与 RAG 知识库后的交付版本: - Builder 对话式简历生成(FSM + 意图路由 LLM 兜底增强) - 条目级轻度优化:事实覆盖门禁 + STAR/bullet 修复链,功能/简介/成果与技术栈同级保护 - 简历导入:DOCX/PDF 解析、结构归一、手机号脱敏 - PostgreSQL 运行时 + Alembic 迁移链 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""B-Step1c: intent router settings, shadow logger, and Builder flow mount (P0 observe-only)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from app.chat_intent_classifier import RuleBasedChatIntentClassifier
|
||||
from app.chat_intent_shadow import (
|
||||
ChatIntentShadowLogger,
|
||||
build_chat_intent_shadow,
|
||||
)
|
||||
from app.chat_intents import ChatTurnClassification
|
||||
from app.settings import Settings, load_settings
|
||||
from builder_flow_helpers import create_builder_session, send_message
|
||||
|
||||
|
||||
def test_intent_router_settings_defaults() -> None:
|
||||
settings = Settings()
|
||||
assert settings.intent_router_mode == "off"
|
||||
assert settings.intent_model is None
|
||||
|
||||
|
||||
def test_intent_router_settings_from_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Any) -> None:
|
||||
monkeypatch.setenv("RESUME_AGENT_INTENT_ROUTER_MODE", "shadow")
|
||||
monkeypatch.setenv("RESUME_AGENT_INTENT_MODEL", "kimi-k3")
|
||||
settings = load_settings(tmp_path / "missing.env")
|
||||
assert settings.intent_router_mode == "shadow"
|
||||
assert settings.intent_model == "kimi-k3"
|
||||
monkeypatch.setenv("RESUME_AGENT_INTENT_ROUTER_MODE", "bogus")
|
||||
with pytest.raises(ValueError, match="INTENT_ROUTER_MODE"):
|
||||
load_settings(tmp_path / "missing.env")
|
||||
|
||||
|
||||
class _StubClassifier:
|
||||
def __init__(self, result: ChatTurnClassification | None = None, exc: Exception | None = None) -> None:
|
||||
self.result = result
|
||||
self.exc = exc
|
||||
|
||||
def classify(self, message: str, *, state_summary: dict[str, Any]) -> ChatTurnClassification:
|
||||
if self.exc:
|
||||
raise self.exc
|
||||
assert self.result is not None
|
||||
return self.result
|
||||
|
||||
|
||||
def _captured_events(monkeypatch: pytest.MonkeyPatch) -> list[tuple[str, dict[str, Any]]]:
|
||||
events: list[tuple[str, dict[str, Any]]] = []
|
||||
monkeypatch.setattr(
|
||||
"app.chat_intent_shadow.log_ai_event",
|
||||
lambda event, **fields: events.append((event, fields)),
|
||||
)
|
||||
return events
|
||||
|
||||
|
||||
def test_shadow_logs_llm_vs_rule_disagreement(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
events = _captured_events(monkeypatch)
|
||||
shadow = ChatIntentShadowLogger(
|
||||
_StubClassifier(ChatTurnClassification(intent="ask_question", confidence=0.9)),
|
||||
RuleBasedChatIntentClassifier(),
|
||||
)
|
||||
shadow.observe("没有", state_summary={})
|
||||
assert events == [
|
||||
(
|
||||
"chat_intent_shadow",
|
||||
{
|
||||
"registry_version": "1",
|
||||
"rule_intent": "no_info",
|
||||
"llm_intent": "ask_question",
|
||||
"llm_confidence": 0.9,
|
||||
"disagreement": True,
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_shadow_logs_agreement_and_survives_llm_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
events = _captured_events(monkeypatch)
|
||||
agree = ChatIntentShadowLogger(
|
||||
_StubClassifier(ChatTurnClassification(intent="no_info")),
|
||||
RuleBasedChatIntentClassifier(),
|
||||
)
|
||||
agree.observe("没有", state_summary={})
|
||||
assert events[0][1]["disagreement"] is False
|
||||
|
||||
failing = ChatIntentShadowLogger(
|
||||
_StubClassifier(exc=RuntimeError("boom")),
|
||||
RuleBasedChatIntentClassifier(),
|
||||
)
|
||||
failing.observe("没有", state_summary={}) # must not raise
|
||||
assert events[1][0] == "chat_intent_shadow_error"
|
||||
|
||||
|
||||
def test_build_chat_intent_shadow_requires_shadow_mode_and_openai() -> None:
|
||||
openai = {"llm_provider": "openai", "openai_api_key": "k"}
|
||||
assert build_chat_intent_shadow(Settings(**openai, intent_router_mode="off")) is None
|
||||
assert build_chat_intent_shadow(Settings(llm_provider="rule", intent_router_mode="shadow")) is None
|
||||
shadow = build_chat_intent_shadow(Settings(**openai, intent_router_mode="shadow"))
|
||||
assert isinstance(shadow, ChatIntentShadowLogger)
|
||||
tuned = build_chat_intent_shadow(Settings(**openai, intent_router_mode="shadow", intent_model="kimi-k3"))
|
||||
assert tuned is not None
|
||||
assert tuned.primary._client.settings.openai_model == "kimi-k3"
|
||||
|
||||
|
||||
class _Spy:
|
||||
def __init__(self, *, raises: bool = False) -> None:
|
||||
self.raises = raises
|
||||
self.calls: list[dict[str, Any]] = []
|
||||
|
||||
def observe(self, message: str, *, state_summary: dict[str, Any]) -> None:
|
||||
if self.raises:
|
||||
raise RuntimeError("spy boom")
|
||||
self.calls.append({"message": message, "state_summary": state_summary})
|
||||
|
||||
|
||||
def test_process_message_notifies_mounted_shadow(client: Any, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
agent = client.app.state.resume_agent
|
||||
spy = _Spy()
|
||||
monkeypatch.setattr(agent, "_chat_intent_shadow", spy, raising=False)
|
||||
session_id, _ = create_builder_session(client)
|
||||
send_message(client, session_id, "新增一段教育经历")
|
||||
assert spy.calls[0]["message"] == "新增一段教育经历"
|
||||
assert "confirmed_entries" in spy.calls[0]["state_summary"]
|
||||
|
||||
|
||||
def test_shadow_failure_never_breaks_routing(client: Any, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
agent = client.app.state.resume_agent
|
||||
monkeypatch.setattr(agent, "_chat_intent_shadow", _Spy(raises=True), raising=False)
|
||||
session_id, _ = create_builder_session(client)
|
||||
body = send_message(client, session_id, "新增一段教育经历")
|
||||
assert body["turn"]["role"] == "assistant"
|
||||
Reference in New Issue
Block a user