import json from pathlib import Path from typing import Any import requests class LLMConfigManager: def __init__(self, config_path: str): self.config_path = Path(config_path) def load(self) -> dict: if not self.config_path.exists(): return {} with self.config_path.open("r", encoding="utf-8-sig") as file: return json.load(file) def _join_url(base_url: str, path: str) -> str: return f"{base_url.rstrip('/')}/{path.lstrip('/')}" def _timeout(config: dict) -> int: section = config.get("request", {}) if isinstance(config, dict) else {} try: return int(section.get("timeout_seconds", 45)) except Exception: return 45 def _extract_openai_text(payload: Any) -> str: if isinstance(payload, dict): choices = payload.get("choices") if isinstance(choices, list) and choices: msg = choices[0].get("message") if isinstance(choices[0], dict) else {} if isinstance(msg, dict) and msg.get("content"): return str(msg["content"]) if payload.get("answer"): return str(payload["answer"]) if payload.get("data"): return json.dumps(payload["data"], ensure_ascii=False) return json.dumps(payload, ensure_ascii=False) if isinstance(payload, (dict, list)) else str(payload) def ask_fastgpt(config: dict, prompt: str, context: str = "", custom_uid: str = "") -> dict: section = config.get("fastgpt", {}) base_url = section.get("base_url", "").strip() api_key = section.get("api_key", "").strip() chat_id = str(section.get("chat_id", "111")) model = section.get("model", "") timeout = _timeout(config) if not base_url or not api_key: return {"ok": False, "message": "FastGPT 未配置 base_url 或 api_key"} url = _join_url(base_url, "/v1/chat/completions") headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } content = prompt if not context else f"请基于以下本地知识库内容回答。\n\n知识库:\n{context}\n\n问题: {prompt}" messages = [] system_prompt = section.get("system_prompt") if system_prompt: messages.append({"role": "system", "content": system_prompt}) messages.append({"role": "user", "content": content}) use_custom_uid = custom_uid or section.get("custom_uid", "") payload = { "chatId": chat_id, "stream": False, "detail": False, "messages": messages, } if model: payload["model"] = model if use_custom_uid: payload["customUid"] = use_custom_uid try: response = requests.post(url, headers=headers, json=payload, timeout=timeout, allow_redirects=True) if response.status_code >= 400: return { "ok": False, "message": f"FastGPT 调用失败: HTTP {response.status_code}", "detail": response.text, } data = response.json() return { "ok": True, "provider": "fastgpt", "raw": data, "answer": _extract_openai_text(data), } except Exception as exc: return { "ok": False, "message": "FastGPT 请求异常", "detail": str(exc), } def ask_dify(config: dict, prompt: str, context: str = "", user: str = "anonymous") -> dict: section = config.get("dify", {}) base_url = section.get("base_url", "").strip() api_key = section.get("api_key", "").strip() timeout = _timeout(config) if not base_url or not api_key: return {"ok": False, "message": "Dify 未配置 base_url 或 api_key"} url = _join_url(base_url, "/v1/chat-messages") headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } query = prompt if not context else f"知识库上下文:\n{context}\n\n用户问题:\n{prompt}" payload = { "inputs": {}, "query": query, "response_mode": "blocking", "conversation_id": "", "user": user, } try: response = requests.post(url, headers=headers, json=payload, timeout=timeout, allow_redirects=True) if response.status_code >= 400: return { "ok": False, "message": f"Dify 调用失败: HTTP {response.status_code}", "detail": response.text, } data = response.json() answer = data.get("answer") or _extract_openai_text(data) return { "ok": True, "provider": "dify", "raw": data, "answer": answer, } except Exception as exc: return { "ok": False, "message": "Dify 请求异常", "detail": str(exc), }