import json from typing import Any from fastapi import HTTPException, status from app.modules.ai_agent.constants import ( COMPANY_MANAGEMENT_SYSTEM_INSTRUCTIONS, UNEXPECTED_HERMES_RESPONSE, AIChatRole, AIContextKey, AIErrorKey, AIHttpPayloadKey, AIResponseKey, ) def _error_detail(exc: Exception) -> Any: if isinstance(exc, HTTPException): return exc.detail return {AIResponseKey.TYPE: type(exc).__name__, AIResponseKey.MESSAGE: str(exc)} def _response_payload(response: Any) -> dict[str, Any]: try: data = response.json() except ValueError: data = {AIResponseKey.TEXT: response.text} return {AIResponseKey.STATUS_CODE: response.status_code, AIResponseKey.DATA: data} def _chat_completion_payload(response: Any, error_key: AIErrorKey) -> dict[str, Any]: try: return response.json() except ValueError as exc: raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail={ error_key: UNEXPECTED_HERMES_RESPONSE, AIResponseKey.RAW: {AIResponseKey.TEXT: response.text}, }, ) from exc def _chat_messages(prompt: str, context: dict[str, Any] | None = None) -> list[dict[str, str]]: request_context = context or {} return [ { AIHttpPayloadKey.ROLE: AIChatRole.SYSTEM, AIHttpPayloadKey.CONTENT: COMPANY_MANAGEMENT_SYSTEM_INSTRUCTIONS, }, { AIHttpPayloadKey.ROLE: AIChatRole.USER, AIHttpPayloadKey.CONTENT: _ordered_context(prompt, request_context), }, ] def _ordered_context(prompt: str, context: dict[str, Any]) -> str: """Serialize trusted personalization layers in their required precedence.""" company_rules = context.get(AIContextKey.COMPANY_RULES) if company_rules is None: company_rules = context.get(AIContextKey.USER_RULES) or [] controlled_keys = { AIContextKey.USER_RULES, AIContextKey.COMPANY_RULES, AIContextKey.PERSONAL_RULES, AIContextKey.PREFERENCES, AIContextKey.INTERESTS, AIContextKey.LOCAL_MEMORY, AIContextKey.CONVERSATION_HISTORY, AIContextKey.PROVIDER_SESSION_ID, AIContextKey.ALLOW_PROVIDER_MEMORY, } current_context = { str(key): value for key, value in context.items() if key not in controlled_keys } sections = [ ("公司规则", company_rules), ("个人规则", context.get(AIContextKey.PERSONAL_RULES) or []), ( "当前请求", { "prompt": prompt, "context": current_context, }, ), ( "个人偏好与兴趣", { "preferences": context.get(AIContextKey.PREFERENCES) or [], "interests": context.get(AIContextKey.INTERESTS) or [], }, ), ("个人相关记忆", context.get(AIContextKey.LOCAL_MEMORY) or []), ("当前会话历史", context.get(AIContextKey.CONVERSATION_HISTORY) or []), ] return "\n\n".join( f"{title}:\n{json.dumps(value, ensure_ascii=False, default=str)}" for title, value in sections ) def _service_root(base_url: str, suffix: str) -> str: root = base_url.rstrip("/") normalized_suffix = suffix.rstrip("/") if root.endswith(normalized_suffix): root = root[: -len(normalized_suffix)] return root.rstrip("/")