PMFlow/backend/app/services/llm_service/mixin_generate_db_structure...

153 lines
6.5 KiB
Python

from __future__ import annotations
from .shared import *
class LLMGenerateDbStructuredMixin:
async def _generate_structured_with_db(
self,
db: AsyncSession,
prompt: str,
**kwargs: Any,
) -> StructuredExtractionResult:
tenant_id = int(kwargs.get("tenant_id") or 1)
function_name = str(kwargs.get("function_name") or "llm_generate").strip().lower() or "llm_generate"
temperature = float(kwargs.get("temperature") or 0.0)
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"
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 = prompt_with_context
provider = ""
model = ""
engine_meta: dict[str, Any] = {}
status = "success"
response_text = ""
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,
)
engine_meta = engine
provider = str(engine.get("provider") or "")
model = str(engine.get("model") or "")
cache_key = ""
if self._structured_cache_enabled(function_name):
cache_key = self._structured_cache_key(
function_name=function_name,
model=model or "unknown",
prompt=merged_prompt,
)
cached = await self._structured_cache_get(key=cache_key)
if cached is not None:
status = "success_cache_hit"
result = StructuredExtractionResult(
payload=cached,
source_field="cache",
provider=provider,
model=model,
raw_text=json.dumps(cached, ensure_ascii=False),
validation="ok",
from_cache=True,
)
_runtime_log(
"LLM结构化",
f"function={function_name}, structured_source=cache, validation=ok, fallback=false",
)
return result
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,
)
response_text = completion.text
extraction = self._extract_structured_object_from_response(
function_name=function_name,
response=completion.raw_response,
fallback_text=completion.text,
provider=provider,
model=model,
)
_runtime_log(
"LLM结构化",
(
f"function={function_name}, structured_source={extraction.source_field or 'none'}, "
f"validation={extraction.validation}, fallback=false"
),
)
if extraction.payload is None:
raise ValueError(str(extraction.error_code or "structured_schema_invalid"))
if extraction.payload is not None and cache_key:
await self._structured_cache_set(key=cache_key, value=extraction.payload)
status = extraction.error_code or "success"
return extraction
except Exception as exc:
failover_result, failover_engine = await self._retry_structured_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,
)
if isinstance(failover_result, StructuredExtractionResult) and isinstance(failover_engine, dict):
provider = str(failover_engine.get("provider") or "")
model = str(failover_engine.get("model") or "")
response_text = failover_result.raw_text or json.dumps(failover_result.payload or {}, ensure_ascii=False)
status = failover_result.error_code or "success_failover"
return failover_result
error_code = self._classify_structured_exception(exc)
status = error_code
reason_text = self._build_model_error_message(
function_name=function_name,
provider=provider,
model=model,
error=exc,
)
response_text = reason_text
_runtime_log(
"LLM结构化",
f"function={function_name}, structured_source=none, validation=invalid, fallback=false",
)
raise RuntimeError(reason_text) from exc
finally:
try:
log_payload = response_text or status
await self._log_ai_call(
tenant_id=tenant_id,
function_name=function_name,
prompt=merged_prompt,
response=log_payload,
status=status,
provider=provider,
model=model,
)
except Exception as log_exc:
_runtime_log("LLM", f"结构化调用日志写入失败:{log_exc}")