Repository navigation
Expand file tree
/
Copy pathchat.py
More file actions
127 lines (100 loc) · 3.71 KB
/
Copy pathchat.py
File metadata and controls
127 lines (100 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from __future__ import annotations
import asyncio
from typing import List, Optional, Sequence, Tuple
from nonebot.log import logger
from openai import AsyncOpenAI, OpenAIError
_client_lock = asyncio.Lock()
_request_lock = asyncio.Lock()
_client: Optional[AsyncOpenAI] = None
_client_config: Optional[Tuple[str, str, float]] = None
async def _get_client(
*, api_key: str, base_url: str, timeout: float
) -> Optional[AsyncOpenAI]:
"""Get or create a shared AsyncOpenAI client instance."""
global _client, _client_config
if not api_key:
return None
config = (api_key, base_url, timeout)
async with _client_lock:
if _client is not None and _client_config == config:
return _client
if _client is not None:
try:
await _client.close()
except Exception as exc: # noqa: BLE001
logger.debug(
f"simple-gpt: 关闭旧的 OpenAI 客户端时出错:{exc}"
)
_client = AsyncOpenAI(api_key=api_key, base_url=base_url, timeout=timeout)
_client_config = config
return _client
def _extract_text_content(message: object) -> Optional[str]:
"""Extract plain text from a ChatCompletionMessage."""
content = getattr(message, "content", None)
if isinstance(content, str):
stripped = content.strip()
return stripped or None
if isinstance(content, Sequence):
parts: List[str] = []
for item in content:
text = getattr(item, "text", None)
if isinstance(text, str) and text.strip():
parts.append(text)
if parts:
combined = "".join(parts).strip()
return combined or None
return None
async def generate_chat_reply(
*,
prompt: str,
api_key: str,
base_url: str,
model: str,
temperature: float,
max_tokens: int,
timeout: float,
) -> Optional[str]:
"""Call OpenAI's chat completion API through the official SDK."""
logger.info(f"simple-gpt: 生成聊天回复,prompt={prompt}")
client = await _get_client(api_key=api_key, base_url=base_url, timeout=timeout)
if client is None:
logger.warning("simple-gpt: 未配置 API Key,跳过调用。")
return None
logger.info(
f"simple-gpt: 准备调用 OpenAI,model={model}, base_url={base_url}, "
f"temperature={temperature}, max_tokens={max_tokens}"
)
try:
async with _request_lock:
completion = await client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=temperature,
# 暂时不添加最大 token 数量
# max_tokens=max_tokens,
)
except OpenAIError as exc:
logger.exception(f"simple-gpt: OpenAI 请求失败:{exc}")
return None
except Exception as exc: # noqa: BLE001
logger.exception(f"simple-gpt: 调用 OpenAI SDK 时出错:{exc}")
return None
if not completion.choices:
logger.warning(f"simple-gpt: OpenAI 响应未包含 choices:{completion}")
return None
message = completion.choices[0].message
content = _extract_text_content(message)
if not content:
logger.warning(f"simple-gpt: OpenAI 响应未包含文本内容:{completion}")
return None
return content
async def close_chat_client() -> None:
"""Release the shared AsyncOpenAI client, if any."""
global _client, _client_config
async with _client_lock:
if _client is not None:
try:
await _client.close()
finally:
_client = None
_client_config = None