refactor(chat): AI-native prompts, remove interview heuristics

- Drop interview_reply_length and utterance_substance; always run stage LLM
  and memory retrieval when enabled; trim Settings fields and .env.example.
- Replace guided/opening prompts with compact fact blocks plus unified
  behavior guidance; slim background_voice and persona to tone hints.
- InterviewAgent uses fixed chat_interview max_tokens/chars/segments.

Also includes stacked work: profile followup/extract path, evaluation rubric
and judge schema updates, transcript SPLIT handling in execution service,
user export markdown split tests, and golden case fixture.
This commit is contained in:
Kevin
2026-04-06 22:22:50 +08:00
parent ca8bcc8489
commit 2fded6fbd9
27 changed files with 426 additions and 1349 deletions

View File

@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.agents.chat.agent_turn import AgentChatTurn
from app.agents.chat.helpers import get_history_with_window
from app.agents.chat.interview_agent import InterviewAgent
from app.agents.chat.profile_agent import ProfileAgent
from app.agents.state_schema import MemoirStateSchema
@@ -19,13 +20,9 @@ from app.agents.chat.stage_detection import (
detect_primary_life_stage,
life_stage_display_name,
)
from app.agents.chat.utterance_substance import should_run_chat_stage_memory_heavy_work
from app.core.config import settings
from app.core.dependencies import get_llm_provider
from app.features.conversation.input_normalize import (
apply_conversation_input_rules,
normalize_chat_input_for_agent,
)
from app.features.conversation.input_normalize import normalize_chat_input_for_agent
from app.features.memoir.state_service import get_or_create_state, switch_stage
@@ -68,15 +65,6 @@ async def _fetch_interview_memory_evidence(
"event=chat_memory_retrieval_skip reason=empty user_id={}", user_id
)
return ""
if (
settings.chat_memory_retrieval_require_substantive
and not should_run_chat_stage_memory_heavy_work(msg)
):
logger.debug(
"event=chat_memory_retrieval_skip reason=not_substantive user_id={}",
user_id,
)
return ""
try:
emb = get_embedding_provider()
ms = MemoryService(db, embedding_provider=emb)
@@ -143,60 +131,79 @@ class ChatOrchestrator:
if user:
missing = get_missing_profile_fields_fn(user)
if missing:
try:
log_agent_detail(
logger,
"ChatOrchestrator route=profile conversation_id={} "
"missing_fields={} user_msg_len={}",
hw_profile = await get_history_with_window(
conversation_id,
max_pairs=settings.chat_history_max_pairs,
max_chars=settings.chat_history_max_chars,
)
profile_turn_total = hw_profile.turn_total
if profile_turn_total >= settings.chat_profile_max_turns:
logger.info(
"event=chat_profile_cap_skip conversation_id={} "
"turn_total={} cap={} missing_fields={}",
conversation_id,
profile_turn_total,
settings.chat_profile_max_turns,
missing,
len(user_message or ""),
)
run_extract = True
if settings.chat_profile_extract_require_substantive:
rules_only = apply_conversation_input_rules(user_message or "")
run_extract = should_run_chat_stage_memory_heavy_work(
rules_only
else:
try:
log_agent_detail(
logger,
"ChatOrchestrator route=profile conversation_id={} "
"missing_fields={} user_msg_len={} profile_turn_total={}",
conversation_id,
missing,
len(user_message or ""),
profile_turn_total,
)
extracted = None
if run_extract:
# Profile 阶段每轮都抽取:短确认语也可能带可推断资料,跳过抽取会导致槽位长期不更新
extracted = (
await self.profile_agent.extract_profile_from_message(
user_message, missing, conversation_id=conversation_id
)
)
if extracted:
await apply_extracted_profile_fn(user, extracted, db)
remaining = get_missing_profile_fields_fn(user)
filled = get_filled_profile_fields_fn(user)
interview_stage_hint = ""
if not remaining:
st = await get_or_create_state(user.id, db)
interview_stage_hint = life_stage_display_name(st.current_stage)
responses = await self.profile_agent.generate_profile_followup(
conversation_id=conversation_id,
user_message=user_message,
missing_fields=remaining,
filled_fields=filled,
nickname=user.nickname or "",
interview_stage_hint=interview_stage_hint,
)
if agent_summary_enabled():
logger.info(
"ChatOrchestrator.process_user_message route=profile "
"duration_ms={:.2f} conversation_id={} response_segments={}",
(time.perf_counter() - t0) * 1000,
"event=chat_profile_extract conversation_id={} "
"extracted_keys={} missing_before={}",
conversation_id,
len(responses),
list(extracted.keys()) if extracted else [],
missing,
)
if extracted:
await apply_extracted_profile_fn(user, extracted, db)
remaining = get_missing_profile_fields_fn(user)
filled = get_filled_profile_fields_fn(user)
interview_stage_hint = ""
if not remaining:
st = await get_or_create_state(user.id, db)
interview_stage_hint = life_stage_display_name(
st.current_stage
)
responses = await self.profile_agent.generate_profile_followup(
conversation_id=conversation_id,
user_message=user_message,
missing_fields=remaining,
filled_fields=filled,
nickname=user.nickname or "",
interview_stage_hint=interview_stage_hint,
)
if agent_summary_enabled():
logger.info(
"ChatOrchestrator.process_user_message route=profile "
"duration_ms={:.2f} conversation_id={} response_segments={}",
(time.perf_counter() - t0) * 1000,
conversation_id,
len(responses),
)
return AgentChatTurn(messages=responses, skip_tts=False)
except Exception as e:
logger.error(f"资料收集处理失败: {e}", exc_info=True)
return AgentChatTurn(
messages=["不好意思刚才没接住,你再说一遍好吗?"],
skip_tts=False,
)
return AgentChatTurn(messages=responses, skip_tts=False)
except Exception as e:
logger.error(f"资料收集处理失败: {e}", exc_info=True)
return AgentChatTurn(
messages=["不好意思刚才没接住,你再说一遍好吗?"],
skip_tts=False,
)
# --- 正式访谈模式 ---
user_id = user.id if user else None
@@ -227,14 +234,10 @@ class ChatOrchestrator:
is_from_voice=is_from_voice,
)
state = await get_or_create_state(user_id, db)
substantive_turn = should_run_chat_stage_memory_heavy_work(
normalized_user_message
)
detected = await detect_primary_life_stage(
normalized_user_message,
state.current_stage,
self.interview_agent.llm,
skip_llm=not substantive_turn,
)
if detected != state.current_stage:
state = await switch_stage(user_id, detected, db)