"""统一 LLM 服务(DeepSeek / GLM / OpenAI / Ollama)。 能力: 1. 多引擎配置持久化(Integration.type=llm_engine) 2. 动态选择激活引擎 3. OpenAI-Compatible chat/completions 调用 4. 基础 AI 调用日志写入(ai_call_logs) """ from __future__ import annotations import asyncio from dataclasses import dataclass import hashlib import json import logging import os import re import time from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Optional from uuid import uuid4 import httpx from sqlalchemy import and_, select from sqlalchemy.exc import OperationalError from sqlalchemy.ext.asyncio import AsyncSession from ...config.ai_prompts import build_default_role_prompt_bundle from ...core.database import AsyncSessionLocal from ...models import AICallLog, Integration from ..model_context_bundle import ModelContextBundleService from ...utils.timezone import now_cn_aware try: import redis.asyncio as redis_async REDIS_AVAILABLE = True except Exception: # pragma: no cover REDIS_AVAILABLE = False redis_async = None logger = logging.getLogger(__name__) SUPPORTED_PROVIDERS = {"deepseek", "glm", "openai", "ollama"} OPENCLAW_DEFAULT_LOW_MODEL = "GLM-4.7" OPENCLAW_DEFAULT_HIGH_MODEL = "MiniMax-M2.5" OPENCLAW_DEFAULT_SAFETY_MODEL = "deepseek-chat" PROVIDER_DEFAULTS: dict[str, dict[str, str]] = { "deepseek": { "name": "DeepSeek", "base_url": "https://api.deepseek.com/v1", "model": "deepseek-chat", "api_env": "DEEPSEEK_API_KEY", }, "glm": { "name": "GLM", "base_url": "https://open.bigmodel.cn/api/paas/v4", "model": "GLM-4.7", "api_env": "GLM_API_KEY", }, "openai": { "name": "OpenAI", "base_url": "https://api.openai.com/v1", "model": "gpt-4o-mini", "api_env": "OPENAI_API_KEY", }, "ollama": { "name": "Ollama", "base_url": "http://127.0.0.1:11434", "model": "llama3:latest", "api_env": "", }, } PROVIDER_ALIASES = { "zhipu": "glm", "chatglm": "glm", "deep-seek": "deepseek", "local": "ollama", } PROMPT_PROFILE_CACHE_TTL_SECONDS = 60.0 BOOL_TRUE_VALUES = {"1", "true", "yes", "y", "on", "是"} OPENCLAW_ROUTING_CACHE_TTL_SECONDS = 30.0 OPENCLAW_DEFAULT_RULES_RELATIVE_PATH = "../.openclaw/pmflow-routing-rules.json" OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS = 120 OPENCLAW_AGENTS_TIMEOUT_SECONDS_MAX = 120 DEFAULT_TIER_MODEL_OVERRIDES = { "low": OPENCLAW_DEFAULT_LOW_MODEL, "high": OPENCLAW_DEFAULT_HIGH_MODEL, "safety": OPENCLAW_DEFAULT_SAFETY_MODEL, } DEFAULT_HIGH_TIER_HINTS = ( "classify_intent_complex", "project_from_text", "feishu_project_prefill", "feishu_task_orchestration", "feishu_project_signal_classification", "adaptive_rag_answer_deep", "rag_db_project_semantic_retrieval", "analyze_food_calories", "sstc_ai_breakdown", "generate_task_breakdown", "recommend_assignee", "alignment", "charter", "cognitive_conflict", "blocker", "model_requirement", "signal_planner_analyze", ) DEFAULT_LOW_TIER_HINTS = ( "feishu_level_classification", "feishu_signal_extraction", "feishu_humanized_reply", "signal_source_onboarding_plan", "signal_source_ai_worker_execution", "extract_context_insights", "project_draft_", "adaptive_rag_answer_normal", "rag_intent_classification", "classify_intent", ) STRUCTURED_OUTPUT_FUNCTION_HINTS = ( "feishu_level_classification", "feishu_signal_extraction", "signal_source_onboarding_plan", "signal_source_ai_worker_execution", "rag_intent_classification", "classify_intent", "feishu_project_prefill", "feishu_task_orchestration", "generate_task_breakdown", "generate_charter_from_signal", "decompose_tasks_from_charter", "recommend_assignees_for_task", "evaluate_model_requirement", "calculate_alignment_score", "detect_cognitive_conflicts", "rag_db_project_semantic_retrieval", "workload_optimize_prompt", "analyze_cost_roi", "analyze_food_calories", "sstc_ai_breakdown", "sstc_charter_localize_zh", "sstc_charter_prefill", "signal_planner_analyze", ) LOW_TO_HIGH_ESCALATION_FALLBACK_MARKERS = ( "不知道", "不清楚", "无法回答", "无法确定", "无法判断", "信息不足", "缺少信息", "没有足够信息", "请提供更多信息", "抱歉", "不能提供", "无法访问", "as an ai", "i'm sorry", "cannot answer", "don't know", ) FAST_LOCAL_FALLBACK_FUNCTION_HINTS = ( "signal_source_onboarding_plan", "signal_source_ai_worker_execution", ) FAST_LOCAL_PLANNER_FUNCTION_HINTS = ( "signal_source_onboarding_plan", ) FAST_LOCAL_WORKER_FUNCTION_HINTS = ("signal_source_ai_worker_execution",) FAST_LOCAL_FALLBACK_FAST_TIMEOUT_SECONDS = 20.0 FAST_LOCAL_FALLBACK_COMPLETE_TIMEOUT_SECONDS = 35.0 STRUCTURED_CACHE_TTL_SECONDS = 300 LLM_ENGINE_SETTINGS_DEFAULTS = { "enable_local_kb_qa": False, "local_kb_qa_scope": "kb_qa_only", } LOCAL_KB_QA_FUNCTION_PREFIXES = ( "adaptive_rag_answer_", ) LOCAL_KB_QA_FUNCTION_EXACT = { "rag_intent_classification", "rag_db_project_semantic_retrieval", } STRICT_MODEL_ERROR_FUNCTION_PREFIXES = ( "adaptive_rag_answer_", ) STRICT_MODEL_ERROR_FUNCTION_EXACT = { "rag_db_project_semantic_retrieval", } FAIL_FAST_ERROR_MARKERS = ( "primary_engine_empty_response", "high_tier_empty_response", "llm 返回空内容", "返回空内容", "empty_response", "provider_circuit_open", ) CIRCUIT_BREAKER_FAILURE_THRESHOLD = 2 CIRCUIT_BREAKER_COOLDOWN_SECONDS = 300.0 CIRCUIT_BREAKER_EXEMPT_FUNCTION_PREFIXES = ( "project_from_text_", "project_draft_", ) DDL_API_GUARDRAIL_FUNCTION_EXACT = { "classify_intent_ai_pmo_kernel", "classify_intent_complex", } DDL_API_GUARDRAIL_SYSTEM_PROMPT = """ 硬性约束(最高优先级): 1. 禁止直接执行或建议执行数据库 DDL SQL(CREATE/ALTER/DROP/TRUNCATE/RENAME/COMMENT/GRANT/REVOKE)。 2. 所有数据库 DDL 变更必须通过受控接口或迁移流程(例如 Alembic migration)。 3. 若输入要求直接改库执行 DDL: - `classify_intent_ai_pmo_kernel` / `classify_intent_complex` 必须返回 `query.read_only`,并在 reasoning 包含 `ddl_api_required`。 """.strip() STRUCTURED_OBJECT_SCHEMAS: dict[str, dict[str, Any]] = { "ai_pmo_signal_title_refine": { "required_keys": ("title",), "type_checks": { "title": "str", }, }, "signal_source_onboarding_plan": { "required_keys": ("should_onboard", "summary"), "type_checks": { "should_onboard": "bool", "summary": "str", }, "optional_type_checks": { "worker_roles": ("list",), "execution_instruction": ("str_or_none",), "source": ("dict_or_none",), }, }, "signal_source_ai_worker_execution": { "required_keys": ("summary", "deliverables", "risks", "next_actions"), "type_checks": { "summary": "str", "deliverables": "list", "risks": "list", "next_actions": "list", }, }, "classify_intent_ai_pmo_kernel": { "required_keys": ("confidence", "reasoning"), "type_checks": { "confidence": "number", "reasoning": "str", }, "optional_type_checks": { "intent": ("str_or_none",), "family": ("str_or_none",), "action": ("str_or_none",), "project_id": ("number_or_none",), "requested_count": ("number_or_none",), "requires_confirmation": ("bool",), }, }, "classify_intent_complex": { "required_keys": ("confidence", "reasoning"), "type_checks": { "confidence": "number", "reasoning": "str", }, "optional_type_checks": { "intent": ("str_or_none",), "family": ("str_or_none",), "action": ("str_or_none",), "project_id": ("number_or_none",), "requested_count": ("number_or_none",), "requires_confirmation": ("bool",), }, }, "signal_planner_analyze": { "required_keys": ("summary", "priority", "execution_plan", "risks", "confidence"), "type_checks": { "summary": "str", "priority": "str", "execution_plan": "list", "risks": "list", "confidence": "number", }, "optional_type_checks": { "assignee_user_id": ("number_or_none",), "assignee_reason": ("str_or_none",), }, }, }