使用 aiogram 3.x 将 Gemini API 异步集成到 Telegram 机器人:实用指南
当你的 Telegram 机器人突然因为 Gemini API 的 429 错误而停止响应时,这不仅仅是个 bug——这是技术债。本文将一步步拆解:如何正确地将 Gemini 集成到基于 aiogram 3.x 的异步机器人中,避免阻塞事件循环,绕过 RPM/TPM 限制,并实现函数调用而不牺牲性能。
架构:从简单处理器到生产级解决方案
通过 Gemini 的基本用户-机器人交互流程看起来很简单:请求 → 处理 → 响应。但现实场景需要额外的层。不正确包装就异步调用同步 API,会冻结 aiogram 事件循环,尤其在高负载下。因此关键组件是:
- Asyncio Queue — 用于缓冲传入请求
- Rate Limiter — 遵守 Gemini 限制(5–60 RPM)
- Response Cache — 为重复请求节省令牌
- Streaming Handler — 绕过 Telegram 超时(10 秒)
即使付费计划,Gemini API 也强制执行严格限制:最大响应延迟可达 15 秒,免费层仅限 5 请求/分钟。没有队列和缓存,你的用户体验将彻底崩坏。
客户端设置:在异步环境中使用同步 SDK
Google GenAI SDK 尽管社区满怀期望,仍是同步的。这意味着在 aiogram 处理器中直接调用 generate_content() 会阻塞整个事件循环。解决方案:用 asyncio.to_thread 包装调用,或使用 run_in_executor。
import asyncio
from google import genai
class AsyncGeminiClient:
def __init__(self, model: str = "gemini-3-flash-preview"):
self.client = genai.Client()
self.model = model
async def generate(self, prompt: str) -> str:
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(
None,
self._sync_generate,
prompt
)
return response
def _sync_generate(self, prompt: str) -> str:
response = self.client.models.generate_content(
model=self.model,
contents=prompt
)
return response.text
这是一个最小化可工作的包装器。生产环境中,添加异常处理和执行指标。
与 aiogram 集成:从 /start 到流式响应
基本处理器需要做的不仅仅是接受消息:还要管理状态显示“typing”、拆分长响应,并缓存结果。Telegram 消息长度上限为 4096 字符——忽略这一点会导致截断。
@router.message()
async def handle_message(message: types.Message):
await message.bot.send_chat_action(chat_id=message.chat.id, action="typing")
try:
response = await gemini.generate(message.text)
if len(response) > 4000:
for i in range(0, len(response), 4000):
await message.answer(response[i:i+4000])
else:
await message.answer(response)
except Exception as e:
logging.error(f"Gemini error: {e}")
await message.answer("Chto-then poshlo not so. Poprobuyte pozzhe.")
对于长时间运行的请求,使用流式处理——随着文本生成更新单条消息。这能规避超时并提升用户体验。
函数调用:Gemini 变身智能代理
函数调用让模型基于请求上下文调用外部 API。例如,用户说“明天上午 3 点预约会议”,Gemini 可以解析参数并调用你的 schedule_meeting 函数。
关键步骤:
- 用 JSON Schema 格式描述函数——让模型理解其签名。
- 在
GenerateContentConfig中作为tools传递描述。 - 在响应中处理
function_call并执行实际调用。 - 将结果反馈回对话作为对话的一部分。
示例函数声明:
schedule_meeting_function = {
"name": "schedule_meeting",
"description": "Withzdayot meetingsu with ukazannymi uchastnikami",
"parameters": {
"type": "object",
"properties": {
"attendees": {"type": "array", "items": {"type": "string"}},
"date": {"type": "string"},
"time": {"type": "string"},
"topic": {"type": "string"}
},
"required": ["attendees", "date", "time", "topic"]
}
}
这将静态机器人转变为能与外部系统交互的动态助手。
缓存和限流:防止破产和封禁
Gemini 按令牌计费。没有缓存,你会为每个重复问题付费。用提示的 MD5 哈希加 TTL 的简单内存缓存就能解决:
class SimpleCache:
def __init__(self, ttl_seconds: int = 3600):
self._cache = {}
self._ttl = ttl_seconds
def get(self, key: str) -> Optional[str]:
# check TTL and vozvrat values
def set(self, key: str, value: str):
self._cache[key] = (value, datetime.now())
@staticmethod
def hash_prompt(prompt: str) -> str:
return hashlib.md5(prompt.lower().strip().encode()).hexdigest()
即使付费层,限流也必不可少。以异步上下文管理器或中间件实现:
class AsyncRateLimiter:
def __init__(self, max_requests: int, time_window: int = 60):
self.max_requests = max_requests
self.time_window = time_window
self.requests = []
self._lock = asyncio.Lock()
async def acquire(self):
async with self._lock:
now = time.time()
self.requests = [t for t in self.requests if now - t < self.time_window]
if len(self.requests) >= self.max_requests:
sleep_time = self.time_window - (now - self.requests[0])
await asyncio.sleep(sleep_time + 0.1)
return await self.acquire()
self.requests.append(now)
常见错误及规避方法
每个人都会踩的三大经典坑:
- 错误 429 (RESOURCE_EXHAUSTED) — 因超过 RPM、TPM 或 RPD 触发。解决:本地限流器 + 指数退避。
- Telegram 超时 — 服务器等待响应 10 秒。如果 Gemini 慢,用流式或发送中间消息。
- 过时模型 — Google 定期停用旧版本。2026 年 3 月,Pro 模型转为仅订阅。部署前始终验证模型可用性。
关键要点:
- 同步 Gemini SDK 需要
run_in_executor包装——否则会阻塞事件循环。 - 在机器人端实现限流,不要依赖 API。
- 流式是绕过 Telegram 10 秒超时的唯一方法,用于长响应。
- 缓存能省钱并降低重复请求延迟。
- 函数调用让你的机器人从文本生成器进化为完整代理。
采用这些实践能减少生产事故,并随着负载增长保持稳定。在架构上别偷懒——在令牌上省钱吧。
— Editorial Team
暂无评论。