feat(api): 回忆录管线简化、路由延迟池与相关加固
- Phase1/2:移除 MemoirOrchestrator.run 与 process_memoir_segments 别名;文档改为 process_memoir_phase1。 - 槽位校验集中到 stage_constants(filter_stage_slots),批处理与顺序路径及 state_service 写库一致。 - StoryRoute:no_llm/parse_error/invalid_target 保守 new_story;短篇护栏不覆盖这些 fallback。 - Phase2 低置信单路径可选延迟(StoryPipelineResult.deferred):不写 Chapter/Story,Segment 记录 defer 元数据,冷却内不重复消费;上限后停自动重试,Phase1 同类目新段唤醒池内段。 - Alembic 0017:segments 表 narrative_defer_* 列。 - ProfileAgent:经 LlmGateway/注入 Provider 统一聊天与 JSON,新增测试。 - ImagePromptOrchestrator:LLM 初始化失败可依配置降级或硬失败;补充策略测试。 - 配套单测与 README/本地开发文档表述更新。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,7 +22,6 @@ from app.agents.chat.reply_limits import (
|
||||
from app.agents.chat.schemas import ProfileExtractionOutput
|
||||
from app.core.agent_logging import agent_span, log_agent_payload, log_agent_summary
|
||||
from app.core.config import settings
|
||||
from app.core.dependencies import get_llm_provider
|
||||
from app.core.llm_call import allm_json_call
|
||||
from app.core.llm_gateway import LlmGateway, LlmUseCase
|
||||
from app.core.logging import get_logger
|
||||
@@ -31,11 +30,53 @@ from app.ports.llm import LLMProvider
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def _get_langchain_llm():
|
||||
try:
|
||||
return LlmGateway().langchain_llm_for(LlmUseCase("chat.profile"))
|
||||
except Exception:
|
||||
return None
|
||||
class _ProviderBackedProfileGateway:
|
||||
def __init__(self, provider: LLMProvider) -> None:
|
||||
self._provider = provider
|
||||
|
||||
async def chat_text(
|
||||
self,
|
||||
messages: list[dict],
|
||||
*,
|
||||
use_case: LlmUseCase | None = None,
|
||||
temperature: float | None = None,
|
||||
model: str | None = None,
|
||||
max_tokens: int | None = None,
|
||||
) -> str:
|
||||
resolved_temperature = temperature
|
||||
if resolved_temperature is None:
|
||||
resolved_temperature = (
|
||||
use_case.temperature
|
||||
if use_case and use_case.temperature is not None
|
||||
else 0.7
|
||||
)
|
||||
return await self._provider.complete(
|
||||
messages,
|
||||
temperature=resolved_temperature,
|
||||
model=model if model is not None else (use_case.model if use_case else None),
|
||||
max_tokens=(
|
||||
max_tokens
|
||||
if max_tokens is not None
|
||||
else (use_case.max_tokens if use_case else None)
|
||||
),
|
||||
)
|
||||
|
||||
async def json_object(
|
||||
self,
|
||||
prompt: str,
|
||||
schema: type[ProfileExtractionOutput],
|
||||
*,
|
||||
use_case: LlmUseCase,
|
||||
fallback_factory: Any = None,
|
||||
) -> ProfileExtractionOutput:
|
||||
return await allm_json_call(
|
||||
getattr(self._provider, "langchain_llm", None),
|
||||
prompt,
|
||||
schema,
|
||||
max_tokens=use_case.max_tokens or 1024,
|
||||
agent=use_case.name,
|
||||
fallback_factory=fallback_factory,
|
||||
)
|
||||
|
||||
|
||||
def _langchain_messages_to_port(messages: List[Any]) -> list[dict]:
|
||||
@@ -66,14 +107,17 @@ def _message_contents_char_count(messages: List[Any]) -> int:
|
||||
class ProfileAgent:
|
||||
"""用户资料收集 Specialist Agent"""
|
||||
|
||||
def __init__(self, llm_provider: LLMProvider | None = None):
|
||||
self._llm_provider = llm_provider
|
||||
self.llm = _get_langchain_llm()
|
||||
|
||||
def _provider(self) -> LLMProvider:
|
||||
if self._llm_provider is not None:
|
||||
return self._llm_provider
|
||||
return get_llm_provider()
|
||||
def __init__(
|
||||
self,
|
||||
llm_provider: LLMProvider | None = None,
|
||||
llm_gateway: Any | None = None,
|
||||
) -> None:
|
||||
if llm_gateway is not None:
|
||||
self._llm_gateway = llm_gateway
|
||||
elif llm_provider is not None:
|
||||
self._llm_gateway = _ProviderBackedProfileGateway(llm_provider)
|
||||
else:
|
||||
self._llm_gateway = LlmGateway()
|
||||
|
||||
async def _invoke_chat(
|
||||
self,
|
||||
@@ -88,8 +132,9 @@ class ProfileAgent:
|
||||
with agent_span(
|
||||
logger, f"{agent_name}.llm", conversation_id=conversation_id or ""
|
||||
):
|
||||
response_text = await self._provider().complete(
|
||||
response_text = await self._llm_gateway.chat_text(
|
||||
port_messages,
|
||||
use_case=LlmUseCase("chat.profile", max_tokens=max_tokens),
|
||||
max_tokens=max_tokens,
|
||||
)
|
||||
logger.info(
|
||||
@@ -130,7 +175,7 @@ class ProfileAgent:
|
||||
conversation_id: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""从用户消息中提取资料字段,不持久化"""
|
||||
if not self.llm or not missing_fields:
|
||||
if not missing_fields:
|
||||
return {}
|
||||
recent_dialogue = ""
|
||||
if conversation_id:
|
||||
@@ -151,12 +196,13 @@ class ProfileAgent:
|
||||
prompt = get_profile_extraction_prompt(
|
||||
user_message, missing_fields, recent_dialogue=recent_dialogue or None
|
||||
)
|
||||
parsed = await allm_json_call(
|
||||
self.llm,
|
||||
parsed = await self._llm_gateway.json_object(
|
||||
prompt,
|
||||
ProfileExtractionOutput,
|
||||
max_tokens=settings.chat_profile_extract_max_tokens,
|
||||
agent="ProfileAgent.extract_profile_from_message",
|
||||
use_case=LlmUseCase(
|
||||
"ProfileAgent.extract_profile_from_message",
|
||||
max_tokens=settings.chat_profile_extract_max_tokens,
|
||||
),
|
||||
fallback_factory=lambda: ProfileExtractionOutput(),
|
||||
)
|
||||
result = {}
|
||||
@@ -197,8 +243,6 @@ class ProfileAgent:
|
||||
interview_stage_hint: str = "",
|
||||
) -> List[str]:
|
||||
"""生成资料追问回复,不持久化(由 Orchestrator 负责)"""
|
||||
if not self.llm:
|
||||
return ["谢谢!还能告诉我更多吗?"]
|
||||
try:
|
||||
prompt = get_profile_followup_prompt(
|
||||
missing_fields,
|
||||
@@ -260,8 +304,6 @@ class ProfileAgent:
|
||||
nickname: str = "",
|
||||
) -> List[str]:
|
||||
"""生成资料收集开场白,不持久化(由 Orchestrator 负责)"""
|
||||
if not self.llm:
|
||||
return ["你好!在开始之前,能告诉我你是哪一年出生的吗?"]
|
||||
try:
|
||||
prompt = get_profile_greeting_prompt(missing_fields, nickname)
|
||||
hw = await get_history_with_window(
|
||||
|
||||
Reference in New Issue
Block a user