refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
@@ -48,6 +48,9 @@ from app.core.config import settings
|
||||
from app.core.llm_gateway import LlmGateway, LlmUseCase
|
||||
from app.core.logging import get_logger
|
||||
from app.features.conversation.input_normalize import normalize_chat_input_for_agent
|
||||
from app.core.runtime_constants import agent_log_defaults
|
||||
from app.features.conversation.constants import chat
|
||||
from app.features.story.constants import story
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -171,8 +174,8 @@ class InterviewAgent:
|
||||
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"
|
||||
if chat.input_normalize_enabled and (
|
||||
(chat.input_normalize_mode or "").strip().lower() == "llm"
|
||||
):
|
||||
llm_n = self.llm
|
||||
return normalize_chat_input_for_agent(user_message or "", llm=llm_n)
|
||||
@@ -218,16 +221,16 @@ class InterviewAgent:
|
||||
du = self._detect_user_stage(text_for_model)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
recent_questions = extract_recent_questions(hw.window)
|
||||
conversation_turn_total = hw.turn_total
|
||||
all_stages_coverage = narrative_state.all_stages_coverage()
|
||||
persona = normalize_interview_persona(settings.chat_interview_persona)
|
||||
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)
|
||||
persona = normalize_interview_persona(chat.interview_persona)
|
||||
max_segments = int(chat.interview_max_segments)
|
||||
max_tokens = int(chat.interview_max_tokens)
|
||||
max_chars = int(chat.interview_max_chars_per_segment)
|
||||
|
||||
turn_plan = plan_interview_turn(
|
||||
current_stage=memoir_state.current_stage,
|
||||
@@ -246,7 +249,7 @@ class InterviewAgent:
|
||||
reply_planner_raw = ""
|
||||
baseline_mode = turn_plan.mode
|
||||
baseline_primary_focus = turn_plan.primary_focus
|
||||
if settings.chat_reply_planner_llm_enabled:
|
||||
if chat.reply_planner_llm_enabled:
|
||||
rq_preview = (
|
||||
"\n".join(recent_questions[-4:])
|
||||
if recent_questions
|
||||
@@ -258,8 +261,8 @@ class InterviewAgent:
|
||||
text_for_model=text_for_model,
|
||||
memory_evidence_text=(memory_planner_text or memory_evidence_text)
|
||||
or "",
|
||||
max_tokens=int(settings.chat_reply_planner_max_tokens),
|
||||
temperature=float(settings.chat_reply_planner_temperature),
|
||||
max_tokens=int(chat.reply_planner_max_tokens),
|
||||
temperature=float(chat.reply_planner_temperature),
|
||||
scene_cues_for_planner=scene_cues_for_planner or [],
|
||||
recent_questions_preview=rq_preview,
|
||||
)
|
||||
@@ -310,12 +313,12 @@ class InterviewAgent:
|
||||
"InterviewAgent.generate_response.prompt",
|
||||
format_history_string(
|
||||
messages,
|
||||
omit_system_body=settings.agent_log_omit_system_message_body,
|
||||
omit_system_body=agent_log_defaults.omit_system_message_body,
|
||||
),
|
||||
)
|
||||
chat_llm = self.llm.bind(
|
||||
max_tokens=max_tokens,
|
||||
temperature=float(settings.chat_interview_temperature),
|
||||
temperature=float(chat.interview_temperature),
|
||||
)
|
||||
prompt_chars = _message_contents_char_count(messages)
|
||||
llm_t0 = time.perf_counter()
|
||||
@@ -377,7 +380,7 @@ class InterviewAgent:
|
||||
"InterviewAgent.generate_response.retry_prompt",
|
||||
format_history_string(
|
||||
retry_messages,
|
||||
omit_system_body=settings.agent_log_omit_system_message_body,
|
||||
omit_system_body=agent_log_defaults.omit_system_message_body,
|
||||
),
|
||||
)
|
||||
llm_t1 = time.perf_counter()
|
||||
@@ -445,7 +448,7 @@ class InterviewAgent:
|
||||
"duplicate_question_guard_llm_retry": retry_used,
|
||||
"autobiographical_boundary_guard_triggered": auto_bio,
|
||||
"reply_planner_llm_used": bool(
|
||||
settings.chat_reply_planner_llm_enabled
|
||||
chat.reply_planner_llm_enabled
|
||||
and (reply_planner_raw or "").strip()
|
||||
),
|
||||
"reply_planner_raw_preview": (reply_planner_raw or "")[:800],
|
||||
@@ -483,7 +486,7 @@ class InterviewAgent:
|
||||
)
|
||||
slot_table = SLOT_NAME_MAP_EN if language == "en" else SLOT_NAME_MAP
|
||||
empty_slots_readable = [slot_table.get(s, s) for s in empty_slots]
|
||||
persona = normalize_interview_persona(settings.chat_interview_persona)
|
||||
persona = normalize_interview_persona(chat.interview_persona)
|
||||
prompt = get_opening_prompt(
|
||||
current_stage=memoir_state.current_stage,
|
||||
empty_slots_readable=empty_slots_readable,
|
||||
@@ -497,8 +500,8 @@ class InterviewAgent:
|
||||
)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
messages: List[Any] = [SystemMessage(content=prompt)]
|
||||
messages.extend(hw.window)
|
||||
@@ -520,12 +523,12 @@ class InterviewAgent:
|
||||
"InterviewAgent.opening.prompt",
|
||||
format_history_string(
|
||||
messages,
|
||||
omit_system_body=settings.agent_log_omit_system_message_body,
|
||||
omit_system_body=agent_log_defaults.omit_system_message_body,
|
||||
),
|
||||
)
|
||||
opening_llm = self.llm.bind(
|
||||
max_tokens=settings.chat_opening_max_tokens,
|
||||
temperature=float(settings.chat_interview_temperature),
|
||||
max_tokens=chat.opening_max_tokens,
|
||||
temperature=float(chat.interview_temperature),
|
||||
)
|
||||
prompt_chars = _message_contents_char_count(messages)
|
||||
llm_t0 = time.perf_counter()
|
||||
@@ -564,7 +567,7 @@ class InterviewAgent:
|
||||
raw_list = segments_from_llm_response(response_text, max_segments=2)
|
||||
if not raw_list:
|
||||
raw_list = [response_text.strip()]
|
||||
max_chars = int(settings.chat_interview_max_chars_per_segment)
|
||||
max_chars = int(chat.interview_max_chars_per_segment)
|
||||
out = truncate_chat_segments(
|
||||
raw_list,
|
||||
max_segments=2,
|
||||
@@ -612,7 +615,7 @@ class InterviewAgent:
|
||||
)
|
||||
slot_table = SLOT_NAME_MAP_EN if language == "en" else SLOT_NAME_MAP
|
||||
empty_slots_readable = [slot_table.get(s, s) for s in empty_slots]
|
||||
persona = normalize_interview_persona(settings.chat_interview_persona)
|
||||
persona = normalize_interview_persona(chat.interview_persona)
|
||||
prompt = get_re_greeting_prompt(
|
||||
current_stage=memoir_state.current_stage,
|
||||
empty_slots_readable=empty_slots_readable,
|
||||
@@ -627,8 +630,8 @@ class InterviewAgent:
|
||||
)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
messages: List[Any] = [SystemMessage(content=prompt)]
|
||||
messages.extend(hw.window)
|
||||
@@ -647,12 +650,12 @@ class InterviewAgent:
|
||||
"InterviewAgent.re_greeting.prompt",
|
||||
format_history_string(
|
||||
messages,
|
||||
omit_system_body=settings.agent_log_omit_system_message_body,
|
||||
omit_system_body=agent_log_defaults.omit_system_message_body,
|
||||
),
|
||||
)
|
||||
re_greet_llm = self.llm.bind(
|
||||
max_tokens=settings.chat_opening_max_tokens,
|
||||
temperature=float(settings.chat_interview_temperature),
|
||||
max_tokens=chat.opening_max_tokens,
|
||||
temperature=float(chat.interview_temperature),
|
||||
)
|
||||
llm_t0 = time.perf_counter()
|
||||
with agent_span(
|
||||
@@ -691,7 +694,7 @@ class InterviewAgent:
|
||||
raw_list = segments_from_llm_response(response_text, max_segments=2)
|
||||
if not raw_list:
|
||||
raw_list = [response_text.strip()]
|
||||
max_chars = int(settings.chat_interview_max_chars_per_segment)
|
||||
max_chars = int(chat.interview_max_chars_per_segment)
|
||||
out = truncate_chat_segments(
|
||||
raw_list,
|
||||
max_segments=2,
|
||||
|
||||
@@ -35,6 +35,9 @@ from app.features.memoir.state_service import (
|
||||
switch_stage,
|
||||
)
|
||||
from app.features.memory.prompt_adapter import MemoryPromptAdapter
|
||||
from app.features.conversation.constants import chat
|
||||
from app.features.memory.constants import memory
|
||||
from app.features.story.constants import story
|
||||
|
||||
|
||||
def _llm_for_chat_input_normalize():
|
||||
@@ -80,7 +83,7 @@ async def _fetch_interview_memory_bundle(
|
||||
)
|
||||
from app.features.memory.service import MemoryService
|
||||
|
||||
if not settings.chat_memory_retrieval_enabled:
|
||||
if not chat.memory_retrieval_enabled:
|
||||
logger.debug(
|
||||
"event=chat_memory_retrieval_skip reason=disabled user_id={}", user_id
|
||||
)
|
||||
@@ -94,7 +97,7 @@ async def _fetch_interview_memory_bundle(
|
||||
try:
|
||||
emb = get_embedding_provider_fn()
|
||||
ms = MemoryService(db, embedding_provider=emb)
|
||||
top_k = settings.chat_memory_top_k
|
||||
top_k = chat.memory_top_k
|
||||
bundle = await ms.retrieve(user_id, msg, top_k=top_k)
|
||||
bd = bundle.model_dump()
|
||||
trace = chat_memory_retrieval_trace_from_bundle(
|
||||
@@ -164,17 +167,17 @@ class ChatOrchestrator:
|
||||
if missing:
|
||||
hw_profile = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
profile_turn_total = hw_profile.turn_total
|
||||
if profile_turn_total >= settings.chat_profile_max_turns:
|
||||
if profile_turn_total >= 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,
|
||||
chat.profile_max_turns,
|
||||
missing,
|
||||
)
|
||||
else:
|
||||
@@ -269,8 +272,8 @@ class ChatOrchestrator:
|
||||
len(user_message or ""),
|
||||
)
|
||||
llm_n = None
|
||||
if settings.chat_input_normalize_enabled and (
|
||||
(settings.chat_input_normalize_mode or "").strip().lower() == "llm"
|
||||
if chat.input_normalize_enabled and (
|
||||
(chat.input_normalize_mode or "").strip().lower() == "llm"
|
||||
):
|
||||
llm_n = _llm_for_chat_input_normalize()
|
||||
normalized_user_message = normalize_chat_input_for_agent(
|
||||
@@ -290,8 +293,10 @@ class ChatOrchestrator:
|
||||
state = await switch_stage(user_id, detected, db)
|
||||
|
||||
if conversation and conversation.conversation_stage != state.current_stage:
|
||||
conversation.conversation_stage = state.current_stage
|
||||
await db.commit()
|
||||
from app.core.db import transactional
|
||||
|
||||
async with transactional(db):
|
||||
conversation.conversation_stage = state.current_stage
|
||||
|
||||
from app.agents.chat.background_voice import infer_background_voice
|
||||
from app.agents.chat.prompts_profile import format_user_profile_context
|
||||
|
||||
@@ -7,6 +7,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from app.features.conversation.constants import chat
|
||||
|
||||
# Brand / interviewer name — keep aligned with frontend i18n `conversation.agentName`,
|
||||
# OpenAPI title, README, and project metadata. zh = 「岁月知己」,en = Life Echo.
|
||||
AGENT_NAME_ZH: Final[str] = "岁月知己"
|
||||
@@ -18,7 +20,7 @@ def agent_name(language: str = "zh") -> str:
|
||||
return AGENT_NAME_EN if (language or "zh").strip().lower() == "en" else AGENT_NAME_ZH
|
||||
|
||||
|
||||
# 与 settings.chat_interview_persona 及文档保持一致
|
||||
# 与 chat.interview_persona 及文档保持一致
|
||||
VALID_INTERVIEW_PERSONAS: Final[frozenset[str]] = frozenset(
|
||||
{"default", "warm_listener", "curious_guide"}
|
||||
)
|
||||
|
||||
@@ -26,6 +26,9 @@ from app.core.llm_call import allm_json_call
|
||||
from app.core.llm_gateway import LlmGateway, LlmUseCase
|
||||
from app.core.logging import get_logger
|
||||
from app.ports.llm import LLMProvider
|
||||
from app.core.runtime_constants import agent_log_defaults
|
||||
from app.features.conversation.constants import chat
|
||||
from app.features.story.constants import story
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -207,8 +210,8 @@ class ProfileAgent:
|
||||
if conversation_id:
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
recent = hw.window[-4:] if len(hw.window) > 4 else hw.window
|
||||
parts = []
|
||||
@@ -232,7 +235,7 @@ class ProfileAgent:
|
||||
ProfileExtractionOutput,
|
||||
use_case=LlmUseCase(
|
||||
"ProfileAgent.extract_profile_from_message",
|
||||
max_tokens=settings.chat_profile_extract_max_tokens,
|
||||
max_tokens=chat.profile_extract_max_tokens,
|
||||
),
|
||||
fallback_factory=lambda: ProfileExtractionOutput(),
|
||||
)
|
||||
@@ -285,8 +288,8 @@ class ProfileAgent:
|
||||
)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
messages: List[Any] = [SystemMessage(content=prompt)]
|
||||
messages.extend(hw.window)
|
||||
@@ -296,7 +299,7 @@ class ProfileAgent:
|
||||
"ProfileAgent.followup.prompt",
|
||||
format_history_string(
|
||||
messages,
|
||||
omit_system_body=settings.agent_log_omit_system_message_body,
|
||||
omit_system_body=agent_log_defaults.omit_system_message_body,
|
||||
),
|
||||
)
|
||||
prompt_chars = _message_contents_char_count(messages)
|
||||
@@ -309,14 +312,14 @@ class ProfileAgent:
|
||||
)
|
||||
response_text = await self._invoke_chat(
|
||||
messages,
|
||||
max_tokens=settings.chat_profile_followup_max_tokens,
|
||||
max_tokens=chat.profile_followup_max_tokens,
|
||||
conversation_id=conversation_id,
|
||||
agent_name="ProfileAgent.generate_profile_followup",
|
||||
)
|
||||
segments = await self._segments_from_response(
|
||||
response_text,
|
||||
max_segments=3,
|
||||
max_chars_per_segment=settings.chat_interview_max_chars_per_segment,
|
||||
max_chars_per_segment=chat.interview_max_chars_per_segment,
|
||||
fallback=_profile_followup_fallback(language),
|
||||
)
|
||||
log_agent_summary(
|
||||
@@ -344,8 +347,8 @@ class ProfileAgent:
|
||||
)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
max_chars=settings.chat_history_max_chars,
|
||||
max_pairs=chat.history_max_pairs,
|
||||
max_chars=chat.history_max_chars,
|
||||
)
|
||||
messages: List[Any] = [SystemMessage(content=prompt)]
|
||||
messages.extend(hw.window)
|
||||
@@ -367,7 +370,7 @@ class ProfileAgent:
|
||||
"ProfileAgent.greeting.prompt",
|
||||
format_history_string(
|
||||
messages,
|
||||
omit_system_body=settings.agent_log_omit_system_message_body,
|
||||
omit_system_body=agent_log_defaults.omit_system_message_body,
|
||||
),
|
||||
)
|
||||
prompt_chars = _message_contents_char_count(messages)
|
||||
@@ -380,14 +383,14 @@ class ProfileAgent:
|
||||
)
|
||||
response_text = await self._invoke_chat(
|
||||
messages,
|
||||
max_tokens=settings.chat_profile_followup_max_tokens,
|
||||
max_tokens=chat.profile_followup_max_tokens,
|
||||
conversation_id=conversation_id,
|
||||
agent_name="ProfileAgent.generate_profile_greeting",
|
||||
)
|
||||
segments = await self._segments_from_response(
|
||||
response_text,
|
||||
max_segments=2,
|
||||
max_chars_per_segment=settings.chat_interview_max_chars_per_segment,
|
||||
max_chars_per_segment=chat.interview_max_chars_per_segment,
|
||||
fallback=_profile_greeting_fallback(language),
|
||||
)
|
||||
log_agent_summary(
|
||||
|
||||
@@ -34,6 +34,7 @@ from app.agents.stage_constants import (
|
||||
)
|
||||
from app.agents.state_schema import KnownFact, PersonaThread
|
||||
from app.core.config import settings
|
||||
from app.features.conversation.constants import chat
|
||||
|
||||
# 风格示例的单一事实源已迁至 `app.agents.style_profiles.ChatStyleProfile.reply_style_examples`;
|
||||
# 这里**不再**维护具体字面示例,避免同一模块被当作 few-shot 锚点反复注入,导致风格过拟合。
|
||||
@@ -292,7 +293,7 @@ def get_opening_prompt(
|
||||
|
||||
era_opening_line = ""
|
||||
if (
|
||||
settings.chat_era_context_enabled
|
||||
chat.era_context_enabled
|
||||
and profile_birth_year is not None
|
||||
and _compact_era_hint(
|
||||
current_stage,
|
||||
@@ -450,7 +451,7 @@ def get_guided_conversation_prompt(
|
||||
)
|
||||
|
||||
era_line = ""
|
||||
if settings.chat_era_context_enabled:
|
||||
if chat.era_context_enabled:
|
||||
era_line = _compact_era_hint(
|
||||
active_stage,
|
||||
birth_year=profile_birth_year,
|
||||
@@ -696,7 +697,7 @@ _STAGE_TOPIC_CHIP_BANK: Dict[str, List[tuple[str, str]]] = {
|
||||
("support", "家人之间的相互支持"),
|
||||
("responsibility", "肩上的家庭责任"),
|
||||
],
|
||||
"later_life": [
|
||||
"belief": [
|
||||
("value", "现在最看重的事"),
|
||||
("regret", "心里的遗憾"),
|
||||
("pride", "最骄傲的事"),
|
||||
|
||||
@@ -20,6 +20,7 @@ from app.agents.stage_constants import (
|
||||
from app.core.config import settings
|
||||
from app.core.llm_call import allm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.conversation.constants import chat
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -59,7 +60,7 @@ async def detect_primary_life_stage(
|
||||
每轮在启用时调用阶段检测 LLM(短句亦由模型判断,不用关键词替代)。
|
||||
"""
|
||||
fb = normalize_chat_stage(current_stage, "childhood")
|
||||
if not settings.chat_stage_detection_enabled:
|
||||
if not chat.stage_detection_enabled:
|
||||
return _keyword_fallback_stage(user_message, fb)
|
||||
|
||||
if not llm:
|
||||
@@ -76,7 +77,7 @@ async def detect_primary_life_stage(
|
||||
llm,
|
||||
prompt,
|
||||
StageDetectionOutput,
|
||||
max_tokens=settings.chat_stage_detection_max_tokens,
|
||||
max_tokens=chat.stage_detection_max_tokens,
|
||||
agent="detect_primary_life_stage",
|
||||
fallback_factory=fallback_factory,
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ from app.agents.image_prompt.prompt_agent import PromptGenerationAgent
|
||||
from app.core.config import settings
|
||||
from app.core.logging import get_logger
|
||||
from app.features.memoir.memoir_images.settings import MemoirImageSettings
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -84,7 +85,7 @@ def get_image_prompt_orchestrator() -> ImagePromptOrchestrator:
|
||||
try:
|
||||
llm = LlmGateway().langchain_llm_for(LlmUseCase("image_prompt"))
|
||||
except Exception as e:
|
||||
if settings.image_prompt_fallback_disabled:
|
||||
if memoir.image_prompt_fallback_disabled:
|
||||
raise
|
||||
logger.warning(
|
||||
"ImagePromptOrchestrator LLM 初始化失败,使用确定性 fallback: {}",
|
||||
|
||||
@@ -15,6 +15,7 @@ from app.core.config import settings
|
||||
from app.core.llm_call import LLMCallError, llm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.conversation.models import Segment
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -68,7 +69,7 @@ def run_batch_phase1_prep(
|
||||
llm,
|
||||
prompt,
|
||||
BatchPhase1LLMOutput,
|
||||
max_tokens=int(settings.memoir_phase1_batch_llm_max_tokens),
|
||||
max_tokens=int(memoir.phase1_batch_llm_max_tokens),
|
||||
agent="BatchPhase1Prep.run",
|
||||
)
|
||||
except LLMCallError as e:
|
||||
|
||||
@@ -26,6 +26,7 @@ from app.core.config import settings
|
||||
from app.core.json_utils import extract_json_payload
|
||||
from app.core.llm_call import LLMCallError, llm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -144,7 +145,7 @@ class ClassificationAgent:
|
||||
llm,
|
||||
prompt,
|
||||
ClassificationOutput,
|
||||
max_tokens=settings.memoir_classification_max_tokens,
|
||||
max_tokens=memoir.classification_max_tokens,
|
||||
agent="ClassificationAgent.classify",
|
||||
)
|
||||
category = _normalize_llm_category(out.category)
|
||||
|
||||
@@ -14,6 +14,7 @@ from app.agents.stage_constants import normalize_chat_stage
|
||||
from app.core.config import settings
|
||||
from app.core.llm_call import LLMCallError, llm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -64,7 +65,7 @@ class ExtractionAgent:
|
||||
llm,
|
||||
prompt,
|
||||
StateExtractionOutput,
|
||||
max_tokens=settings.memoir_extraction_max_tokens,
|
||||
max_tokens=memoir.extraction_max_tokens,
|
||||
agent="ExtractionAgent.extract",
|
||||
)
|
||||
raw_slots = parsed.slots or {}
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.agents.memoir.schemas import FidelityOutput
|
||||
from app.core.config import settings
|
||||
from app.core.llm_call import LLMCallError, llm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -46,7 +47,7 @@ class FidelityCheckAgent:
|
||||
existing_canonical_markdown: str | None = None,
|
||||
is_append: bool = False,
|
||||
) -> bool:
|
||||
if not llm or not settings.memoir_fidelity_check_enabled:
|
||||
if not llm or not memoir.fidelity_check_enabled:
|
||||
return True
|
||||
oral = (oral_text or "").strip()
|
||||
gen = (narrative_json or "").strip()
|
||||
@@ -108,7 +109,7 @@ class FidelityCheckAgent:
|
||||
llm,
|
||||
prompt,
|
||||
FidelityOutput,
|
||||
max_tokens=settings.memoir_fidelity_check_max_tokens,
|
||||
max_tokens=memoir.fidelity_check_max_tokens,
|
||||
agent="FidelityCheckAgent.passes",
|
||||
)
|
||||
ok = bool(out.pass_)
|
||||
@@ -120,7 +121,7 @@ class FidelityCheckAgent:
|
||||
return ok
|
||||
except LLMCallError as e:
|
||||
logger.warning("FidelityCheckAgent 解析失败: {}", e)
|
||||
if is_append or settings.memoir_fidelity_fail_open_on_parse_error:
|
||||
if is_append or memoir.fidelity_fail_open_on_parse_error:
|
||||
logger.info("event=fidelity_parse_fail_open is_append={}", is_append)
|
||||
return True
|
||||
logger.warning("event=fidelity_parse_fail_closed")
|
||||
|
||||
@@ -18,6 +18,7 @@ from app.core.config import settings
|
||||
from app.core.langchain_llm import invoke_json_object
|
||||
from app.core.llm_call import llm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -63,7 +64,7 @@ class NarrativeAgent:
|
||||
llm,
|
||||
prompt,
|
||||
MemoirTitleOutput,
|
||||
max_tokens=settings.memoir_title_max_tokens,
|
||||
max_tokens=memoir.title_max_tokens,
|
||||
agent="NarrativeAgent.generate_title",
|
||||
fallback_factory=_title_fallback,
|
||||
)
|
||||
@@ -118,7 +119,7 @@ class NarrativeAgent:
|
||||
occupation=occupation,
|
||||
language=language,
|
||||
)
|
||||
max_tokens = int(settings.memoir_narrative_merge_max_tokens)
|
||||
max_tokens = int(memoir.narrative_merge_max_tokens)
|
||||
agent_name = "NarrativeAgent.generate_narrative_merge"
|
||||
else:
|
||||
prompt = get_narrative_json_prompt(
|
||||
@@ -132,7 +133,7 @@ class NarrativeAgent:
|
||||
occupation=occupation,
|
||||
language=language,
|
||||
)
|
||||
max_tokens = int(settings.memoir_narrative_max_tokens)
|
||||
max_tokens = int(memoir.narrative_max_tokens)
|
||||
agent_name = "NarrativeAgent.generate_narrative"
|
||||
return invoke_json_object(
|
||||
llm,
|
||||
|
||||
@@ -29,6 +29,7 @@ from app.core.agent_logging import agent_span, agent_summary_enabled, log_agent_
|
||||
from app.core.config import settings
|
||||
from app.core.logging import get_logger
|
||||
from app.features.conversation.models import Segment
|
||||
from app.features.memoir.constants import memoir
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -90,7 +91,7 @@ class MemoirOrchestrator:
|
||||
use_batch = (
|
||||
bool(segments)
|
||||
and classify_extract_llm is not None
|
||||
and settings.memoir_phase1_batch_llm_enabled
|
||||
and memoir.phase1_batch_llm_enabled
|
||||
)
|
||||
if use_batch:
|
||||
try:
|
||||
@@ -204,7 +205,7 @@ class MemoirOrchestrator:
|
||||
segments,
|
||||
state,
|
||||
classify_extract_llm,
|
||||
chunk_size=int(settings.memoir_phase1_batch_llm_chunk_size),
|
||||
chunk_size=int(memoir.phase1_batch_llm_chunk_size),
|
||||
on_chunk=on_phase1_chunk,
|
||||
language=language,
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ from app.core.config import settings
|
||||
from app.core.llm_call import LLMCallError, llm_json_call
|
||||
from app.core.logging import get_logger
|
||||
from app.features.story.models import Story
|
||||
from app.features.memoir.constants import memoir
|
||||
from app.features.story.constants import story
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -47,7 +49,7 @@ def default_append_target_story_id(
|
||||
ordered = sort_stories_for_route(
|
||||
candidate_stories,
|
||||
meta,
|
||||
summary_min_chars=int(settings.story_route_summary_min_chars),
|
||||
summary_min_chars=int(story.route_summary_min_chars),
|
||||
)
|
||||
if not ordered:
|
||||
return None
|
||||
@@ -247,7 +249,7 @@ class StoryRouteAgent:
|
||||
llm,
|
||||
prompt,
|
||||
StoryRouteDecision,
|
||||
max_tokens=settings.memoir_story_route_max_tokens,
|
||||
max_tokens=memoir.story_route_max_tokens,
|
||||
agent="StoryRouteAgent.decide",
|
||||
fallback_factory=_decide_fallback,
|
||||
)
|
||||
@@ -295,7 +297,7 @@ class StoryRouteAgent:
|
||||
llm,
|
||||
prompt,
|
||||
StoryBatchPlan,
|
||||
max_tokens=settings.memoir_story_batch_plan_max_tokens,
|
||||
max_tokens=memoir.story_batch_plan_max_tokens,
|
||||
agent="StoryRouteAgent.plan_batch",
|
||||
)
|
||||
except LLMCallError as e:
|
||||
|
||||
@@ -15,6 +15,7 @@ if TYPE_CHECKING:
|
||||
from app.core.config import Settings
|
||||
|
||||
from app.features.story.models import Story
|
||||
from app.features.story.constants import story
|
||||
|
||||
_PLAIN_SNIPPET_NOISE = re.compile(r"[`*_#]+")
|
||||
|
||||
@@ -213,11 +214,11 @@ def build_route_candidate_rows(
|
||||
) -> list[dict[str, Any]]:
|
||||
"""排序 + 完整候选行(尚未做总预算降级)。"""
|
||||
meta = story_meta or {}
|
||||
summary_min = int(settings.story_route_summary_min_chars)
|
||||
summary_min = int(story.route_summary_min_chars)
|
||||
ordered = sort_stories_for_route(stories, meta, summary_min_chars=summary_min)
|
||||
body_max = int(settings.story_route_candidate_body_max_chars)
|
||||
head_c = int(settings.story_route_long_body_head_chars)
|
||||
tail_c = int(settings.story_route_long_body_tail_chars)
|
||||
body_max = int(story.route_candidate_body_max_chars)
|
||||
head_c = int(story.route_long_body_head_chars)
|
||||
tail_c = int(story.route_long_body_tail_chars)
|
||||
rows: list[dict[str, Any]] = []
|
||||
for s in ordered:
|
||||
rows.append(
|
||||
@@ -231,8 +232,8 @@ def build_route_candidate_rows(
|
||||
)
|
||||
)
|
||||
by_id = {str(s.id): s for s in ordered}
|
||||
total_max = int(settings.story_route_candidate_total_max_chars)
|
||||
index_prev = int(settings.story_route_index_preview_chars)
|
||||
total_max = int(story.route_candidate_total_max_chars)
|
||||
index_prev = int(story.route_index_preview_chars)
|
||||
return apply_total_budget_downgrade(
|
||||
rows,
|
||||
stories_by_id=by_id,
|
||||
|
||||
Reference in New Issue
Block a user