2026-03-19 10:36:55 +08:00
|
|
|
|
"""
|
|
|
|
|
|
InterviewAgent:正式访谈 Specialist
|
|
|
|
|
|
负责状态感知回复、开场白,不负责 Redis 持久化(由 Orchestrator 统一处理)
|
|
|
|
|
|
"""
|
2026-03-19 14:36:14 +08:00
|
|
|
|
|
2026-04-02 12:00:00 +08:00
|
|
|
|
import time
|
2026-03-26 12:13:36 +08:00
|
|
|
|
from typing import Any, List, Optional
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-04-02 12:00:00 +08:00
|
|
|
|
from langchain_core.messages import HumanMessage, SystemMessage
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-04-02 12:00:00 +08:00
|
|
|
|
from app.agents.chat.agent_turn import AgentChatTurn
|
|
|
|
|
|
from app.agents.chat.helpers import format_history_string, get_history_with_window
|
2026-04-08 21:36:12 +08:00
|
|
|
|
from app.agents.chat.interview_state_hints import (
|
|
|
|
|
|
apply_duplicate_question_guard,
|
|
|
|
|
|
extract_recent_questions,
|
|
|
|
|
|
update_recent_questions,
|
|
|
|
|
|
)
|
2026-04-10 13:56:44 +08:00
|
|
|
|
from app.agents.chat.interview_turn_plan import plan_interview_turn
|
2026-03-31 23:55:26 +08:00
|
|
|
|
from app.agents.chat.personas import normalize_interview_persona
|
2026-04-02 12:00:00 +08:00
|
|
|
|
from app.agents.chat.prompt_context import ChatPromptContext
|
2026-03-19 10:54:48 +08:00
|
|
|
|
from app.agents.chat.prompts_conversation import (
|
|
|
|
|
|
SLOT_NAME_MAP,
|
|
|
|
|
|
get_opening_prompt,
|
|
|
|
|
|
)
|
2026-03-27 16:01:28 +08:00
|
|
|
|
from app.agents.chat.reply_limits import (
|
|
|
|
|
|
nonempty_segments_or_fallback,
|
|
|
|
|
|
segments_from_llm_response,
|
|
|
|
|
|
truncate_chat_segments,
|
|
|
|
|
|
)
|
2026-04-08 15:37:09 +08:00
|
|
|
|
from app.agents.chat.stage_detection import keyword_fallback_primary_stage
|
|
|
|
|
|
from app.agents.state_schema import MemoirStateSchema
|
2026-03-26 12:13:36 +08:00
|
|
|
|
from app.core.agent_logging import (
|
|
|
|
|
|
agent_span,
|
|
|
|
|
|
log_agent_payload,
|
|
|
|
|
|
log_agent_summary,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.core.config import settings
|
2026-04-02 12:00:00 +08:00
|
|
|
|
from app.core.dependencies import get_llm_provider
|
|
|
|
|
|
from app.core.logging import get_logger
|
2026-03-31 23:55:26 +08:00
|
|
|
|
from app.features.conversation.input_normalize import normalize_chat_input_for_agent
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
2026-03-20 17:25:42 +08:00
|
|
|
|
# LLM 不可用或调用失败时对用户展示(不暴露异常细节、不触发 TTS)
|
|
|
|
|
|
_FALLBACK_REPLY = "刚才网络不太稳,没接上。你可以再说一遍,或稍后再试。"
|
|
|
|
|
|
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
def _get_langchain_llm():
|
|
|
|
|
|
try:
|
|
|
|
|
|
provider = get_llm_provider()
|
|
|
|
|
|
return getattr(provider, "langchain_llm", None)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-02 12:00:00 +08:00
|
|
|
|
def _message_contents_char_count(messages: List[Any]) -> int:
|
|
|
|
|
|
n = 0
|
|
|
|
|
|
for m in messages:
|
|
|
|
|
|
c = getattr(m, "content", None)
|
|
|
|
|
|
if isinstance(c, str):
|
|
|
|
|
|
n += len(c)
|
|
|
|
|
|
return n
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 10:36:55 +08:00
|
|
|
|
class InterviewAgent:
|
|
|
|
|
|
"""正式访谈 Specialist Agent"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.llm = _get_langchain_llm()
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_user_stage(self, user_message: str) -> str:
|
2026-03-26 12:13:36 +08:00
|
|
|
|
"""关键词回退:与 stage_detection 一致(多阶段打分)。"""
|
|
|
|
|
|
return keyword_fallback_primary_stage(user_message)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-03-31 23:55:26 +08:00
|
|
|
|
def _resolve_text_for_model(
|
|
|
|
|
|
self,
|
|
|
|
|
|
user_message: str,
|
|
|
|
|
|
normalized_user_message: Optional[str],
|
|
|
|
|
|
) -> str:
|
|
|
|
|
|
"""模型侧净稿:编排层已归一则直接用;否则在本层补一次(含可选 LLM)。"""
|
|
|
|
|
|
if normalized_user_message is not None:
|
|
|
|
|
|
return (normalized_user_message or "").strip()
|
|
|
|
|
|
llm_n = None
|
|
|
|
|
|
if settings.chat_input_normalize_enabled and (
|
|
|
|
|
|
(settings.chat_input_normalize_mode or "").strip().lower() == "llm"
|
|
|
|
|
|
):
|
|
|
|
|
|
llm_n = self.llm
|
|
|
|
|
|
return normalize_chat_input_for_agent(user_message or "", llm=llm_n)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
async def generate_response_with_state(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
user_message: str,
|
|
|
|
|
|
memoir_state: MemoirStateSchema,
|
|
|
|
|
|
user_profile_context: str = "",
|
2026-03-26 12:13:36 +08:00
|
|
|
|
detected_user_stage: Optional[str] = None,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
memory_evidence_text: str = "",
|
|
|
|
|
|
background_voice: str = "default",
|
|
|
|
|
|
normalized_user_message: Optional[str] = None,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation: str = "",
|
2026-04-08 15:37:09 +08:00
|
|
|
|
profile_birth_year: int | None = None,
|
|
|
|
|
|
profile_era_place: str = "",
|
2026-04-10 13:56:44 +08:00
|
|
|
|
stage_switched_this_turn: bool = False,
|
2026-03-20 17:25:42 +08:00
|
|
|
|
) -> AgentChatTurn:
|
2026-03-19 10:36:55 +08:00
|
|
|
|
"""生成状态感知的访谈回复,不持久化(由 Orchestrator 负责)"""
|
|
|
|
|
|
if not self.llm:
|
2026-03-20 17:25:42 +08:00
|
|
|
|
logger.warning("InterviewAgent: LLM 未配置,返回兜底文案")
|
|
|
|
|
|
return AgentChatTurn(messages=[_FALLBACK_REPLY], skip_tts=True)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
try:
|
2026-03-31 23:55:26 +08:00
|
|
|
|
text_for_model = self._resolve_text_for_model(
|
|
|
|
|
|
user_message, normalized_user_message
|
|
|
|
|
|
)
|
2026-04-08 21:36:12 +08:00
|
|
|
|
empty_slots = memoir_state.prompt_empty_slots_for_current_stage()
|
2026-03-19 10:36:55 +08:00
|
|
|
|
filled_slots = {
|
|
|
|
|
|
key: value.snippet
|
2026-03-19 14:36:14 +08:00
|
|
|
|
for key, value in memoir_state.slots.get(
|
|
|
|
|
|
memoir_state.current_stage, {}
|
|
|
|
|
|
).items()
|
2026-03-19 10:36:55 +08:00
|
|
|
|
if value.snippet
|
|
|
|
|
|
}
|
2026-03-26 12:13:36 +08:00
|
|
|
|
if detected_user_stage is not None:
|
|
|
|
|
|
du = detected_user_stage
|
|
|
|
|
|
else:
|
2026-03-31 23:55:26 +08:00
|
|
|
|
du = self._detect_user_stage(text_for_model)
|
2026-04-02 12:00:00 +08:00
|
|
|
|
hw = await get_history_with_window(
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
max_pairs=settings.chat_history_max_pairs,
|
|
|
|
|
|
max_chars=settings.chat_history_max_chars,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
2026-04-08 21:36:12 +08:00
|
|
|
|
recent_questions = extract_recent_questions(hw.window)
|
2026-04-02 12:00:00 +08:00
|
|
|
|
conversation_turn_total = hw.turn_total
|
2026-03-19 10:36:55 +08:00
|
|
|
|
all_stages_coverage = memoir_state.all_stages_coverage()
|
2026-03-31 23:55:26 +08:00
|
|
|
|
persona = normalize_interview_persona(settings.chat_interview_persona)
|
2026-04-06 22:22:50 +08:00
|
|
|
|
max_segments = int(settings.chat_interview_max_segments)
|
|
|
|
|
|
max_tokens = int(settings.chat_interview_max_tokens)
|
|
|
|
|
|
max_chars = int(settings.chat_interview_max_chars_per_segment)
|
|
|
|
|
|
|
2026-04-10 13:56:44 +08:00
|
|
|
|
turn_plan = plan_interview_turn(
|
|
|
|
|
|
current_stage=memoir_state.current_stage,
|
|
|
|
|
|
empty_slots=empty_slots,
|
|
|
|
|
|
normalized_user_message=text_for_model,
|
|
|
|
|
|
memory_evidence_text=memory_evidence_text,
|
|
|
|
|
|
stage_switched_this_turn=stage_switched_this_turn,
|
|
|
|
|
|
)
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=interview_turn_plan mode={} anchor_slot={} snippet_len={}",
|
|
|
|
|
|
turn_plan.mode,
|
|
|
|
|
|
turn_plan.anchor_slot_key or "-",
|
|
|
|
|
|
len(turn_plan.anchor_snippet or ""),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-02 12:00:00 +08:00
|
|
|
|
ctx = ChatPromptContext(
|
2026-03-19 10:36:55 +08:00
|
|
|
|
current_stage=memoir_state.current_stage,
|
|
|
|
|
|
empty_slots=empty_slots,
|
|
|
|
|
|
filled_slots=filled_slots,
|
|
|
|
|
|
all_stages_coverage=all_stages_coverage,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
detected_user_stage=du,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
user_profile_context=user_profile_context,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
persona=persona,
|
|
|
|
|
|
memory_evidence_text=memory_evidence_text,
|
|
|
|
|
|
background_voice=background_voice,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation=occupation,
|
2026-04-08 15:37:09 +08:00
|
|
|
|
profile_birth_year=profile_birth_year,
|
|
|
|
|
|
profile_era_place=profile_era_place,
|
2026-04-08 21:36:12 +08:00
|
|
|
|
known_facts=memoir_state.known_facts,
|
|
|
|
|
|
persona_threads=memoir_state.persona_threads,
|
|
|
|
|
|
recent_questions=recent_questions or memoir_state.recent_questions,
|
2026-04-10 13:56:44 +08:00
|
|
|
|
turn_plan=turn_plan,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
2026-04-02 12:00:00 +08:00
|
|
|
|
system_prompt = ctx.guided_system_prompt()
|
|
|
|
|
|
messages: List[Any] = [SystemMessage(content=system_prompt)]
|
|
|
|
|
|
messages.extend(hw.window)
|
|
|
|
|
|
messages.append(HumanMessage(content=text_for_model))
|
|
|
|
|
|
history_pairs_windowed = len(hw.window) // 2
|
|
|
|
|
|
window_chars = sum(len(getattr(m, "content", "") or "") for m in hw.window)
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=history_window_applied total={} windowed={} chars={}",
|
|
|
|
|
|
conversation_turn_total,
|
|
|
|
|
|
history_pairs_windowed,
|
|
|
|
|
|
window_chars,
|
|
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
log_agent_payload(
|
2026-04-02 12:00:00 +08:00
|
|
|
|
logger,
|
|
|
|
|
|
"InterviewAgent.generate_response.prompt",
|
2026-04-03 13:49:24 +08:00
|
|
|
|
format_history_string(
|
|
|
|
|
|
messages,
|
|
|
|
|
|
omit_system_body=settings.agent_log_omit_system_message_body,
|
|
|
|
|
|
),
|
2026-03-26 12:13:36 +08:00
|
|
|
|
)
|
2026-04-09 15:32:35 +08:00
|
|
|
|
chat_llm = self.llm.bind(
|
|
|
|
|
|
max_tokens=max_tokens,
|
|
|
|
|
|
temperature=float(settings.chat_interview_temperature),
|
|
|
|
|
|
)
|
2026-04-02 12:00:00 +08:00
|
|
|
|
prompt_chars = _message_contents_char_count(messages)
|
|
|
|
|
|
llm_t0 = time.perf_counter()
|
2026-03-26 12:13:36 +08:00
|
|
|
|
with agent_span(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
"InterviewAgent.generate_response.llm",
|
|
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
stage=memoir_state.current_stage,
|
|
|
|
|
|
):
|
2026-04-02 12:00:00 +08:00
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=chat_prompt_built agent=InterviewAgent.generate_response_with_state "
|
|
|
|
|
|
"prompt_chars={} history_pairs_total={} history_pairs_windowed={}",
|
|
|
|
|
|
prompt_chars,
|
|
|
|
|
|
conversation_turn_total,
|
|
|
|
|
|
history_pairs_windowed,
|
|
|
|
|
|
)
|
|
|
|
|
|
response = await chat_llm.ainvoke(messages)
|
|
|
|
|
|
response_ms = (time.perf_counter() - llm_t0) * 1000
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=chat_llm_done agent=InterviewAgent.generate_response_with_state "
|
|
|
|
|
|
"response_latency_ms={:.2f}",
|
|
|
|
|
|
response_ms,
|
|
|
|
|
|
)
|
2026-03-19 14:36:14 +08:00
|
|
|
|
response_text = (
|
|
|
|
|
|
response.content if hasattr(response, "content") else str(response)
|
|
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
log_agent_payload(
|
|
|
|
|
|
logger, "InterviewAgent.generate_response.raw_response", response_text
|
|
|
|
|
|
)
|
2026-03-27 16:01:28 +08:00
|
|
|
|
raw_list = segments_from_llm_response(
|
|
|
|
|
|
response_text,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
max_segments=max_segments,
|
2026-03-27 16:01:28 +08:00
|
|
|
|
)
|
|
|
|
|
|
if not raw_list:
|
|
|
|
|
|
raw_list = [response_text.strip()]
|
2026-03-26 12:13:36 +08:00
|
|
|
|
out = truncate_chat_segments(
|
|
|
|
|
|
raw_list,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
max_segments=max_segments,
|
|
|
|
|
|
max_chars_per_segment=max_chars,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
)
|
|
|
|
|
|
if not out:
|
2026-04-06 22:22:50 +08:00
|
|
|
|
out = [response_text.strip()[:max_chars]]
|
2026-03-27 16:01:28 +08:00
|
|
|
|
out = nonempty_segments_or_fallback(out, fallback=_FALLBACK_REPLY)
|
2026-04-08 21:36:12 +08:00
|
|
|
|
out, deduped = apply_duplicate_question_guard(
|
|
|
|
|
|
out,
|
|
|
|
|
|
state=memoir_state,
|
|
|
|
|
|
recent_questions=recent_questions or memoir_state.recent_questions,
|
|
|
|
|
|
)
|
|
|
|
|
|
updated_recent_questions = update_recent_questions(
|
|
|
|
|
|
recent_questions or memoir_state.recent_questions,
|
|
|
|
|
|
out,
|
|
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
log_agent_summary(
|
|
|
|
|
|
logger,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
"InterviewAgent.generate_response segments={} conversation_id={} "
|
2026-04-06 22:22:50 +08:00
|
|
|
|
"max_tokens={}",
|
2026-03-26 12:13:36 +08:00
|
|
|
|
len(out),
|
|
|
|
|
|
conversation_id,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
max_tokens,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
)
|
2026-04-08 21:36:12 +08:00
|
|
|
|
return AgentChatTurn(
|
|
|
|
|
|
messages=out,
|
|
|
|
|
|
skip_tts=False,
|
|
|
|
|
|
interview_state_meta={
|
|
|
|
|
|
"recent_questions": updated_recent_questions,
|
|
|
|
|
|
"duplicate_question_guard_triggered": deduped,
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
except Exception as e:
|
2026-03-26 12:13:36 +08:00
|
|
|
|
logger.error("生成回应失败: {}", e, exc_info=True)
|
2026-03-20 17:25:42 +08:00
|
|
|
|
return AgentChatTurn(messages=[_FALLBACK_REPLY], skip_tts=True)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
async def generate_opening_message(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
memoir_state: MemoirStateSchema,
|
|
|
|
|
|
user_profile_context: str = "",
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice: str = "default",
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation: str = "",
|
2026-04-08 17:10:09 +08:00
|
|
|
|
profile_birth_year: Optional[int] = None,
|
|
|
|
|
|
profile_era_place: str = "",
|
2026-03-19 10:36:55 +08:00
|
|
|
|
) -> List[str]:
|
|
|
|
|
|
"""生成空对话开场白,不持久化(由 Orchestrator 负责)"""
|
|
|
|
|
|
if not self.llm:
|
2026-04-10 13:55:08 +08:00
|
|
|
|
return ["你好呀~ 又见面了。今天想从人生里哪一小段回忆开始聊聊?"]
|
2026-03-19 10:36:55 +08:00
|
|
|
|
try:
|
2026-04-08 21:36:12 +08:00
|
|
|
|
empty_slots = memoir_state.prompt_empty_slots_for_current_stage()
|
2026-03-19 10:36:55 +08:00
|
|
|
|
empty_slots_readable = [SLOT_NAME_MAP.get(s, s) for s in empty_slots]
|
2026-03-31 23:55:26 +08:00
|
|
|
|
persona = normalize_interview_persona(settings.chat_interview_persona)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
prompt = get_opening_prompt(
|
|
|
|
|
|
current_stage=memoir_state.current_stage,
|
|
|
|
|
|
empty_slots_readable=empty_slots_readable,
|
|
|
|
|
|
user_profile_context=user_profile_context,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
persona=persona,
|
|
|
|
|
|
background_voice=background_voice,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation=occupation,
|
2026-04-08 17:10:09 +08:00
|
|
|
|
profile_birth_year=profile_birth_year,
|
|
|
|
|
|
profile_era_place=profile_era_place,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
2026-04-02 12:00:00 +08:00
|
|
|
|
hw = await get_history_with_window(
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
max_pairs=settings.chat_history_max_pairs,
|
|
|
|
|
|
max_chars=settings.chat_history_max_chars,
|
|
|
|
|
|
)
|
|
|
|
|
|
messages: List[Any] = [SystemMessage(content=prompt)]
|
|
|
|
|
|
messages.extend(hw.window)
|
|
|
|
|
|
if not hw.window:
|
|
|
|
|
|
messages.append(
|
|
|
|
|
|
HumanMessage(content="(对话刚开始,请自然地说出你的开场白。)")
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
messages.append(
|
|
|
|
|
|
HumanMessage(content="(请根据上文,自然接续并说出你的开场白。)")
|
|
|
|
|
|
)
|
|
|
|
|
|
log_agent_payload(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
"InterviewAgent.opening.prompt",
|
2026-04-03 13:49:24 +08:00
|
|
|
|
format_history_string(
|
|
|
|
|
|
messages,
|
|
|
|
|
|
omit_system_body=settings.agent_log_omit_system_message_body,
|
|
|
|
|
|
),
|
2026-04-02 12:00:00 +08:00
|
|
|
|
)
|
2026-04-09 15:32:35 +08:00
|
|
|
|
opening_llm = self.llm.bind(
|
|
|
|
|
|
max_tokens=settings.chat_opening_max_tokens,
|
|
|
|
|
|
temperature=float(settings.chat_interview_temperature),
|
|
|
|
|
|
)
|
2026-04-02 12:00:00 +08:00
|
|
|
|
prompt_chars = _message_contents_char_count(messages)
|
|
|
|
|
|
llm_t0 = time.perf_counter()
|
2026-03-26 12:13:36 +08:00
|
|
|
|
with agent_span(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
"InterviewAgent.opening.llm",
|
|
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
):
|
2026-04-02 12:00:00 +08:00
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=chat_prompt_built agent=InterviewAgent.generate_opening_message "
|
|
|
|
|
|
"prompt_chars={} history_pairs_total={} history_pairs_windowed={}",
|
|
|
|
|
|
prompt_chars,
|
|
|
|
|
|
hw.turn_total,
|
|
|
|
|
|
len(hw.window) // 2,
|
|
|
|
|
|
)
|
|
|
|
|
|
response = await opening_llm.ainvoke(messages)
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=chat_llm_done agent=InterviewAgent.generate_opening_message "
|
|
|
|
|
|
"response_latency_ms={:.2f}",
|
|
|
|
|
|
(time.perf_counter() - llm_t0) * 1000,
|
|
|
|
|
|
)
|
2026-03-19 14:36:14 +08:00
|
|
|
|
response_text = (
|
|
|
|
|
|
response.content if hasattr(response, "content") else str(response)
|
|
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
log_agent_payload(
|
|
|
|
|
|
logger, "InterviewAgent.opening.raw_response", response_text
|
|
|
|
|
|
)
|
2026-03-27 16:01:28 +08:00
|
|
|
|
raw_list = segments_from_llm_response(response_text, max_segments=2)
|
|
|
|
|
|
if not raw_list:
|
|
|
|
|
|
raw_list = [response_text.strip()]
|
2026-04-06 22:22:50 +08:00
|
|
|
|
max_chars = int(settings.chat_interview_max_chars_per_segment)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
out = truncate_chat_segments(
|
|
|
|
|
|
raw_list,
|
|
|
|
|
|
max_segments=2,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
max_chars_per_segment=max_chars,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
)
|
|
|
|
|
|
log_agent_summary(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
"InterviewAgent.opening segments={} conversation_id={}",
|
|
|
|
|
|
len(out),
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
)
|
2026-04-06 22:22:50 +08:00
|
|
|
|
segments = out if out else [response_text.strip()[:max_chars]]
|
2026-03-27 16:01:28 +08:00
|
|
|
|
return nonempty_segments_or_fallback(
|
|
|
|
|
|
segments,
|
2026-04-10 13:55:08 +08:00
|
|
|
|
fallback="你好呀~ 又见面了。今天想从人生里哪一小段回忆开始聊聊?",
|
2026-03-27 16:01:28 +08:00
|
|
|
|
)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
except Exception as e:
|
2026-03-26 12:13:36 +08:00
|
|
|
|
logger.error("生成开场白失败: {}", e, exc_info=True)
|
2026-04-10 13:55:08 +08:00
|
|
|
|
return ["你好呀~ 又见面了。今天想从人生里哪一小段回忆开始聊聊?"]
|