验证当前模型可用性」}ાડી Reviewing format? no
This commit is contained in:
parent
7f837e7019
commit
054d056182
25
.env.example
25
.env.example
|
|
@ -11,27 +11,30 @@ FRONTEND_PORT=5174
|
||||||
|
|
||||||
# Backend Runtime
|
# Backend Runtime
|
||||||
CORS_ORIGINS=http://localhost:5174,http://127.0.0.1:5174
|
CORS_ORIGINS=http://localhost:5174,http://127.0.0.1:5174
|
||||||
LLM_PROVIDER=openai
|
LLM_PROVIDER=glm
|
||||||
LLM_FALLBACK_PROVIDER=ollama
|
LLM_FALLBACK_PROVIDER=glm
|
||||||
OPENAI_BASE_URL=http://openclaw:18789/v1
|
OPENAI_BASE_URL=https://api.edgefn.net/v1
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
OPENCLAW_ROUTING_ENABLED=true
|
OPENAI_MODEL=MiniMax-M2.5
|
||||||
|
OPENCLAW_ROUTING_ENABLED=false
|
||||||
OPENCLAW_ROUTING_RULES_PATH=/app/backend/.openclaw/pmflow-routing-rules.json
|
OPENCLAW_ROUTING_RULES_PATH=/app/backend/.openclaw/pmflow-routing-rules.json
|
||||||
OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS=120
|
OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS=120
|
||||||
LLM_MIXED_ROUTING_ENABLED=false
|
LLM_MIXED_ROUTING_ENABLED=true
|
||||||
LLM_LOW_TO_HIGH_ESCALATION_ENABLED=true
|
LLM_LOW_TO_HIGH_ESCALATION_ENABLED=true
|
||||||
LLM_ROUTING_LOW_PROVIDER=openai
|
LLM_ROUTING_LOW_PROVIDER=glm
|
||||||
LLM_ROUTING_LOW_MODEL=zai/glm-4-flash
|
LLM_ROUTING_LOW_MODEL=GLM-4.7
|
||||||
LLM_ROUTING_HIGH_PROVIDER=openai
|
LLM_ROUTING_HIGH_PROVIDER=openai
|
||||||
LLM_ROUTING_HIGH_MODEL=minimax/MiniMax-M2.5
|
LLM_ROUTING_HIGH_MODEL=MiniMax-M2.5
|
||||||
LLM_ROUTING_SAFETY_PROVIDER=openai
|
LLM_ROUTING_SAFETY_PROVIDER=glm
|
||||||
LLM_ROUTING_SAFETY_MODEL=deepseek/deepseek-chat
|
LLM_ROUTING_SAFETY_MODEL=GLM-4.7
|
||||||
LLM_DIRECT_EMERGENCY_ENABLED=false
|
LLM_DIRECT_EMERGENCY_ENABLED=true
|
||||||
RAG_DEEP_RETRIEVAL_STEPS=5
|
RAG_DEEP_RETRIEVAL_STEPS=5
|
||||||
RAG_DEEP_VECTOR_TOP_K=12
|
RAG_DEEP_VECTOR_TOP_K=12
|
||||||
RAG_DEEP_CONTEXT_BLOCKS=16
|
RAG_DEEP_CONTEXT_BLOCKS=16
|
||||||
RAG_DEEP_ANSWER_MAX_TOKENS=1400
|
RAG_DEEP_ANSWER_MAX_TOKENS=1400
|
||||||
GLM_API_KEY=
|
GLM_API_KEY=
|
||||||
|
GLM_BASE_URL=https://api.edgefn.net/v1
|
||||||
|
GLM_MODEL=GLM-4.7
|
||||||
DEEPSEEK_API_KEY=
|
DEEPSEEK_API_KEY=
|
||||||
OLLAMA_HOST=http://host.docker.internal:11434
|
OLLAMA_HOST=http://host.docker.internal:11434
|
||||||
OLLAMA_BASE_URL=http://host.docker.internal:11434
|
OLLAMA_BASE_URL=http://host.docker.internal:11434
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ backend/
|
||||||
│ └── utils/ # 工具函数
|
│ └── utils/ # 工具函数
|
||||||
├── alembic/ # 数据库迁移
|
├── alembic/ # 数据库迁移
|
||||||
│ └── versions/
|
│ └── versions/
|
||||||
│ └── 20260310_add_feishu_outbox_reply_hash.py # 飞书 outbox 回执哈希幂等字段
|
│ ├── 20260310_add_feishu_outbox_reply_hash.py # 飞书 outbox 回执哈希幂等字段
|
||||||
|
│ ├── 20260311_add_pg_trgm_text_indexes.py # pg_trgm 扩展与文本检索索引
|
||||||
|
│ └── 20260311_add_kb_chunk_ivfflat_index.py # knowledge_base_chunks 向量 IVFFlat 索引
|
||||||
├── requirements.txt # Python 依赖
|
├── requirements.txt # Python 依赖
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
└── tests/
|
└── tests/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
"""add ivfflat index for knowledge chunk embedding
|
||||||
|
|
||||||
|
Revision ID: add_kb_chunk_ivfflat_index
|
||||||
|
Revises: add_pg_trgm_text_indexes
|
||||||
|
Create Date: 2026-03-11
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
revision = "add_kb_chunk_ivfflat_index"
|
||||||
|
down_revision = "add_pg_trgm_text_indexes"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_postgresql() -> bool:
|
||||||
|
bind = op.get_bind()
|
||||||
|
return str(getattr(getattr(bind, "dialect", None), "name", "")).lower().startswith("postgres")
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
if not _is_postgresql():
|
||||||
|
return
|
||||||
|
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_knowledge_chunks_embedding_ivfflat
|
||||||
|
ON knowledge_base_chunks
|
||||||
|
USING ivfflat (embedding vector_cosine_ops)
|
||||||
|
WITH (lists = 100)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
if not _is_postgresql():
|
||||||
|
return
|
||||||
|
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_knowledge_chunks_embedding_ivfflat")
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""add pg_trgm text indexes for project search
|
||||||
|
|
||||||
|
Revision ID: add_pg_trgm_text_indexes
|
||||||
|
Revises: add_feishu_outbox_reply_hash
|
||||||
|
Create Date: 2026-03-11
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
revision = "add_pg_trgm_text_indexes"
|
||||||
|
down_revision = "add_feishu_outbox_reply_hash"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_postgresql() -> bool:
|
||||||
|
bind = op.get_bind()
|
||||||
|
return str(getattr(getattr(bind, "dialect", None), "name", "")).lower().startswith("postgres")
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
if not _is_postgresql():
|
||||||
|
return
|
||||||
|
|
||||||
|
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_projects_name_trgm
|
||||||
|
ON projects USING GIN (name gin_trgm_ops)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_projects_title_trgm
|
||||||
|
ON projects USING GIN (title gin_trgm_ops)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_projects_description_trgm
|
||||||
|
ON projects USING GIN (description gin_trgm_ops)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_signals_title_trgm
|
||||||
|
ON signals USING GIN (title gin_trgm_ops)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_signals_content_trgm
|
||||||
|
ON signals USING GIN (content gin_trgm_ops)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_tasks_title_trgm
|
||||||
|
ON tasks USING GIN (title gin_trgm_ops)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
if not _is_postgresql():
|
||||||
|
return
|
||||||
|
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_tasks_title_trgm")
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_signals_content_trgm")
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_signals_title_trgm")
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_projects_description_trgm")
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_projects_title_trgm")
|
||||||
|
op.execute("DROP INDEX IF EXISTS ix_projects_name_trgm")
|
||||||
|
|
@ -24,7 +24,7 @@ from datetime import datetime, timezone
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from sqlalchemy import and_, delete, func, or_, select
|
from sqlalchemy import and_, bindparam, delete, func, or_, select, text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from ..config.ai_prompts import PROMPT_RAG_ANSWER, PROMPT_RAG_DB_PROJECT_QA, PROMPT_RAG_INTENT_CLASSIFICATION
|
from ..config.ai_prompts import PROMPT_RAG_ANSWER, PROMPT_RAG_DB_PROJECT_QA, PROMPT_RAG_INTENT_CLASSIFICATION
|
||||||
|
|
@ -4490,7 +4490,147 @@ class AdaptiveRAGService:
|
||||||
source_types = self._map_domains_to_source_types(domains)
|
source_types = self._map_domains_to_source_types(domains)
|
||||||
if not source_types:
|
if not source_types:
|
||||||
return []
|
return []
|
||||||
|
limit = max(1, min(int(top_k or DEFAULT_TOP_K), 40))
|
||||||
|
if self._is_postgresql_session():
|
||||||
|
pg_rows = await self._retrieve_vector_context_pg(
|
||||||
|
question_embedding=question_embedding,
|
||||||
|
source_types=source_types,
|
||||||
|
project_id=project_id,
|
||||||
|
source_chat_id=source_chat_id,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
if pg_rows is not None:
|
||||||
|
return pg_rows
|
||||||
|
|
||||||
|
return await self._retrieve_vector_context_legacy(
|
||||||
|
question_embedding=question_embedding,
|
||||||
|
source_types=source_types,
|
||||||
|
project_id=project_id,
|
||||||
|
source_chat_id=source_chat_id,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _is_postgresql_session(self) -> bool:
|
||||||
|
try:
|
||||||
|
bind = self.db.get_bind()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
dialect_name = str(getattr(getattr(bind, "dialect", None), "name", "")).lower()
|
||||||
|
return dialect_name.startswith("postgres")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _vector_literal(values: list[float]) -> str:
|
||||||
|
normalized: list[str] = []
|
||||||
|
for raw in values:
|
||||||
|
try:
|
||||||
|
normalized.append(f"{float(raw):.8f}")
|
||||||
|
except Exception:
|
||||||
|
normalized.append("0.0")
|
||||||
|
return "[" + ",".join(normalized) + "]"
|
||||||
|
|
||||||
|
async def _retrieve_vector_context_pg(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
question_embedding: list[float],
|
||||||
|
source_types: list[str],
|
||||||
|
project_id: Optional[int],
|
||||||
|
source_chat_id: Optional[str],
|
||||||
|
limit: int,
|
||||||
|
) -> Optional[list[RetrievedItem]]:
|
||||||
|
query_vec = self._vector_literal(question_embedding)
|
||||||
|
scoped_project_id = int(project_id) if project_id is not None else 0
|
||||||
|
scoped_source_chat_id = str(source_chat_id or "").strip()
|
||||||
|
stmt = text(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
source_type,
|
||||||
|
content,
|
||||||
|
project_id,
|
||||||
|
"metadata" AS chunk_metadata,
|
||||||
|
1 - (embedding <=> CAST(:query_vec AS vector)) AS similarity
|
||||||
|
FROM knowledge_base_chunks
|
||||||
|
WHERE tenant_id = :tenant_id
|
||||||
|
AND source_type IN :source_types
|
||||||
|
AND embedding IS NOT NULL
|
||||||
|
AND (
|
||||||
|
:project_scope = FALSE
|
||||||
|
OR (source_type IN ('feishu', 'pmflow') AND project_id = :project_id)
|
||||||
|
OR (source_type NOT IN ('feishu', 'pmflow') AND (project_id = :project_id OR project_id IS NULL))
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
:source_chat_id = ''
|
||||||
|
OR source_type <> 'feishu'
|
||||||
|
OR COALESCE("metadata"->>'chat_id', '') = ''
|
||||||
|
OR "metadata"->>'chat_id' = :source_chat_id
|
||||||
|
)
|
||||||
|
ORDER BY embedding <=> CAST(:query_vec AS vector)
|
||||||
|
LIMIT :limit
|
||||||
|
"""
|
||||||
|
).bindparams(bindparam("source_types", expanding=True))
|
||||||
|
try:
|
||||||
|
rows = (
|
||||||
|
await self.db.execute(
|
||||||
|
stmt,
|
||||||
|
{
|
||||||
|
"tenant_id": self.tenant_id,
|
||||||
|
"source_types": list(source_types),
|
||||||
|
"project_scope": project_id is not None,
|
||||||
|
"project_id": scoped_project_id,
|
||||||
|
"source_chat_id": scoped_source_chat_id,
|
||||||
|
"query_vec": query_vec,
|
||||||
|
"limit": int(limit),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
).mappings().all()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
scored: list[RetrievedItem] = []
|
||||||
|
for row in rows:
|
||||||
|
source_type = str(row.get("source_type") or "doc")
|
||||||
|
meta = self._safe_dict(row.get("chunk_metadata")).copy()
|
||||||
|
chunk_project_id_raw = row.get("project_id")
|
||||||
|
chunk_project_id = int(chunk_project_id_raw) if chunk_project_id_raw is not None else None
|
||||||
|
if chunk_project_id is not None:
|
||||||
|
meta.setdefault("project_id", chunk_project_id)
|
||||||
|
|
||||||
|
if source_type == "pmflow":
|
||||||
|
target_url = str(meta.get("target_url") or "").strip()
|
||||||
|
if not target_url:
|
||||||
|
target_url = self._build_pmflow_target_url(
|
||||||
|
record_type=str(meta.get("record_type") or ""),
|
||||||
|
record_id=meta.get("record_id"),
|
||||||
|
project_id=meta.get("project_id"),
|
||||||
|
)
|
||||||
|
if target_url:
|
||||||
|
meta["target_url"] = target_url
|
||||||
|
|
||||||
|
score = self._to_float(row.get("similarity"), 0.0)
|
||||||
|
if score <= 0:
|
||||||
|
continue
|
||||||
|
label = self._source_label(source_type, meta)
|
||||||
|
scored.append(
|
||||||
|
RetrievedItem(
|
||||||
|
source=source_type,
|
||||||
|
content=str(row.get("content") or "")[:1200],
|
||||||
|
score=score,
|
||||||
|
source_label=label,
|
||||||
|
metadata=meta,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
scored.sort(key=lambda x: x.score, reverse=True)
|
||||||
|
return scored[:limit]
|
||||||
|
|
||||||
|
async def _retrieve_vector_context_legacy(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
question_embedding: list[float],
|
||||||
|
source_types: list[str],
|
||||||
|
project_id: Optional[int],
|
||||||
|
source_chat_id: Optional[str],
|
||||||
|
limit: int,
|
||||||
|
) -> list[RetrievedItem]:
|
||||||
stmt = select(KnowledgeBaseChunk).where(
|
stmt = select(KnowledgeBaseChunk).where(
|
||||||
and_(
|
and_(
|
||||||
KnowledgeBaseChunk.tenant_id == self.tenant_id,
|
KnowledgeBaseChunk.tenant_id == self.tenant_id,
|
||||||
|
|
@ -4498,7 +4638,8 @@ class AdaptiveRAGService:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
chunks = (await self.db.execute(stmt.limit(500))).scalars().all()
|
scan_limit = max(120, min(1200, limit * 20))
|
||||||
|
chunks = (await self.db.execute(stmt.limit(scan_limit))).scalars().all()
|
||||||
scored: list[RetrievedItem] = []
|
scored: list[RetrievedItem] = []
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
meta = self._safe_dict(chunk.chunk_metadata).copy()
|
meta = self._safe_dict(chunk.chunk_metadata).copy()
|
||||||
|
|
@ -4507,8 +4648,6 @@ class AdaptiveRAGService:
|
||||||
meta.setdefault("project_id", chunk_project_id)
|
meta.setdefault("project_id", chunk_project_id)
|
||||||
|
|
||||||
if project_id is not None:
|
if project_id is not None:
|
||||||
# 防止跨项目泄漏:Feishu/PMFlow 来源必须严格匹配项目组;
|
|
||||||
# 文档/URL 来源可读取全局(project_id 为空)和当前项目数据。
|
|
||||||
if chunk.source_type in {"feishu", "pmflow"}:
|
if chunk.source_type in {"feishu", "pmflow"}:
|
||||||
if chunk_project_id != int(project_id):
|
if chunk_project_id != int(project_id):
|
||||||
continue
|
continue
|
||||||
|
|
@ -4543,7 +4682,6 @@ class AdaptiveRAGService:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
scored.sort(key=lambda x: x.score, reverse=True)
|
scored.sort(key=lambda x: x.score, reverse=True)
|
||||||
limit = max(1, min(int(top_k or DEFAULT_TOP_K), 40))
|
|
||||||
return scored[:limit]
|
return scored[:limit]
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
@ -38,6 +39,8 @@ except Exception: # pragma: no cover
|
||||||
REDIS_AVAILABLE = False
|
REDIS_AVAILABLE = False
|
||||||
redis_async = None
|
redis_async = None
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORTED_PROVIDERS = {"deepseek", "glm", "openai", "ollama"}
|
SUPPORTED_PROVIDERS = {"deepseek", "glm", "openai", "ollama"}
|
||||||
OPENCLAW_PRIMARY_GATEWAY_URL = "http://openclaw:18789/v1"
|
OPENCLAW_PRIMARY_GATEWAY_URL = "http://openclaw:18789/v1"
|
||||||
OPENCLAW_DEFAULT_LOW_MODEL = "zai/glm-4-flash"
|
OPENCLAW_DEFAULT_LOW_MODEL = "zai/glm-4-flash"
|
||||||
|
|
@ -626,6 +629,7 @@ class LLMService:
|
||||||
return await self._generate_structured_with_db(db, prompt, **call_kwargs)
|
return await self._generate_structured_with_db(db, prompt, **call_kwargs)
|
||||||
|
|
||||||
async def _generate_with_db(self, db: AsyncSession, prompt: str, **kwargs: Any) -> str:
|
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)
|
tenant_id = int(kwargs.get("tenant_id") or 1)
|
||||||
function_name = str(kwargs.get("function_name") or "llm_generate")
|
function_name = str(kwargs.get("function_name") or "llm_generate")
|
||||||
temperature = float(kwargs.get("temperature") or 0.2)
|
temperature = float(kwargs.get("temperature") or 0.2)
|
||||||
|
|
@ -639,6 +643,20 @@ class LLMService:
|
||||||
prompt_profile = {"role_name": "", "system_prompt": ""}
|
prompt_profile = {"role_name": "", "system_prompt": ""}
|
||||||
merged_prompt = self._merge_prompt_with_profile(prompt=prompt, prompt_profile=prompt_profile)
|
merged_prompt = self._merge_prompt_with_profile(prompt=prompt, prompt_profile=prompt_profile)
|
||||||
safety_attempted = False
|
safety_attempted = False
|
||||||
|
|
||||||
|
def _log_stub_failure(error_obj: Exception) -> None:
|
||||||
|
elapsed_ms = (time.perf_counter() - started_at) * 1000
|
||||||
|
provider = str(engine_meta.get("provider") or "")
|
||||||
|
model = str(engine_meta.get("model") or "")
|
||||||
|
logger.warning(
|
||||||
|
"[LLM_FAIL] 生成失败 → 兜底 stub function=%s provider=%s model=%s error=%r duration_ms=%.0f",
|
||||||
|
function_name,
|
||||||
|
provider,
|
||||||
|
model,
|
||||||
|
error_obj,
|
||||||
|
elapsed_ms,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if use_prompt_profile:
|
if use_prompt_profile:
|
||||||
prompt_profile = await self._resolve_active_prompt_profile(db, tenant_id=tenant_id)
|
prompt_profile = await self._resolve_active_prompt_profile(db, tenant_id=tenant_id)
|
||||||
|
|
@ -743,6 +761,7 @@ class LLMService:
|
||||||
status = "failed_fast"
|
status = "failed_fast"
|
||||||
response_text = self._stub_response(prompt)
|
response_text = self._stub_response(prompt)
|
||||||
_runtime_log("LLM", f"生成快速失败,已跳过升级重试:{exc}")
|
_runtime_log("LLM", f"生成快速失败,已跳过升级重试:{exc}")
|
||||||
|
_log_stub_failure(exc)
|
||||||
return response_text
|
return response_text
|
||||||
if self._is_low_tier_function(function_name):
|
if self._is_low_tier_function(function_name):
|
||||||
if self._prefer_fast_local_fallback(function_name):
|
if self._prefer_fast_local_fallback(function_name):
|
||||||
|
|
@ -850,7 +869,7 @@ class LLMService:
|
||||||
|
|
||||||
status = "failed"
|
status = "failed"
|
||||||
response_text = self._stub_response(prompt)
|
response_text = self._stub_response(prompt)
|
||||||
_runtime_log("LLM", f"生成失败,已返回兜底 stub:{exc}")
|
_log_stub_failure(upgraded_exc)
|
||||||
return response_text
|
return response_text
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -65,19 +65,20 @@ services:
|
||||||
OPENCLAW_ROUTING_RULES_PATH: ${OPENCLAW_ROUTING_RULES_PATH:-/app/backend/.openclaw/pmflow-routing-rules.json}
|
OPENCLAW_ROUTING_RULES_PATH: ${OPENCLAW_ROUTING_RULES_PATH:-/app/backend/.openclaw/pmflow-routing-rules.json}
|
||||||
LLM_MIXED_ROUTING_ENABLED: ${LLM_MIXED_ROUTING_ENABLED:-false}
|
LLM_MIXED_ROUTING_ENABLED: ${LLM_MIXED_ROUTING_ENABLED:-false}
|
||||||
LLM_LOW_TO_HIGH_ESCALATION_ENABLED: ${LLM_LOW_TO_HIGH_ESCALATION_ENABLED:-true}
|
LLM_LOW_TO_HIGH_ESCALATION_ENABLED: ${LLM_LOW_TO_HIGH_ESCALATION_ENABLED:-true}
|
||||||
LLM_ROUTING_LOW_PROVIDER: ${LLM_ROUTING_LOW_PROVIDER:-openai}
|
LLM_ROUTING_LOW_PROVIDER: ${LLM_ROUTING_LOW_PROVIDER:-glm}
|
||||||
LLM_ROUTING_LOW_MODEL: ${LLM_ROUTING_LOW_MODEL:-zai/glm-4-flash}
|
LLM_ROUTING_LOW_MODEL: ${LLM_ROUTING_LOW_MODEL:-GLM-4.7}
|
||||||
LLM_ROUTING_HIGH_PROVIDER: ${LLM_ROUTING_HIGH_PROVIDER:-openai}
|
LLM_ROUTING_HIGH_PROVIDER: ${LLM_ROUTING_HIGH_PROVIDER:-openai}
|
||||||
LLM_ROUTING_HIGH_MODEL: ${LLM_ROUTING_HIGH_MODEL:-minimax/MiniMax-M2.5}
|
LLM_ROUTING_HIGH_MODEL: ${LLM_ROUTING_HIGH_MODEL:-MiniMax-M2.5}
|
||||||
LLM_ROUTING_SAFETY_PROVIDER: ${LLM_ROUTING_SAFETY_PROVIDER:-openai}
|
LLM_ROUTING_SAFETY_PROVIDER: ${LLM_ROUTING_SAFETY_PROVIDER:-glm}
|
||||||
LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:-deepseek/deepseek-chat}
|
LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:-GLM-4.7}
|
||||||
LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:-false}
|
LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:-false}
|
||||||
LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:-90}
|
LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:-90}
|
||||||
AUTO_CREATE_TABLES: "true"
|
AUTO_CREATE_TABLES: "true"
|
||||||
GLM_API_KEY: ${GLM_API_KEY:-}
|
GLM_API_KEY: ${GLM_API_KEY:-}
|
||||||
GLM_BASE_URL: ${GLM_BASE_URL:-https://open.bigmodel.cn/api/paas/v4}
|
GLM_BASE_URL: ${GLM_BASE_URL:-https://open.bigmodel.cn/api/paas/v4}
|
||||||
GLM_MODEL: ${GLM_MODEL:-glm-4-flash}
|
GLM_MODEL: ${GLM_MODEL:-GLM-4.7}
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||||
|
OPENAI_MODEL: ${OPENAI_MODEL:-MiniMax-M2.5}
|
||||||
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
|
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
|
||||||
OLLAMA_HOST: ${OLLAMA_HOST:-http://host.docker.internal:11434}
|
OLLAMA_HOST: ${OLLAMA_HOST:-http://host.docker.internal:11434}
|
||||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
||||||
|
|
|
||||||
|
|
@ -65,19 +65,20 @@ services:
|
||||||
OPENCLAW_ROUTING_RULES_PATH: ${OPENCLAW_ROUTING_RULES_PATH:-/app/backend/.openclaw/pmflow-routing-rules.json}
|
OPENCLAW_ROUTING_RULES_PATH: ${OPENCLAW_ROUTING_RULES_PATH:-/app/backend/.openclaw/pmflow-routing-rules.json}
|
||||||
LLM_MIXED_ROUTING_ENABLED: ${LLM_MIXED_ROUTING_ENABLED:-false}
|
LLM_MIXED_ROUTING_ENABLED: ${LLM_MIXED_ROUTING_ENABLED:-false}
|
||||||
LLM_LOW_TO_HIGH_ESCALATION_ENABLED: ${LLM_LOW_TO_HIGH_ESCALATION_ENABLED:-true}
|
LLM_LOW_TO_HIGH_ESCALATION_ENABLED: ${LLM_LOW_TO_HIGH_ESCALATION_ENABLED:-true}
|
||||||
LLM_ROUTING_LOW_PROVIDER: ${LLM_ROUTING_LOW_PROVIDER:-openai}
|
LLM_ROUTING_LOW_PROVIDER: ${LLM_ROUTING_LOW_PROVIDER:-glm}
|
||||||
LLM_ROUTING_LOW_MODEL: ${LLM_ROUTING_LOW_MODEL:-zai/glm-4-flash}
|
LLM_ROUTING_LOW_MODEL: ${LLM_ROUTING_LOW_MODEL:-GLM-4.7}
|
||||||
LLM_ROUTING_HIGH_PROVIDER: ${LLM_ROUTING_HIGH_PROVIDER:-openai}
|
LLM_ROUTING_HIGH_PROVIDER: ${LLM_ROUTING_HIGH_PROVIDER:-openai}
|
||||||
LLM_ROUTING_HIGH_MODEL: ${LLM_ROUTING_HIGH_MODEL:-minimax/MiniMax-M2.5}
|
LLM_ROUTING_HIGH_MODEL: ${LLM_ROUTING_HIGH_MODEL:-MiniMax-M2.5}
|
||||||
LLM_ROUTING_SAFETY_PROVIDER: ${LLM_ROUTING_SAFETY_PROVIDER:-openai}
|
LLM_ROUTING_SAFETY_PROVIDER: ${LLM_ROUTING_SAFETY_PROVIDER:-glm}
|
||||||
LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:-deepseek/deepseek-chat}
|
LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:-GLM-4.7}
|
||||||
LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:-false}
|
LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:-false}
|
||||||
LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:-90}
|
LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:-90}
|
||||||
AUTO_CREATE_TABLES: "true"
|
AUTO_CREATE_TABLES: "true"
|
||||||
GLM_API_KEY: ${GLM_API_KEY:-}
|
GLM_API_KEY: ${GLM_API_KEY:-}
|
||||||
GLM_BASE_URL: ${GLM_BASE_URL:-https://open.bigmodel.cn/api/paas/v4}
|
GLM_BASE_URL: ${GLM_BASE_URL:-https://open.bigmodel.cn/api/paas/v4}
|
||||||
GLM_MODEL: ${GLM_MODEL:-glm-4-flash}
|
GLM_MODEL: ${GLM_MODEL:-GLM-4.7}
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||||
|
OPENAI_MODEL: ${OPENAI_MODEL:-MiniMax-M2.5}
|
||||||
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
|
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
|
||||||
OLLAMA_HOST: ${OLLAMA_HOST:-http://host.docker.internal:11434}
|
OLLAMA_HOST: ${OLLAMA_HOST:-http://host.docker.internal:11434}
|
||||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue