Spaces:
Running
Running
| """ | |
| 流式响应处理器(简化版) | |
| 实现"假流式":收集所有上游数据后,解析并聚合为一个包含完整上下文(思考过程+最终内容)的响应,一次性发送给下游。 | |
| """ | |
| import json | |
| import time | |
| from typing import Any, cast | |
| import collections.abc | |
| from src.core.errors import ( | |
| VcoreError, | |
| EmptyResponseError, | |
| InternalError, | |
| NotFoundError, | |
| InvalidArgumentError, | |
| RateLimitError, | |
| ) | |
| from src.utils.logger import get_logger | |
| from src.utils.token_counter import calculate_usage_metadata | |
| from .parser import parse_upstream_data | |
| # 初始化日志 | |
| logger = get_logger(__name__) | |
| class StreamProcessor: | |
| """ | |
| 简化的流式响应处理器 (v3 - 假流式 + 思考过程) | |
| 职责: | |
| 1. 收集上游所有流式数据 | |
| 2. 使用 parser 解析聚合后的数据 | |
| 3. 构建 Gemini 格式的 SSE 响应 | |
| """ | |
| def __init__(self): | |
| logger.debug("初始化流处理器") | |
| # 状态追踪 | |
| self._actual_content_sent = False | |
| self._request_context: dict[str, Any] = {} | |
| def has_actual_content_sent(self) -> bool: | |
| """检查是否已发送实际文本内容""" | |
| return self._actual_content_sent | |
| def set_request_context(self, downstream_payload: dict[str, Any], upstream_payload: dict[str, Any]): | |
| """设置请求上下文""" | |
| logger.debug("设置流处理器请求上下文") | |
| self._request_context = { | |
| 'downstream_payload': downstream_payload, | |
| 'upstream_payload': upstream_payload | |
| } | |
| def _create_gemini_chunk( | |
| self, | |
| parts: list[dict[str, Any]], | |
| finish_reason: str | None, | |
| safety_ratings: list[dict[str, Any]], | |
| citation_metadata: dict[str, Any], | |
| grounding_metadata: dict[str, Any], | |
| candidate_index: int, | |
| prompt_feedback: dict[str, Any], | |
| usage_metadata: dict[str, Any], | |
| finish_message: str | None = None, | |
| token_count: int | None = None, | |
| avg_logprobs: float | None = None, | |
| logprobs_result: dict[str, Any] | None = None, | |
| create_time: str | None = None, | |
| model_version: str | None = None, | |
| response_id: str | None = None, | |
| model_status: dict[str, Any] | None = None, | |
| ) -> str: | |
| """根据聚合后的内容,创建一个包含完整上下文的Gemini格式SSE事件。""" | |
| candidate: dict[str, Any] = { | |
| "index": candidate_index | |
| } | |
| if finish_reason and isinstance(finish_reason, str): | |
| candidate["finishReason"] = finish_reason.upper() | |
| if finish_message: | |
| candidate["finishMessage"] = finish_message | |
| if parts: | |
| candidate["content"] = { | |
| "parts": parts, | |
| "role": "model" | |
| } | |
| # 只添加实际存在的字段 | |
| if safety_ratings: | |
| candidate["safetyRatings"] = safety_ratings | |
| if citation_metadata: | |
| candidate["citationMetadata"] = citation_metadata | |
| if grounding_metadata: | |
| candidate["groundingMetadata"] = grounding_metadata | |
| if token_count is not None: | |
| candidate["tokenCount"] = token_count | |
| if avg_logprobs is not None: | |
| candidate["avgLogprobs"] = avg_logprobs | |
| if logprobs_result: | |
| candidate["logprobsResult"] = logprobs_result | |
| # 根据 doc.md,确保 candidate 其他字段正确 | |
| chunk: dict[str, Any] = {"candidates": [candidate]} | |
| # 按照 doc.md 定义 | |
| if prompt_feedback: | |
| chunk["promptFeedback"] = prompt_feedback | |
| if usage_metadata: | |
| chunk["usageMetadata"] = usage_metadata | |
| if create_time: | |
| chunk["createTime"] = create_time | |
| if model_version: | |
| chunk["modelVersion"] = model_version | |
| if response_id: | |
| chunk["responseId"] = response_id | |
| if model_status: | |
| chunk["modelStatus"] = model_status | |
| return "data: " + json.dumps(chunk, ensure_ascii=False, separators=(',', ':')) + "\n\n" | |
| async def process_stream( | |
| self, | |
| response_iterator: collections.abc.AsyncIterator[str], | |
| model: str = "vcore-ai-proxy" | |
| ) -> collections.abc.AsyncGenerator[str, None]: | |
| """ | |
| 处理流式响应(假流式 v3)。 | |
| """ | |
| # 移除重复日志,只在开始时记录一次 | |
| # logger.info(f"开始处理流式响应: 模型={model}") | |
| start_time = time.time() | |
| raw_chunks: list[str] = [] | |
| try: | |
| chunk_count = 0 | |
| logger.debug("开始收集上游数据块") | |
| async for chunk in response_iterator: | |
| chunk_count += 1 | |
| raw_chunks.append(chunk) | |
| logger.debug(f"收集完成,共 {chunk_count} 个数据块") | |
| raw_data = '\n'.join(raw_chunks) | |
| # 记录完整的原始上游响应 | |
| try: | |
| parsed_data = json.loads(raw_data) | |
| logger.debug_json("完整原始上游响应", parsed_data) | |
| except json.JSONDecodeError: | |
| logger.debug_large("完整原始上游响应", raw_data) | |
| if not raw_data: | |
| logger.error("上游返回空数据") | |
| # 触发错误备份 | |
| from src.utils.error_logger import save_error_snapshot | |
| save_error_snapshot( | |
| downstream_payload=self._request_context.get('downstream_payload', {}), | |
| upstream_payload=self._request_context.get('upstream_payload', {}), | |
| upstream_response="[EMPTY RESPONSE]", | |
| error_type="empty_response" | |
| ) | |
| raise EmptyResponseError("Upstream returned no data") | |
| # 使用独立解析函数 | |
| result = parse_upstream_data(raw_data) | |
| # 关键修复:在这里处理解析出的上游错误 | |
| if result["has_error"] and not result["parts"]: | |
| error_msg = result["error_message"] | |
| error_obj = result.get("error_obj") | |
| # 排除特定的、不需要备份的错误 | |
| is_auth_error = "Failed to verify action" in error_msg or "The caller does not have permission" in error_msg | |
| is_rate_limit = isinstance(error_obj, RateLimitError) or "resource has been exhausted" in error_msg.lower() or "quota" in error_msg.lower() | |
| if not is_auth_error and not is_rate_limit: | |
| logger.error(f"API 错误且无内容: {error_msg}") | |
| # 确定错误类型用于备份目录名 | |
| error_type = "api_error" | |
| if error_obj: | |
| # 如果有解析出的错误对象,使用其类名或 code | |
| error_type = f"upstream_{error_obj.code}_{type(error_obj).__name__}" | |
| # 触发错误备份 | |
| from src.utils.error_logger import save_error_snapshot | |
| save_error_snapshot( | |
| downstream_payload=self._request_context.get('downstream_payload', {}), | |
| upstream_payload=self._request_context.get('upstream_payload', {}), | |
| upstream_response=raw_data, | |
| error_type=error_type | |
| ) | |
| # 如果 parser 已经解析出了错误对象,直接抛出 | |
| if error_obj: | |
| raise error_obj | |
| # 降级处理:基于上游错误消息抛出错误 | |
| error_msg_lower = error_msg.lower() | |
| if "not found" in error_msg_lower: | |
| raise NotFoundError(message=error_msg) | |
| elif is_rate_limit: | |
| raise RateLimitError(message=error_msg) | |
| elif is_auth_error: | |
| from src.core.errors import AuthenticationError | |
| raise AuthenticationError( | |
| message=f"Authentication/Recaptcha failed: {error_msg}", | |
| details={"upstream_response": error_msg}, | |
| upstream_response=error_msg | |
| ) | |
| else: | |
| raise InvalidArgumentError(message=error_msg) | |
| finish_reason = result.get("finish_reason") or "STOP" | |
| if not result["parts"] and finish_reason == "STOP" and not result["has_error"]: | |
| if not result.get("prompt_feedback"): | |
| logger.error("上游返回空响应 (无 parts 且 finish_reason=STOP)") | |
| # 触发错误备份 | |
| from src.utils.error_logger import save_error_snapshot | |
| save_error_snapshot( | |
| downstream_payload=self._request_context.get('downstream_payload', {}), | |
| upstream_payload=self._request_context.get('upstream_payload', {}), | |
| upstream_response=raw_data, | |
| error_type="stop_no_content" | |
| ) | |
| # 快照将由 VcoreAIClient 统一保存 | |
| raise EmptyResponseError("Upstream returned empty response (STOP with no content/metadata)") | |
| # 计算 usage metadata | |
| usage_metadata = result.get("usage_metadata", {}) | |
| if not usage_metadata and self._request_context: | |
| try: | |
| downstream_payload = self._request_context.get('downstream_payload', {}) | |
| # 从请求上下文中提取输入内容 | |
| prompt_contents: list[dict[str, Any]] = [] | |
| if 'gemini_payload' in downstream_payload: | |
| gemini_payload = downstream_payload['gemini_payload'] | |
| if isinstance(gemini_payload, dict) and 'contents' in gemini_payload: | |
| prompt_contents = cast(list[dict[str, Any]], gemini_payload['contents']) | |
| # 计算 token 使用情况 | |
| usage_metadata = await calculate_usage_metadata( | |
| prompt_contents=prompt_contents, | |
| response_parts=result["parts"], | |
| request_context=self._request_context | |
| ) | |
| except Exception as e: | |
| logger.warning(f"计算 usage metadata 失败: {e}") | |
| usage_metadata = {} | |
| final_chunk = self._create_gemini_chunk( | |
| parts=result["parts"], | |
| finish_reason=result.get("finish_reason"), | |
| safety_ratings=result.get("safety_ratings", []), | |
| citation_metadata=result.get("citation_metadata", {}), | |
| grounding_metadata=result.get("grounding_metadata", {}), | |
| candidate_index=result.get("candidate_index", 0), | |
| prompt_feedback=result.get("prompt_feedback", {}), | |
| usage_metadata=usage_metadata, | |
| finish_message=result.get("finish_message"), | |
| token_count=result.get("token_count"), | |
| avg_logprobs=result.get("avg_logprobs"), | |
| logprobs_result=result.get("logprobs_result"), | |
| create_time=result.get("create_time"), | |
| model_version=result.get("model_version"), | |
| response_id=result.get("response_id"), | |
| model_status=result.get("model_status") | |
| ) | |
| process_time = time.time() - start_time | |
| logger.success(f"流式响应处理完成: 耗时={process_time:.3f}s, 完成原因={result.get('finish_reason', 'UNKNOWN')}") | |
| yield final_chunk | |
| self._actual_content_sent = True | |
| except VcoreError as e: | |
| if "Failed to verify action" in e.message or "The caller does not have permission" in e.message: | |
| from src.core.errors import AuthenticationError | |
| if not isinstance(e, AuthenticationError): | |
| raise AuthenticationError( | |
| message=f"Stream contained Authentication error: {e.message}", | |
| details={"upstream_response": e.upstream_response or e.message}, | |
| upstream_response=e.upstream_response or e.message | |
| ) | |
| else: | |
| logger.error(f"流处理 Vcore 错误: {e.message}") | |
| raise | |
| except Exception as e: | |
| logger.error(f"流处理未知错误: {e}") | |
| # 触发内部错误备份 | |
| from src.utils.error_logger import save_error_snapshot | |
| save_error_snapshot( | |
| downstream_payload=self._request_context.get('downstream_payload', {}), | |
| upstream_payload=self._request_context.get('upstream_payload', {}), | |
| upstream_response=str(e), | |
| error_type="internal_exception" | |
| ) | |
| raise InternalError(message=f"Unknown stream processing error: {e}") | |
| def get_stream_processor() -> StreamProcessor: | |
| """创建流处理器实例""" | |
| return StreamProcessor() | |