Files
ai-kefu-admin/app/engine/services/ai_service.py
T

99 lines
3.7 KiB
Python
Raw Normal View History

import json
import logging
from collections.abc import AsyncGenerator
from pathlib import Path
from app.engine.providers.base import AIProvider
from app.engine.providers.glm import GLMProvider
from app.engine.config import settings
logger = logging.getLogger(__name__)
PROMPTS_DIR = Path(__file__).resolve().parent.parent / "prompts"
def _load_prompt(filename: str) -> str:
return (PROMPTS_DIR / filename).read_text(encoding="utf-8")
def create_provider() -> AIProvider:
if settings.AI_PROVIDER == "glm":
return GLMProvider(
api_key=settings.ZHIPU_API_KEY,
model=settings.AI_MODEL,
base_url=settings.ZHIPU_BASE_URL,
)
raise ValueError(f"Unknown AI provider: {settings.AI_PROVIDER}")
SYSTEM_PROMPT = _load_prompt("system.md")
COMPANY_FACTS = _load_prompt("company_facts.md") # 公司事实卡(知识与行为分离)
EXTRACTOR_PROMPT = _load_prompt("extractor.md")
EVALUATOR_PROMPT = _load_prompt("evaluator.md")
# 对话基座: 行为 prompt 在前(红线显著性),事实卡居中(参考资料),
# 当轮 context 提醒最后拼接(在 chat/stream_chat 内,保持最新鲜)
_BASE_CHAT_PROMPT = SYSTEM_PROMPT + "\n\n" + COMPANY_FACTS
class AIService:
def __init__(self, provider: AIProvider | None = None):
self.provider = provider or create_provider()
async def chat(self, messages: list[dict], context: str = "") -> str:
"""Main conversation: get AI reply to user message."""
prompt = _BASE_CHAT_PROMPT + ("\n\n" + context if context else "")
return await self.provider.chat(messages, prompt)
async def stream_chat(self, messages: list[dict], context: str = "") -> AsyncGenerator[str, None]:
"""Stream AI reply token by token."""
prompt = _BASE_CHAT_PROMPT + ("\n\n" + context if context else "")
async for token in self.provider.stream_chat(messages, prompt):
yield token
async def extract_info(
self, messages: list[dict], existing_info: dict
) -> dict:
"""Extract structured info from conversation history."""
prompt = EXTRACTOR_PROMPT.replace(
"$existing_info", json.dumps(existing_info or {}, ensure_ascii=False)
).replace("$messages", self._format_messages(messages))
_, extracted = await self.provider.chat_with_extraction(
messages=[], system_prompt=prompt
)
return extracted
async def evaluate(
self, profile: dict, recent_messages: list[dict]
) -> dict:
"""Evaluate customer intent level."""
prompt = EVALUATOR_PROMPT.replace(
"$profile", json.dumps(profile, ensure_ascii=False)
).replace("$recent_messages", self._format_messages(recent_messages[-6:]))
_, result = await self.provider.chat_with_extraction(
messages=[], system_prompt=prompt
)
return self._normalize_evaluation(result)
@staticmethod
def _format_messages(messages: list[dict]) -> str:
lines = []
for m in messages:
role = m.get("role", "unknown")
content = m.get("content", "")
label = {"user": "客户", "assistant": "顾问"}.get(role, role)
lines.append(f"{label}: {content}")
return "\n".join(lines)
@staticmethod
def _normalize_evaluation(result: dict) -> dict:
intent = result.get("intent_level", "pending")
if intent not in ("interested", "not_interested", "pending"):
intent = "pending"
return {
"intent_level": intent,
"reasoning": result.get("reasoning", ""),
"contact_info": result.get("contact_info", {}),
"info_sufficient": result.get("info_sufficient", False),
}