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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user