Compare commits
2 Commits
d5d634bf14
...
98a4dba820
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98a4dba820 | ||
|
|
a5e43190f4 |
@@ -39,6 +39,37 @@ async def _read_openai_sse(response):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def _read_gemini_sse(response):
|
||||||
|
"""读取 Gemini SSE 流(?alt=sse 格式),yield 每个 text chunk。
|
||||||
|
Gemini 无 [DONE] 哨兵,通过 finishReason 字段判断结束。"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line_bytes = await asyncio.wait_for(response.content.readline(), timeout=60.0)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
break
|
||||||
|
if not line_bytes:
|
||||||
|
break
|
||||||
|
line = line_bytes.decode("utf-8").strip()
|
||||||
|
if not line.startswith("data: "):
|
||||||
|
continue
|
||||||
|
data_str = line[6:]
|
||||||
|
try:
|
||||||
|
data = json.loads(data_str)
|
||||||
|
candidates = data.get("candidates", [])
|
||||||
|
if candidates:
|
||||||
|
candidate = candidates[0]
|
||||||
|
parts = candidate.get("content", {}).get("parts", [])
|
||||||
|
for part in parts:
|
||||||
|
text = part.get("text", "")
|
||||||
|
if text:
|
||||||
|
yield text
|
||||||
|
# finishReason 存在表示流已结束,主动退出(等价于 [DONE])
|
||||||
|
if candidate.get("finishReason"):
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Deepseek(Platform):
|
class Deepseek(Platform):
|
||||||
|
|
||||||
def __init__(self, config: BaseProxyConfig, http: ClientSession):
|
def __init__(self, config: BaseProxyConfig, http: ClientSession):
|
||||||
@@ -381,32 +412,12 @@ class Gemini(Platform):
|
|||||||
context = await maubot_llmplus.platforms.get_context(plugin, self, evt)
|
context = await maubot_llmplus.platforms.get_context(plugin, self, evt)
|
||||||
request_body, headers = self._build_gemini_request(context)
|
request_body, headers = self._build_gemini_request(context)
|
||||||
|
|
||||||
endpoint = f"{self.url}/v1beta/models/{self.model}:streamGenerateContent"
|
endpoint = f"{self.url}/v1beta/models/{self.model}:streamGenerateContent?alt=sse"
|
||||||
async with self.http.post(endpoint, headers=headers, data=json.dumps(request_body)) as response:
|
async with self.http.post(endpoint, headers=headers, data=json.dumps(request_body)) as response:
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
raise ValueError(f"Error: {await response.text()}")
|
raise ValueError(f"Error: {await response.text()}")
|
||||||
while True:
|
async for chunk in _read_gemini_sse(response):
|
||||||
try:
|
yield chunk
|
||||||
line_bytes = await asyncio.wait_for(response.content.readline(), timeout=60.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
break
|
|
||||||
if not line_bytes:
|
|
||||||
break
|
|
||||||
line = line_bytes.decode("utf-8").strip()
|
|
||||||
if not line.startswith("data: "):
|
|
||||||
continue
|
|
||||||
data_str = line[6:]
|
|
||||||
try:
|
|
||||||
data = json.loads(data_str)
|
|
||||||
candidates = data.get("candidates", [])
|
|
||||||
if candidates:
|
|
||||||
parts = candidates[0].get("content", {}).get("parts", [])
|
|
||||||
for part in parts:
|
|
||||||
text = part.get("text", "")
|
|
||||||
if text:
|
|
||||||
yield text
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def list_models(self) -> List[str]:
|
async def list_models(self) -> List[str]:
|
||||||
full_url = f"{self.url}/v1beta/models"
|
full_url = f"{self.url}/v1beta/models"
|
||||||
|
|||||||
Reference in New Issue
Block a user