Files
ZNJJ-api-server/src/backends/fastgpt.py
T
Eric Wang 34d09f53bb Merge branch 'main' of http://182.92.86.220:8300/wx44wx/ZNJJ-api-server
# Conflicts:
#	docs/视频快处智能信息采集机器人交互接口文档V1.0.10.docx
#	src/api/endpoints.py
#	src/core/fastgpt_client.py
2026-10-11 21:39:49 +08:00

166 lines
5.5 KiB
Python

"""FastGPT implementation of the backend-neutral chat contract."""
import json
from collections.abc import AsyncIterator
from typing import Any
from fastgpt_client import AsyncChatClient, aiter_stream_events
from fastgpt_client.exceptions import APIError, AuthenticationError, RateLimitError
from .chat import (
ChatBackendAPIError,
ChatBackendAuthenticationError,
ChatBackendRateLimitError,
ChatInput,
ChatResult,
FormUpdate,
TextDelta,
)
FORM_EXTRACT_MODULE_NAME = "更新表单"
def _parse_json_value(value: Any) -> Any:
parsed = value
for _ in range(3):
if not isinstance(parsed, str):
return parsed
parsed = parsed.strip()
if not parsed:
return {}
try:
parsed = json.loads(parsed)
except json.JSONDecodeError:
return parsed
return parsed
def _extract_form_update(nodes: Any) -> Any:
if not isinstance(nodes, list):
return {}
for node in nodes:
if not isinstance(node, dict):
continue
if node.get("moduleName") != FORM_EXTRACT_MODULE_NAME:
continue
# 代码节点返回值在 customOutputs;兼容旧版/其他形态
outputs = node.get("customOutputs")
if not isinstance(outputs, dict):
outputs = node.get("nodeOutputs")
if not isinstance(outputs, dict):
outputs = node.get("extractResult")
if not isinstance(outputs, dict):
return {}
form_update = outputs.get("formUpdate", "")
if form_update in ("", None):
return {}
parsed = _parse_json_value(form_update)
# 无变更时代码节点常返回 {},直接透传空对象
if not isinstance(parsed, dict):
return {}
return parsed
return {}
def _extract_content(data: dict[str, Any]) -> str:
try:
content = data["choices"][0]["message"]["content"]
except (KeyError, IndexError, TypeError) as exc:
raise ChatBackendAPIError("FastGPT response is missing message content") from exc
if isinstance(content, list):
try:
content = content[0]["text"]["content"]
except (KeyError, IndexError, TypeError) as exc:
raise ChatBackendAPIError(
"FastGPT response contains invalid list content"
) from exc
if not isinstance(content, str):
raise ChatBackendAPIError("FastGPT message content is not text")
return content
class FastGPTBackend:
"""Translate FastGPT SDK calls and events into the neutral chat contract."""
def __init__(self, client: AsyncChatClient):
self._client = client
async def stream(self, chat_input: ChatInput) -> AsyncIterator[TextDelta | FormUpdate]:
try:
response = await self._client.create_chat_completion(
messages=[{"role": "user", "content": chat_input.text}],
chatId=chat_input.session_id,
stream=True,
detail=True,
variables={
"needFormUpdate": chat_input.need_form_update,
"clientMode": chat_input.client_mode,
},
)
async for event in aiter_stream_events(response):
if event.kind == "flowResponses":
form_update = _extract_form_update(event.data)
if form_update:
yield FormUpdate(form_update)
continue
if event.kind not in {"answer", "fastAnswer", "data"}:
continue
if not isinstance(event.data, dict):
continue
try:
content = event.data["choices"][0]["delta"].get("content", "")
except (KeyError, IndexError, TypeError, AttributeError):
content = ""
if content:
yield TextDelta(content)
except AuthenticationError as exc:
raise ChatBackendAuthenticationError(str(exc)) from exc
except RateLimitError as exc:
raise ChatBackendRateLimitError(str(exc)) from exc
except APIError as exc:
raise ChatBackendAPIError(str(exc)) from exc
async def complete(self, chat_input: ChatInput) -> ChatResult:
try:
response = await self._client.create_chat_completion(
messages=[{"role": "user", "content": chat_input.text}],
chatId=chat_input.session_id,
stream=False,
detail=True,
variables={
"needFormUpdate": chat_input.need_form_update,
"clientMode": chat_input.client_mode,
},
)
response.raise_for_status()
data = response.json()
except AuthenticationError as exc:
raise ChatBackendAuthenticationError(str(exc)) from exc
except RateLimitError as exc:
raise ChatBackendRateLimitError(str(exc)) from exc
except APIError as exc:
raise ChatBackendAPIError(str(exc)) from exc
if not isinstance(data, dict):
raise ChatBackendAPIError("FastGPT response body is not an object")
status_code = data.get("newVariables", {}).get("status_code")
if status_code is not None:
status_code = str(status_code)
return ChatResult(
content=_extract_content(data),
status_code=status_code,
form_update=_extract_form_update(data.get("responseData", [])),
)