FateForge / fateforge /llm.py
FateForge
Deploy to HF Spaces: Vue SPA + FastAPI, Docker SDK, LLM offline fallback
c9e11de
Raw
History Blame Contribute Delete
2.9 kB
"""LLM 流式调用:与本地 llama-server 的 OpenAI 兼容 /v1/chat/completions 通信。"""
from __future__ import annotations
import json
from typing import AsyncIterator, Dict, List
import httpx
from .config import (
LLM_BASE_URL,
LLM_MAX_TOKENS,
LLM_MODEL,
LLM_TEMPERATURE,
LLM_TIMEOUT_S,
)
async def stream_chat(
system: str,
user: str,
*,
max_tokens: int | None = None,
temperature: float | None = None,
) -> AsyncIterator[str]:
"""流式调用 LLM,逐 chunk yield 文本。
llama.cpp 的 OpenAI 兼容端点返回 SSE 格式:
data: {"choices":[{"delta":{"content":"..."}}]}
data: [DONE]
"""
payload: Dict = {
"model": LLM_MODEL,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
"max_tokens": max_tokens or LLM_MAX_TOKENS,
"temperature": temperature if temperature is not None else LLM_TEMPERATURE,
"stream": True,
}
headers = {"Content-Type": "application/json", "Accept": "text/event-stream"}
timeout = httpx.Timeout(
connect=min(3.0, LLM_TIMEOUT_S),
read=min(8.0, LLM_TIMEOUT_S),
write=min(8.0, LLM_TIMEOUT_S),
pool=min(3.0, LLM_TIMEOUT_S),
)
url = f"{LLM_BASE_URL.rstrip('/')}/v1/chat/completions"
async with httpx.AsyncClient(timeout=timeout) as client:
async with client.stream("POST", url, json=payload, headers=headers) as resp:
resp.raise_for_status()
async for raw in resp.aiter_lines():
line = raw.strip()
if not line.startswith("data:"):
continue
data = line[5:].strip()
if data == "[DONE]":
break
try:
obj = json.loads(data)
except json.JSONDecodeError:
continue
try:
delta = obj["choices"][0].get("delta", {}) or {}
chunk = delta.get("content", "")
except (KeyError, IndexError, TypeError):
chunk = ""
if chunk:
yield chunk
async def stream_fate_reading(
question: str,
hex_info: dict,
changed_info: dict | None,
moving: List[int],
level: str = "中平",
reason: str = "",
ui: dict | None = None,
lang: str = "zh",
) -> AsyncIterator[str]:
"""组装 System+User Prompt,流式返回 LLM 文本。"""
from .constants import build_system_prompt, build_user_prompt
ui = ui or {"remedy": "自渡锦囊"}
system_prompt = build_system_prompt(level, ui, lang=lang)
user_prompt = build_user_prompt(question, hex_info, changed_info, moving, level, reason, lang=lang)
async for chunk in stream_chat(system_prompt, user_prompt):
yield chunk