mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat: 添加访问令牌管理功能并升级至 0.2.4
- 新增 Management Token(访问令牌)功能,支持创建、更新、删除和管理 - 前端添加访问令牌管理页面,支持普通用户和管理员 - 后端实现完整的令牌生命周期管理 API - 添加数据库迁移脚本创建 management_tokens 表 - Nginx 配置添加 gzip 压缩,优化响应传输 - Dialog 组件添加 persistent 属性,防止意外关闭 - 为管理后台 API 添加详细的中文文档注释 - 简化多处类型注解,统一代码风格
This commit is contained in:
@@ -14,6 +14,7 @@ from sqlalchemy import (
|
||||
JSON,
|
||||
BigInteger,
|
||||
Boolean,
|
||||
CheckConstraint,
|
||||
Column,
|
||||
DateTime,
|
||||
Enum,
|
||||
@@ -101,6 +102,9 @@ class User(Base):
|
||||
|
||||
# 关系 - CASCADE delete: 让数据库处理级联删除
|
||||
api_keys = relationship("ApiKey", back_populates="user", cascade="all, delete-orphan")
|
||||
management_tokens = relationship(
|
||||
"ManagementToken", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
preferences = relationship(
|
||||
"UserPreference", back_populates="user", cascade="all, delete-orphan", passive_deletes=True
|
||||
)
|
||||
@@ -1229,6 +1233,192 @@ class AuditEventType(PyEnum):
|
||||
DATA_EXPORT = "data_export"
|
||||
CONFIG_CHANGED = "config_changed"
|
||||
|
||||
# Management Token 相关
|
||||
MANAGEMENT_TOKEN_CREATED = "management_token_created"
|
||||
MANAGEMENT_TOKEN_UPDATED = "management_token_updated"
|
||||
MANAGEMENT_TOKEN_DELETED = "management_token_deleted"
|
||||
MANAGEMENT_TOKEN_USED = "management_token_used"
|
||||
MANAGEMENT_TOKEN_EXPIRED = "management_token_expired"
|
||||
MANAGEMENT_TOKEN_IP_BLOCKED = "management_token_ip_blocked"
|
||||
|
||||
|
||||
class ManagementToken(Base):
|
||||
"""Management Token 模型 - 用于程序化管理 API 调用"""
|
||||
|
||||
__tablename__ = "management_tokens"
|
||||
|
||||
# Token 格式常量
|
||||
TOKEN_PREFIX = "ae_"
|
||||
TOKEN_RANDOM_LENGTH = 40
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), index=True)
|
||||
user_id = Column(String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
|
||||
# Token 信息
|
||||
token_hash = Column(String(64), unique=True, index=True, nullable=False) # SHA256 哈希
|
||||
token_prefix = Column(String(12), nullable=True) # Token 前缀用于显示(如 ae_xxxxxxxx)
|
||||
name = Column(String(100), nullable=False) # Token 名称
|
||||
description = Column(Text, nullable=True) # 描述
|
||||
|
||||
# IP 白名单(可选)
|
||||
allowed_ips = Column(JSON, nullable=True) # 允许的 IP 列表,NULL = 不限制
|
||||
# 格式: ["192.168.1.1", "10.0.0.0/24"]
|
||||
|
||||
# 有效期
|
||||
expires_at = Column(DateTime(timezone=True), nullable=True) # NULL = 永不过期
|
||||
|
||||
# 使用统计
|
||||
last_used_at = Column(DateTime(timezone=True), nullable=True)
|
||||
last_used_ip = Column(String(45), nullable=True)
|
||||
usage_count = Column(Integer, default=0) # 使用次数
|
||||
|
||||
# 状态
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
# 时间戳
|
||||
created_at = Column(
|
||||
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
|
||||
)
|
||||
updated_at = Column(
|
||||
DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# 关系
|
||||
user = relationship("User", back_populates="management_tokens")
|
||||
|
||||
# 索引和约束
|
||||
__table_args__ = (
|
||||
Index("idx_management_tokens_user_id", "user_id"),
|
||||
Index("idx_management_tokens_is_active", "is_active"),
|
||||
UniqueConstraint("user_id", "name", name="uq_management_tokens_user_name"),
|
||||
# IP 白名单必须为 NULL(不限制)或非空数组,禁止空数组
|
||||
# 注意:JSON 类型的 NULL 可能被序列化为 JSON 'null',需要同时处理
|
||||
CheckConstraint(
|
||||
"allowed_ips IS NULL OR allowed_ips::text = 'null' OR json_array_length(allowed_ips) > 0",
|
||||
name="check_allowed_ips_not_empty",
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def generate_token() -> str:
|
||||
"""生成 Management Token(使用加密安全的随机数)"""
|
||||
import string
|
||||
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
random_part = "".join(
|
||||
secrets.choice(alphabet) for _ in range(ManagementToken.TOKEN_RANDOM_LENGTH)
|
||||
)
|
||||
return f"{ManagementToken.TOKEN_PREFIX}{random_part}"
|
||||
|
||||
@staticmethod
|
||||
def hash_token(token: str) -> str:
|
||||
"""对 Token 进行 SHA256 哈希
|
||||
|
||||
安全性说明(当前方案是安全的):
|
||||
- Token 熵为 62^40(约 2^238),暴力破解在计算上不可行
|
||||
- 结合速率限制(默认 30 次/分钟/IP),在线攻击不可行
|
||||
- 不需要盐值:盐值用于防止彩虹表攻击,但 Token 是高熵随机值,
|
||||
不存在可预计算的"常见值",因此彩虹表攻击不适用
|
||||
"""
|
||||
return hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
def set_token(self, token: str) -> None:
|
||||
"""设置 Token(只存储哈希和前缀用于显示)"""
|
||||
self.token_hash = self.hash_token(token)
|
||||
# 存储前缀用于显示(ae_ + 4 个字符,共 7 个字符)
|
||||
self.token_prefix = token[:7] if len(token) > 7 else token
|
||||
|
||||
def get_display_token(self) -> str:
|
||||
"""获取用于显示的脱敏 Token(显示前缀 + 掩码)"""
|
||||
if self.token_prefix:
|
||||
return f"{self.token_prefix}...****"
|
||||
return "ae_****"
|
||||
|
||||
def is_ip_allowed(self, client_ip: str) -> bool:
|
||||
"""检查 IP 是否在白名单中
|
||||
|
||||
安全策略:
|
||||
- None 或不设置表示不限制(允许所有 IP)
|
||||
- 非空列表表示只允许列表中的 IP
|
||||
- 无效的白名单条目会被记录并跳过
|
||||
- 无效的客户端 IP 直接拒绝
|
||||
- 支持 IPv4 映射的 IPv6 地址规范化
|
||||
"""
|
||||
if self.allowed_ips is None:
|
||||
return True # 未设置白名单,不限制
|
||||
|
||||
import ipaddress
|
||||
|
||||
from src.core.logger import logger
|
||||
|
||||
# 防御性检查:空列表应该在数据库层被拒绝,但这里再检查一次
|
||||
if not self.allowed_ips:
|
||||
logger.critical(f"Management Token {self.id} - allowed_ips 为空列表(违反数据库约束)")
|
||||
return False # fail-safe
|
||||
|
||||
def normalize_ip(ip_str: str) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
|
||||
"""规范化 IP 地址,将 IPv4 映射的 IPv6 转换为 IPv4"""
|
||||
try:
|
||||
ip = ipaddress.ip_address(ip_str)
|
||||
if isinstance(ip, ipaddress.IPv6Address) and ip.ipv4_mapped:
|
||||
return ip.ipv4_mapped
|
||||
return ip
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
# 规范化客户端 IP
|
||||
client = normalize_ip(client_ip)
|
||||
if client is None:
|
||||
logger.error(f"Management Token {self.id} - 拒绝无效的客户端 IP: {client_ip}")
|
||||
return False
|
||||
|
||||
valid_entries = 0
|
||||
for allowed in self.allowed_ips:
|
||||
try:
|
||||
if "/" in allowed:
|
||||
# CIDR 格式
|
||||
network = ipaddress.ip_network(allowed, strict=False)
|
||||
valid_entries += 1
|
||||
if client in network:
|
||||
return True
|
||||
else:
|
||||
# 精确 IP
|
||||
allowed_ip = normalize_ip(allowed)
|
||||
if allowed_ip is None:
|
||||
logger.error(f"Management Token {self.id} - 白名单包含无效条目: {allowed}")
|
||||
continue
|
||||
valid_entries += 1
|
||||
if client == allowed_ip:
|
||||
return True
|
||||
except ValueError:
|
||||
logger.error(f"Management Token {self.id} - 白名单包含无效条目: {allowed}")
|
||||
continue
|
||||
|
||||
# 如果白名单全部无效,记录严重错误并拒绝
|
||||
if valid_entries == 0:
|
||||
logger.critical(f"Management Token {self.id} - 白名单全部无效,拒绝所有访问")
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
"""检查 Token 是否已过期(时区安全)"""
|
||||
if not self.expires_at:
|
||||
return False
|
||||
|
||||
expires = self.expires_at
|
||||
if expires.tzinfo is None:
|
||||
# 数据库中的时间应该有时区信息,如果没有则表示数据完整性问题
|
||||
from src.core.logger import logger
|
||||
|
||||
logger.error(f"Management Token {self.id} expires_at 缺少时区信息(数据完整性问题)")
|
||||
expires = expires.replace(tzinfo=timezone.utc)
|
||||
|
||||
return expires < datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""审计日志模型"""
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
Google Gemini API 请求/响应模型
|
||||
|
||||
支持 Gemini 3 Pro 及之前版本的 API 格式
|
||||
参考文档: https://ai.google.dev/gemini-api/docs/gemini-3
|
||||
支持 Gemini API 的请求/响应格式
|
||||
作为 API 网关,采用宽松类型定义以支持 API 新特性透传
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
@@ -17,282 +17,23 @@ class BaseModelWithExtras(BaseModel):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 内容块定义
|
||||
# 内容定义 - 使用宽松类型以支持透传
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GeminiTextPart(BaseModelWithExtras):
|
||||
"""文本内容块"""
|
||||
|
||||
text: str
|
||||
thought_signature: Optional[str] = Field(
|
||||
default=None,
|
||||
alias="thoughtSignature",
|
||||
description="Gemini 3 思维签名,用于维护多轮对话中的推理上下文",
|
||||
)
|
||||
|
||||
|
||||
class GeminiInlineData(BaseModelWithExtras):
|
||||
"""内联数据(图片等)"""
|
||||
|
||||
mime_type: str = Field(alias="mimeType")
|
||||
data: str # base64 encoded
|
||||
|
||||
|
||||
class GeminiMediaResolution(BaseModelWithExtras):
|
||||
"""
|
||||
媒体分辨率配置 (Gemini 3 新增)
|
||||
|
||||
控制图片/视频的处理分辨率:
|
||||
- media_resolution_low: 图片 280 tokens, 视频 70 tokens/帧
|
||||
- media_resolution_medium: 图片 560 tokens, 视频 70 tokens/帧
|
||||
- media_resolution_high: 图片 1120 tokens, 视频 280 tokens/帧
|
||||
"""
|
||||
|
||||
level: Literal["media_resolution_low", "media_resolution_medium", "media_resolution_high"]
|
||||
|
||||
|
||||
class GeminiFileData(BaseModelWithExtras):
|
||||
"""文件引用"""
|
||||
|
||||
mime_type: Optional[str] = Field(default=None, alias="mimeType")
|
||||
file_uri: str = Field(alias="fileUri")
|
||||
|
||||
|
||||
class GeminiFunctionCall(BaseModelWithExtras):
|
||||
"""函数调用"""
|
||||
|
||||
name: str
|
||||
args: Dict[str, Any]
|
||||
|
||||
|
||||
class GeminiFunctionResponse(BaseModelWithExtras):
|
||||
"""函数响应"""
|
||||
|
||||
name: str
|
||||
response: Dict[str, Any]
|
||||
|
||||
|
||||
class GeminiPart(BaseModelWithExtras):
|
||||
"""
|
||||
Gemini 内容部分 - 支持多种类型
|
||||
|
||||
可以是以下类型之一:
|
||||
- text: 文本内容
|
||||
- inline_data: 内联数据(图片等)
|
||||
- file_data: 文件引用
|
||||
- function_call: 函数调用
|
||||
- function_response: 函数响应
|
||||
|
||||
Gemini 3 新增:
|
||||
- thought_signature: 思维签名,用于维护推理上下文
|
||||
- media_resolution: 媒体分辨率配置
|
||||
"""
|
||||
|
||||
text: Optional[str] = None
|
||||
inline_data: Optional[GeminiInlineData] = Field(default=None, alias="inlineData")
|
||||
file_data: Optional[GeminiFileData] = Field(default=None, alias="fileData")
|
||||
function_call: Optional[GeminiFunctionCall] = Field(default=None, alias="functionCall")
|
||||
function_response: Optional[GeminiFunctionResponse] = Field(
|
||||
default=None, alias="functionResponse"
|
||||
)
|
||||
# Gemini 3 新增
|
||||
thought_signature: Optional[str] = Field(
|
||||
default=None,
|
||||
alias="thoughtSignature",
|
||||
description="思维签名,用于函数调用和图片生成的上下文保持",
|
||||
)
|
||||
media_resolution: Optional[GeminiMediaResolution] = Field(
|
||||
default=None, alias="mediaResolution", description="媒体分辨率配置"
|
||||
)
|
||||
|
||||
|
||||
class GeminiContent(BaseModelWithExtras):
|
||||
"""
|
||||
Gemini 消息内容
|
||||
|
||||
对应 Gemini API 的 Content 对象
|
||||
使用宽松类型定义,parts 接受任意字典列表以支持 API 新特性
|
||||
"""
|
||||
|
||||
role: Optional[Literal["user", "model"]] = None
|
||||
parts: List[Union[GeminiPart, Dict[str, Any]]]
|
||||
role: Optional[str] = None
|
||||
parts: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 配置定义
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GeminiImageConfig(BaseModelWithExtras):
|
||||
"""
|
||||
图片生成配置 (Gemini 3 Pro Image)
|
||||
|
||||
用于 gemini-3-pro-image-preview 模型
|
||||
"""
|
||||
|
||||
aspect_ratio: Optional[str] = Field(
|
||||
default=None, alias="aspectRatio", description="图片宽高比,如 '16:9', '1:1', '4:3'"
|
||||
)
|
||||
image_size: Optional[Literal["2K", "4K"]] = Field(
|
||||
default=None, alias="imageSize", description="图片尺寸: 2K 或 4K"
|
||||
)
|
||||
|
||||
|
||||
class GeminiGenerationConfig(BaseModelWithExtras):
|
||||
"""
|
||||
生成配置
|
||||
|
||||
Gemini 3 新增:
|
||||
- thinking_level: 思考深度 (low/medium/high)
|
||||
- response_json_schema: 结构化输出的 JSON Schema
|
||||
- image_config: 图片生成配置
|
||||
"""
|
||||
|
||||
temperature: Optional[float] = Field(
|
||||
default=None, description="采样温度,Gemini 3 建议保持默认值 1.0"
|
||||
)
|
||||
top_p: Optional[float] = Field(default=None, alias="topP")
|
||||
top_k: Optional[int] = Field(default=None, alias="topK")
|
||||
max_output_tokens: Optional[int] = Field(default=None, alias="maxOutputTokens")
|
||||
stop_sequences: Optional[List[str]] = Field(default=None, alias="stopSequences")
|
||||
candidate_count: Optional[int] = Field(default=None, alias="candidateCount")
|
||||
response_mime_type: Optional[str] = Field(default=None, alias="responseMimeType")
|
||||
response_schema: Optional[Dict[str, Any]] = Field(default=None, alias="responseSchema")
|
||||
# Gemini 3 新增
|
||||
response_json_schema: Optional[Dict[str, Any]] = Field(
|
||||
default=None, alias="responseJsonSchema", description="结构化输出的 JSON Schema"
|
||||
)
|
||||
thinking_level: Optional[Literal["low", "medium", "high"]] = Field(
|
||||
default=None,
|
||||
alias="thinkingLevel",
|
||||
description="Gemini 3 思考深度: low(快速), medium(平衡), high(深度推理,默认)",
|
||||
)
|
||||
image_config: Optional[GeminiImageConfig] = Field(
|
||||
default=None, alias="imageConfig", description="图片生成配置"
|
||||
)
|
||||
|
||||
|
||||
class GeminiSafetySettings(BaseModelWithExtras):
|
||||
"""安全设置"""
|
||||
|
||||
category: str
|
||||
threshold: str
|
||||
|
||||
|
||||
class GeminiFunctionDeclaration(BaseModelWithExtras):
|
||||
"""函数声明"""
|
||||
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
parameters: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class GeminiGoogleSearchTool(BaseModelWithExtras):
|
||||
"""Google Search 工具 (Gemini 3)"""
|
||||
|
||||
pass # 空对象表示启用
|
||||
|
||||
|
||||
class GeminiUrlContextTool(BaseModelWithExtras):
|
||||
"""URL Context 工具 (Gemini 3)"""
|
||||
|
||||
pass # 空对象表示启用
|
||||
|
||||
|
||||
class GeminiCodeExecutionTool(BaseModelWithExtras):
|
||||
"""代码执行工具"""
|
||||
|
||||
pass # 空对象表示启用
|
||||
|
||||
|
||||
class GeminiTool(BaseModelWithExtras):
|
||||
"""
|
||||
工具定义
|
||||
|
||||
支持的工具类型:
|
||||
- function_declarations: 自定义函数
|
||||
- code_execution: 代码执行
|
||||
- google_search: Google 搜索 (Gemini 3)
|
||||
- url_context: URL 上下文 (Gemini 3)
|
||||
"""
|
||||
|
||||
function_declarations: Optional[List[GeminiFunctionDeclaration]] = Field(
|
||||
default=None, alias="functionDeclarations"
|
||||
)
|
||||
code_execution: Optional[Dict[str, Any]] = Field(default=None, alias="codeExecution")
|
||||
# Gemini 3 内置工具
|
||||
google_search: Optional[Dict[str, Any]] = Field(
|
||||
default=None, alias="googleSearch", description="启用 Google 搜索工具"
|
||||
)
|
||||
url_context: Optional[Dict[str, Any]] = Field(
|
||||
default=None, alias="urlContext", description="启用 URL 上下文工具"
|
||||
)
|
||||
|
||||
|
||||
class GeminiToolConfig(BaseModelWithExtras):
|
||||
"""工具配置"""
|
||||
|
||||
function_calling_config: Optional[Dict[str, Any]] = Field(
|
||||
default=None, alias="functionCallingConfig"
|
||||
)
|
||||
|
||||
|
||||
class GeminiSystemInstruction(BaseModelWithExtras):
|
||||
"""系统指令"""
|
||||
|
||||
parts: List[Union[GeminiPart, Dict[str, Any]]]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 请求模型
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GeminiGenerateContentRequest(BaseModelWithExtras):
|
||||
"""
|
||||
Gemini generateContent 请求模型
|
||||
|
||||
对应 POST /v1beta/models/{model}:generateContent 端点
|
||||
"""
|
||||
|
||||
contents: List[GeminiContent]
|
||||
system_instruction: Optional[GeminiSystemInstruction] = Field(
|
||||
default=None, alias="systemInstruction"
|
||||
)
|
||||
tools: Optional[List[GeminiTool]] = None
|
||||
tool_config: Optional[GeminiToolConfig] = Field(default=None, alias="toolConfig")
|
||||
safety_settings: Optional[List[GeminiSafetySettings]] = Field(
|
||||
default=None, alias="safetySettings"
|
||||
)
|
||||
generation_config: Optional[GeminiGenerationConfig] = Field(
|
||||
default=None, alias="generationConfig"
|
||||
)
|
||||
|
||||
|
||||
class GeminiStreamGenerateContentRequest(BaseModelWithExtras):
|
||||
"""
|
||||
Gemini streamGenerateContent 请求模型
|
||||
|
||||
对应 POST /v1beta/models/{model}:streamGenerateContent 端点
|
||||
与 generateContent 相同,但返回流式响应
|
||||
"""
|
||||
|
||||
contents: List[GeminiContent]
|
||||
system_instruction: Optional[GeminiSystemInstruction] = Field(
|
||||
default=None, alias="systemInstruction"
|
||||
)
|
||||
tools: Optional[List[GeminiTool]] = None
|
||||
tool_config: Optional[GeminiToolConfig] = Field(default=None, alias="toolConfig")
|
||||
safety_settings: Optional[List[GeminiSafetySettings]] = Field(
|
||||
default=None, alias="safetySettings"
|
||||
)
|
||||
generation_config: Optional[GeminiGenerationConfig] = Field(
|
||||
default=None, alias="generationConfig"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 统一请求模型(用于内部处理)
|
||||
# 请求模型 - 只定义网关需要的字段,其余透传
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -306,158 +47,31 @@ class GeminiRequest(BaseModelWithExtras):
|
||||
- generateContent - 非流式
|
||||
- streamGenerateContent - 流式
|
||||
请求体中不应包含 stream 字段
|
||||
|
||||
采用宽松类型定义,除必要字段外全部透传
|
||||
"""
|
||||
|
||||
model: Optional[str] = Field(default=None, description="模型名称,从 URL 路径提取(内部使用)")
|
||||
contents: List[GeminiContent]
|
||||
system_instruction: Optional[GeminiSystemInstruction] = Field(
|
||||
default=None, alias="systemInstruction"
|
||||
)
|
||||
tools: Optional[List[GeminiTool]] = None
|
||||
tool_config: Optional[GeminiToolConfig] = Field(default=None, alias="toolConfig")
|
||||
safety_settings: Optional[List[GeminiSafetySettings]] = Field(
|
||||
default=None, alias="safetySettings"
|
||||
)
|
||||
generation_config: Optional[GeminiGenerationConfig] = Field(
|
||||
default=None, alias="generationConfig"
|
||||
)
|
||||
# 以下字段全部使用 Dict[str, Any] 透传,不做结构验证
|
||||
system_instruction: Optional[Dict[str, Any]] = Field(default=None, alias="systemInstruction")
|
||||
tools: Optional[List[Dict[str, Any]]] = None
|
||||
tool_config: Optional[Dict[str, Any]] = Field(default=None, alias="toolConfig")
|
||||
safety_settings: Optional[List[Dict[str, Any]]] = Field(default=None, alias="safetySettings")
|
||||
generation_config: Optional[Dict[str, Any]] = Field(default=None, alias="generationConfig")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 响应模型
|
||||
# 响应模型 - 用于解析上游响应提取必要信息(如 usage)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GeminiUsageMetadata(BaseModelWithExtras):
|
||||
"""Token 使用量"""
|
||||
"""Token 使用量 - 用于计费统计"""
|
||||
|
||||
prompt_token_count: int = Field(default=0, alias="promptTokenCount")
|
||||
candidates_token_count: int = Field(default=0, alias="candidatesTokenCount")
|
||||
total_token_count: int = Field(default=0, alias="totalTokenCount")
|
||||
cached_content_token_count: Optional[int] = Field(default=None, alias="cachedContentTokenCount")
|
||||
|
||||
|
||||
class GeminiSafetyRating(BaseModelWithExtras):
|
||||
"""安全评级"""
|
||||
|
||||
category: str
|
||||
probability: str
|
||||
blocked: Optional[bool] = None
|
||||
|
||||
|
||||
class GeminiCitationSource(BaseModelWithExtras):
|
||||
"""引用来源"""
|
||||
|
||||
start_index: Optional[int] = Field(default=None, alias="startIndex")
|
||||
end_index: Optional[int] = Field(default=None, alias="endIndex")
|
||||
uri: Optional[str] = None
|
||||
license: Optional[str] = None
|
||||
|
||||
|
||||
class GeminiCitationMetadata(BaseModelWithExtras):
|
||||
"""引用元数据"""
|
||||
|
||||
citation_sources: Optional[List[GeminiCitationSource]] = Field(
|
||||
default=None, alias="citationSources"
|
||||
)
|
||||
|
||||
|
||||
class GeminiGroundingMetadata(BaseModelWithExtras):
|
||||
"""
|
||||
Grounding 元数据 (Gemini 3)
|
||||
|
||||
当使用 Google Search 工具时返回
|
||||
"""
|
||||
|
||||
search_entry_point: Optional[Dict[str, Any]] = Field(default=None, alias="searchEntryPoint")
|
||||
grounding_chunks: Optional[List[Dict[str, Any]]] = Field(default=None, alias="groundingChunks")
|
||||
grounding_supports: Optional[List[Dict[str, Any]]] = Field(
|
||||
default=None, alias="groundingSupports"
|
||||
)
|
||||
web_search_queries: Optional[List[str]] = Field(default=None, alias="webSearchQueries")
|
||||
|
||||
|
||||
class GeminiCandidate(BaseModelWithExtras):
|
||||
"""候选响应"""
|
||||
|
||||
content: Optional[GeminiContent] = None
|
||||
finish_reason: Optional[str] = Field(default=None, alias="finishReason")
|
||||
safety_ratings: Optional[List[GeminiSafetyRating]] = Field(default=None, alias="safetyRatings")
|
||||
citation_metadata: Optional[GeminiCitationMetadata] = Field(
|
||||
default=None, alias="citationMetadata"
|
||||
)
|
||||
grounding_metadata: Optional[GeminiGroundingMetadata] = Field(
|
||||
default=None, alias="groundingMetadata"
|
||||
)
|
||||
token_count: Optional[int] = Field(default=None, alias="tokenCount")
|
||||
index: Optional[int] = None
|
||||
|
||||
|
||||
class GeminiPromptFeedback(BaseModelWithExtras):
|
||||
"""提示反馈"""
|
||||
|
||||
block_reason: Optional[str] = Field(default=None, alias="blockReason")
|
||||
safety_ratings: Optional[List[GeminiSafetyRating]] = Field(default=None, alias="safetyRatings")
|
||||
|
||||
|
||||
class GeminiGenerateContentResponse(BaseModelWithExtras):
|
||||
"""
|
||||
Gemini generateContent 响应模型
|
||||
|
||||
对应 generateContent 端点的响应体
|
||||
"""
|
||||
|
||||
candidates: Optional[List[GeminiCandidate]] = None
|
||||
prompt_feedback: Optional[GeminiPromptFeedback] = Field(default=None, alias="promptFeedback")
|
||||
usage_metadata: Optional[GeminiUsageMetadata] = Field(default=None, alias="usageMetadata")
|
||||
model_version: Optional[str] = Field(default=None, alias="modelVersion")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 流式响应模型
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GeminiStreamChunk(BaseModelWithExtras):
|
||||
"""
|
||||
Gemini 流式响应块
|
||||
|
||||
流式响应中的单个数据块,结构与完整响应相同
|
||||
"""
|
||||
|
||||
candidates: Optional[List[GeminiCandidate]] = None
|
||||
prompt_feedback: Optional[GeminiPromptFeedback] = Field(default=None, alias="promptFeedback")
|
||||
usage_metadata: Optional[GeminiUsageMetadata] = Field(default=None, alias="usageMetadata")
|
||||
model_version: Optional[str] = Field(default=None, alias="modelVersion")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 错误响应
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class GeminiErrorDetail(BaseModelWithExtras):
|
||||
"""错误详情"""
|
||||
|
||||
type: Optional[str] = Field(default=None, alias="@type")
|
||||
reason: Optional[str] = None
|
||||
domain: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class GeminiError(BaseModelWithExtras):
|
||||
"""错误信息"""
|
||||
|
||||
code: int
|
||||
message: str
|
||||
status: str
|
||||
details: Optional[List[GeminiErrorDetail]] = None
|
||||
|
||||
|
||||
class GeminiErrorResponse(BaseModelWithExtras):
|
||||
"""错误响应"""
|
||||
|
||||
error: GeminiError
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user