PMFlow/backend/app/services/llm_service/mixin_generate_db_chat.py

167 lines
7.7 KiB
Python

from __future__ import annotations
from .shared import *
class LLMGenerateDbChatMixin:
async def _generate_with_db(self, db: AsyncSession, prompt: str, **kwargs: Any) -> str:
started_at = time.perf_counter()
tenant_id = int(kwargs.get("tenant_id") or 1)
function_name = str(kwargs.get("function_name") or "llm_generate")
temperature = float(kwargs.get("temperature") or 0.2)
max_tokens = int(kwargs.get("max_tokens") or 1024)
use_prompt_profile = bool(kwargs.get("use_prompt_profile", True))
response_mode = str(kwargs.get("response_mode") or "complete").strip().lower() or "complete"
routing_meta = _normalize_routing_meta(kwargs.get("routing_meta"))
response_text = ""
status = "success"
routing_decision = "primary_engine"
engine_meta: dict[str, Any] = {}
provider_request_payload: Any = None
provider_response_payload: Any = None
provider_attempts: Any = None
prompt_profile = {"role_name": "", "system_prompt": ""}
prompt_with_context = await self._inject_model_context_bundle_prompt(
db=db,
tenant_id=tenant_id,
function_name=function_name,
prompt=prompt,
)
merged_prompt = self._merge_prompt_with_profile(
prompt=prompt_with_context,
prompt_profile=prompt_profile,
function_name=function_name,
)
def _log_stub_failure(error_obj: Exception, *, used_stub: bool = False) -> None:
elapsed_ms = (time.perf_counter() - started_at) * 1000
routing_meta = _read_engine_routing_meta(engine_meta)
outcome = "兜底 stub" if used_stub else "显式报错"
logger.warning(
"[LLM_FAIL] 生成失败 → %s function=%s route=%s error=%r duration_ms=%.0f",
outcome,
function_name,
_routing_meta_log_text(routing_meta) or "unknown",
error_obj,
elapsed_ms,
)
try:
if use_prompt_profile:
prompt_profile = await self._resolve_active_prompt_profile(db, tenant_id=tenant_id)
merged_prompt = self._merge_prompt_with_profile(
prompt=prompt_with_context,
prompt_profile=prompt_profile,
function_name=function_name,
)
engine = await self._resolve_engine_for_call(
db,
tenant_id=tenant_id,
function_name=function_name,
routing_meta=routing_meta,
)
engine_meta = engine
provider = str(engine.get("provider") or "")
model = str(engine.get("model") or "")
completion = await self._chat_completion_raw(
provider=provider,
prompt=prompt_with_context,
system_prompt=prompt_profile.get("system_prompt") or "",
api_key=str(engine.get("api_key") or ""),
base_url=str(engine.get("base_url") or ""),
model=model,
temperature=temperature,
max_tokens=max_tokens,
function_name=function_name,
response_mode=response_mode,
routing_meta=routing_meta,
)
response_text = completion.text
provider_request_payload = completion.raw_request
provider_response_payload = completion.raw_response
provider_attempts = completion.provider_attempts
if not response_text.strip():
raise ValueError("primary_engine_empty_response")
if self._call_is_invalid_structured_output(
function_name=function_name,
response_text=response_text,
routing_meta=routing_meta,
):
raise ValueError("invalid_structured_output")
return response_text
except Exception as exc:
provider_request_payload = provider_request_payload or getattr(exc, "provider_request_payload", None)
provider_response_payload = provider_response_payload or getattr(exc, "provider_response_payload", None)
provider_attempts = provider_attempts or getattr(exc, "provider_attempts", None)
failover_response, failover_engine = await self._retry_with_high_tier_after_low_model_failure(
db=db,
tenant_id=tenant_id,
function_name=function_name,
failed_engine=engine_meta,
prompt=prompt_with_context,
system_prompt=str(prompt_profile.get("system_prompt") or ""),
temperature=temperature,
max_tokens=max_tokens,
response_mode=response_mode,
error=exc,
routing_meta=routing_meta,
)
if failover_response and isinstance(failover_engine, dict):
routing_decision = "low_to_high_escalated"
status = "success_failover"
response_text = failover_response
engine_meta = failover_engine
provider_request_payload = failover_engine.get("_provider_request_payload")
provider_response_payload = failover_engine.get("_provider_response_payload")
provider_attempts = failover_engine.get("_provider_attempts")
return failover_response
status = "failed"
reason_text = self._build_model_error_message(
function_name=function_name,
routing_meta=_read_engine_routing_meta(engine_meta),
error=exc,
)
response_text = reason_text
_log_stub_failure(exc, used_stub=False)
raise RuntimeError(reason_text) from exc
finally:
try:
merged_prompt = self._merge_prompt_with_profile(
prompt=prompt_with_context,
prompt_profile=prompt_profile,
function_name=function_name,
)
await self._log_ai_call(
tenant_id=tenant_id,
function_name=function_name,
prompt=merged_prompt,
response=response_text,
status=status,
provider=str(engine_meta.get("provider") or ""),
model=str(engine_meta.get("model") or ""),
routing_meta=_merge_call_routing_meta(
caller_meta=routing_meta,
engine_meta=_read_engine_routing_meta(engine_meta),
),
provider_request_payload=provider_request_payload or engine_meta.get("_provider_request_payload"),
provider_response_payload=provider_response_payload or engine_meta.get("_provider_response_payload"),
provider_attempts=provider_attempts or engine_meta.get("_provider_attempts"),
)
routing_meta = _merge_call_routing_meta(
caller_meta=routing_meta,
engine_meta=_read_engine_routing_meta(engine_meta),
)
if (
routing_meta.get("planned_tier")
and routing_meta.get("executed_tier")
and routing_meta.get("planned_tier") != routing_meta.get("executed_tier")
):
_runtime_log(
"LLM路由",
(
f"function={function_name}, {_routing_meta_log_text(routing_meta)}"
),
)
except Exception as log_exc:
_runtime_log("LLM", f"调用日志写入失败:{log_exc}")