把 Grok 接到即时通讯群里,用 @bot 的方式把任务丢给它,是近期效率工具圈里讨论比较多的一种做法。这里的 Grok 是 xAI 推出的对话模型,侧重长上下文、代码理解和自然语言问答;而 @bot 不是某个具体产品,而是一种交互约定:在群聊或工作群里输入@机器人名 你的问题,机器人只处理被 @ 的那条消息,其余聊天不受干扰。
这篇文章就从零搭建一个 Grok @bot 效率助手。它接收群里 @ 机器人的消息,自动调用 Grok 模型生成回答,再把结果发回群聊。核心代码量不大,但涉及模型调用、上下文管理、IM Webhook 适配、限流和排错,适合作为企业微信群、飞书、钉钉等场景的参考实现。
整个链路不复杂,但每一步都有坑:模型 API 地址配错、消息体解析不对、上下文越拼越长、Webhook 重复推送,都会让机器人表现得很不可靠。因此文章不只写能跑的代码,还会解释为什么要这样设计消息结构、为什么用会话维度管理上下文、以及出现问题时应该按什么顺序排查。
1. 先理解 Grok 与 @bot 的工作方式
1.1 Grok 不是一个普通聊天框,而是一个可编程模型
Grok 是由 xAI 推出的对话模型,在社区讨论中最常被提到的特点是:长上下文支持、代码理解能力强、回答风格偏向直接和高效。很多开发者把它当成“对话式编程助手”来用,比如让它解释一段复杂代码、生成单元测试、总结会议纪要、改写技术文档。
模型本身可以通过 Web 端聊天窗口体验,也可以调用 API 来编程接入。网页版适合临时验证想法,但机器人场景需要的是稳定、可重复调用的 API 凭证。手动把问题复制到网页再粘贴回答回群聊,本质上是人工搬运,不具备规模化效率。真正的效率提升来自 API 直连:群里 @ 一下,机器人自动完成调用和回复。
需要说明的是,Grok 的版本迭代较快,社区中已经能见到不同版本号,例如 4.6 或后续更新版本。实际接入时,具体能使用哪个模型版本、模型标识符是什么,要以你拿到的 API 文档和账号权限为准,不要照抄网上的模型名。
1.2 @bot 模式解决什么问题
在群里使用机器人,最怕两种体验:
- 机器人把所有人的消息都当成指令,造成大量误触发。
- 用户需要在特定对话框或后台页面里操作,从 IM 切换到另一个工具,打断工作流。
@bot 模式通过“显式触发”解决这两个问题。用户输入@机器人名时,机器人知道这是一条待处理指令;没有 @ 的消息,机器人直接忽略。这样既减少了误触发,也让用户停留在熟悉的 IM 环境中,不需要切换到浏览器或其他页面。
在企业微信、飞书、钉钉等平台中,机器人还会附带发送者 ID、群 ID、消息 ID 等信息。利用这些字段,可以实现更细粒度的权限控制:只有指定群可以调用、只有指定用户可以用、每条消息只能处理一次。
1.3 一条消息从群聊到 Grok 的完整链路
把整个流程拆开看,Grok @bot 的链路大致可以分为五段:
- IM 平台推送 Webhook 消息到我们的服务。
- 服务解析消息体,判断是否包含 @ 机器人。
- 需要调用 Grok 时,把当前会话的历史消息整理成 messages 数组。
- 请求 Grok 的 OpenAI 兼容接口,拿到回复文本。
- 把回复发送回对应群聊或会话。
其中第一段和第五段依赖具体 IM 平台的开放能力,不同平台的字段名和鉴权方式不同;中间三段是通用的,可以用一套核心逻辑实现。这也是为什么项目里最好把“消息解析”“业务处理”“回复发送”拆开,避免换平台时重写全部代码。
| 链路节点 | 依赖对象 | 典型问题 |
|---|---|---|
| 消息接收 | IM 平台 Webhook | 验签失败、重复推送 |
| 消息解析 | 平台消息体字段 | 解析错字段、@ 识别不到 |
| 核心处理 | Grok API 调用 | 401、404、超时、限流 |
| 上下文管理 | 会话存储 | 内存增长、串群 |
| 回复发送 | 平台发送 API | 频率限制、消息格式错误 |
2. 环境准备:把最小骨架搭起来
2.1 技术选型:Python + OpenAI SDK + FastAPI
实现一个 Grok @bot 服务,不需要引入重量级框架。下面这套技术栈足够覆盖大多数场景,并且在社区资料最多:
- Python 3.10 或更高版本。
openaiPython SDK,用来调用 Grok 的 OpenAI 兼容接口。fastapi和uvicorn,提供 HTTP Webhook 接收服务。pydantic,用于配置加载和消息体校验。python-dotenv,方便从.env文件读取密钥,避免把 API Key 写死在代码中。
如果你要接入的具体 IM 平台有官方 SDK,也可以按需引入,但核心调用逻辑应该和平台 SDK 解耦。因为 Grok 的接口是 OpenAI 兼容的,只要平台 SDK 和网络请求能打通,后续替换模型或调整逻辑都很方便。
安装依赖:
python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate pip install --upgrade pip pip install openai fastapi uvicorn pydantic python-dotenv注意:以上依赖只保证核心服务运行。如果后续需要写入 Word 文档,还要安装
python-docx;如果接入飞书或企业微信,再按平台要求补充对应 SDK。
2.2 准备 Grok API 凭证与基础配置
接入 Grok API 需要三个信息:API Key、Base URL、模型名。三者都必须来自官方渠道或你所在团队的统一 API 入口,不要使用来源不明或来路有风险的密钥。
推荐使用环境变量管理配置,而不是硬编码在代码里。这样不同环境可以复用同一套代码,只是.env文件不同。
创建.env文件:
GROK_API_KEY=your_api_key_here GROK_BASE_URL=https://api.x.ai/v1 GROK_MODEL=grok-3-latest GROK_REQUEST_TIMEOUT=60 GROK_MAX_CONTEXT_TURNS=10 GROK_RATE_LIMIT_SECONDS=1.0其中GROK_MODEL的实际取值要以官方文档为准。社区讨论中常见的模型版本变化很快,不要假设某个版本号永远可用。如果你的团队通过统一的 API 网关暴露模型服务,GROK_BASE_URL就替换成网关地址,这样可以在网关层统一处理密钥、审计和限流。
任何时候都不要把 API Key 提交到 Git 仓库。.env文件加入.gitignore是最基本的保护措施。
echo ".env" >> .gitignore2.3 项目目录结构
项目保持简单,但目录要清晰。一个可运行的 Grok @bot 服务至少包含以下文件:
grok-bot/ ├── .env ├── .gitignore ├── config.py # 读取环境变量 ├── grok_client.py # 封装 Grok API 调用 ├── bot.py # 消息处理与上下文管理 ├── adapter.py # 各 IM 平台消息解析适配 ├── app.py # FastAPI Webhook 入口 ├── cli.py # 本地命令行测试入口 └── requirements.txtconfig.py是第一个要写的文件,它把所有配置集中读取,避免在业务代码里到处读os.getenv。
import os from dotenv import load_dotenv load_dotenv() GROK_API_KEY = os.getenv("GROK_API_KEY", "") GROK_BASE_URL = os.getenv("GROK_BASE_URL", "https://api.x.ai/v1") GROK_MODEL = os.getenv("GROK_MODEL", "grok-3-latest") GROK_REQUEST_TIMEOUT = float(os.getenv("GROK_REQUEST_TIMEOUT", "60")) GROK_MAX_CONTEXT_TURNS = int(os.getenv("GROK_MAX_CONTEXT_TURNS", "10")) GROK_MAX_SESSIONS = int(os.getenv("GROK_MAX_SESSIONS", "200")) GROK_RATE_LIMIT_SECONDS = float(os.getenv("GROK_RATE_LIMIT_SECONDS", "1.0"))这里把默认值都写进了代码,但实际运行时优先读取环境变量。这样做的好处是:本地调试改.env,测试或生产环境用真实环境变量,代码不用动。
2.4 确认网络可达性
在写业务代码之前,先确认运行环境能否访问 Grok API 服务地址。不同公司的网络策略不一样,有些环境访问外部 API 需要配置允许列表,有些环境必须通过内网网关。
最简单的检查方式是直接请求一次 base URL 下的模型列表接口:
curl --request GET \ --url https://api.x.ai/v1/models \ --header "Authorization: Bearer $GROK_API_KEY"如果返回 JSON 数组或对象,说明网络和密钥基本可用;如果返回超时或连接失败,先解决网络问题再继续写代码。排查顺序应该是:网络连通性、证书、域名解析、防火墙、API Key 权限,而不是一上来就怀疑代码。
3. 核心实现:从 @ 触发到 Grok 回复
3.1 定义统一消息对象
不同 IM 平台的消息体字段差异很大:飞书叫open_id,企业微信叫userid,钉钉叫senderNick。如果业务代码直接依赖这些字段,换平台时就要大改。
所以先定义一个统一的Message数据结构,所有适配器都把平台消息转换成这个结构。
from dataclasses import dataclass, field from datetime import datetime @dataclass class Message: message_id: str chat_id: str sender_id: str sender_name: str = "" text: str = "" is_at_bot: bool = False raw: dict = field(default_factory=dict) created_at: datetime = field(default_factory=datetime.now)这里的message_id用于幂等去重,chat_id用于区分不同会话,sender_id用于权限控制,is_at_bot表示是否被 @。通过这个统一对象,核心业务代码就不需要感知具体平台。
3.2 封装 Grok 客户端
Grok 提供 OpenAI 兼容接口,因此可以直接使用openaiSDK。封装一层客户端的好处是:调用逻辑集中在一个文件里,后续增加日志、重试、多模型切换都方便。
from openai import OpenAI from config import ( GROK_API_KEY, GROK_BASE_URL, GROK_MODEL, GROK_REQUEST_TIMEOUT, ) class GrokClient: def __init__( self, api_key: str = GROK_API_KEY, base_url: str = GROK_BASE_URL, model: str = GROK_MODEL, timeout: float = GROK_REQUEST_TIMEOUT, ): if not api_key: raise ValueError("GROK_API_KEY 未配置") self.client = OpenAI( api_key=api_key, base_url=base_url, timeout=timeout, ) self.model = model def chat(self, messages: list[dict]) -> str: response = self.client.chat.completions.create( model=self.model, messages=messages, temperature=0.7, ) content = response.choices[0].message.content if content is None: raise RuntimeError("Grok 返回了空内容") return content几个关键点:
temperature=0.7是一个相对均衡的值。技术问答类任务可以更低,创意写作可以更高,具体按场景调。choices[0].message.content是标准 OpenAI 回复格式,兼容接口一般都保持这个结构。- 返回内容为空时主动抛异常,避免下游拿着
None继续处理。
3.3 上下文窗口管理
Grok 虽然支持长上下文,但 bot 场景不能无限累积历史消息。每一轮对话都把所有历史塞进去,既浪费 token,也容易让模型被陈旧信息干扰。
这里使用一个简单的ContextManager,以chat_id作为会话维度,保留最近 N 轮消息。为了避免内存无限增长,还需要限制最多会话数,超过后按 LRU 策略淘汰最旧的会话。
from collections import OrderedDict SYSTEM_PROMPT = ( "你是一个效率助手,运行在 IM 群里。" "用户通过 @机器人 的方式向你提问,你的回答要简洁、准确、可执行。" "回答代码问题时,要给出完整可运行的示例。" ) class ContextManager: def __init__(self, max_turns: int = 10, max_sessions: int = 200): self.max_turns = max_turns self.max_sessions = max_sessions self.sessions: OrderedDict[str, list[dict]] = OrderedDict() def _ensure_session(self, chat_id: str) -> list[dict]: if chat_id not in self.sessions: if len(self.sessions) >= self.max_sessions: self.sessions.popitem(last=False) self.sessions[chat_id] = [ {"role": "system", "content": SYSTEM_PROMPT} ] return self.sessions[chat_id] def add_user_message(self, chat_id: str, content: str) -> None: messages = self._ensure_session(chat_id) messages.append({"role": "user", "content": content}) self._trim(chat_id) self.sessions.move_to_end(chat_id) def add_assistant_message(self, chat_id: str, content: str) -> None: messages = self._ensure_session(chat_id) messages.append({"role": "assistant", "content": content}) self._trim(chat_id) self.sessions.move_to_end(chat_id) def get_messages(self, chat_id: str) -> list[dict]: return self._ensure_session(chat_id).copy() def clear(self, chat_id: str) -> None: self.sessions.pop(chat_id, None) def _trim(self, chat_id: str) -> None: messages = self.sessions.get(chat_id, []) if len(messages) > self.max_turns * 2 + 1: keep_count = self.max_turns * 2 + 1 messages[:] = [messages[0]] + messages[-keep_count:]这里的max_turns * 2 + 1表示系统提示词之外保留用户消息和助手消息,每一轮占两条。实际项目可以根据 token 预算调整,更精细的做法是按字符数截断,但按轮数更直观,也更容易理解。
3.4 限流、超时和异常保护
群里如果有多个用户同时 @ 机器人,服务可能一下子收到大量请求。Grok API 侧有速率限制,本地也要做基本限流,避免单点被打满。
用一个简单的时间窗口限流器:
import time import threading class RateLimiter: def __init__(self, min_interval: float = 1.0): self.min_interval = min_interval self.last_call_at: dict[str, float] = {} self.lock = threading.Lock() def is_limited(self, key: str) -> bool: now = time.time() with self.lock: last = self.last_call_at.get(key, 0.0) if now - last < self.min_interval: return True self.last_call_at[key] = now return False把限流粒度放在chat_id或sender_id维度都有各自考量。放在chat_id,同一个群不会被单个人刷爆;放在sender_id,能避免某个用户在多个群里同时刷。生产环境可以两个都做。
核心BotHandler把这些组件串起来:
from message import Message class BotHandler: def __init__(self, grok_client, context_manager, rate_limiter): self.grok_client = grok_client self.context_manager = context_manager self.rate_limiter = rate_limiter def handle(self, message: Message) -> str: if not message.is_at_bot: return "" if not message.text.strip(): return "请带上问题,例如:@机器人 总结这段代码" if self.rate_limiter.is_limited(message.chat_id): return "请求太频繁,请稍等片刻再试。" try: history = self.context_manager.get_messages(message.chat_id) history.append({"role": "user", "content": message.text}) reply = self.grok_client.chat(history) self.context_manager.add_user_message(message.chat_id, message.text) self.context_manager.add_assistant_message(message.chat_id, reply) return reply except Exception as exc: return f"处理失败:{exc}"注意handle方法内部捕获了所有异常,并把错误信息返回给群聊。这样用户能第一时间知道请求出了问题,而不是机器人毫无反应。但错误信息里不要暴露完整 API Key 或敏感参数。
4. 接入 IM:用 Webhook 挂到群里
4.1 用 FastAPI 暴露 Webhook
核心逻辑完成后,需要对外开放一个 HTTP 接口接收 IM 平台的 Webhook 推送。FastAPI 可以很简洁地实现这一点。
from fastapi import FastAPI, Request from adapter import parse_platform_message from bot import BotHandler app = FastAPI() @app.post("/webhook") async def webhook(request: Request): raw = await request.json() message = parse_platform_message(raw) if message is None: return {"code": 0, "msg": "ignored"} if message.message_id in handled_message_ids: return {"code": 0, "msg": "duplicated"} reply = bot_handler.handle(message) if reply: send_reply(message.chat_id, reply) return {"code": 0, "msg": "ok"}这里省略了bot_handler、handled_message_ids和send_reply的完整定义,实际项目中可以放在单独模块里。handled_message_ids用带过期时间的内存集合实现,用于应对平台重复推送。
4.2 平台适配器原则
不同平台的 Webhook 消息体差异非常大。以飞书为例,消息事件的大致结构是:
{ "schema": "2.0", "header": { "event_type": "im.message.receive_v1", "event_id": "xxxx" }, "event": { "message": { "message_id": "om_xxx", "chat_id": "oc_xxx", "content": "{\"text\":\"@_user_1 你好\"}" }, "sender": { "sender_id": { "open_id": "ou_xxx" }, "sender_type": "user" } } }企业微信的格式则完全不同,事件里包含ToUserName、FromUserName、MsgType、Content等字段。因此适配器要负责两件事:
- 把平台原始消息转换成统一的
Message对象。 - 判断消息中是否包含 @ 机器人。
以飞书消息为例,content字段是一个 JSON 字符串,文本里通常包含@_user_1这类占位符。需要把@_user_1替换成空字符串,得到纯净的用户问题,同时判断是否真的 @ 了机器人。
import json from message import Message def parse_platform_message(raw: dict) -> Message | None: try: event = raw.get("event", {}) message = event.get("message", {}) sender = event.get("sender", {}) content_raw = message.get("content", "{}") if isinstance(content_raw, str): content = json.loads(content_raw) else: content = content_raw text = content.get("text", "") message_id = message.get("message_id", "") chat_id = message.get("chat_id", "") sender_id = sender.get("sender_id", {}).get("open_id", "") is_at_bot = "@_user_1" in text clean_text = text.replace("@_user_1", "").strip() return Message( message_id=message_id, chat_id=chat_id, sender_id=sender_id, text=clean_text, is_at_bot=is_at_bot, raw=raw, ) except Exception: return None实际项目中,@_user_1是动态的机器人自身 open_id,不能写死。需要先从平台获取机器人身份,再判断文本里是否包含该 ID。上面的@_user_1只是用于说明思路的占位符。
4.3 本地命令行测试入口
在没有接入真实 IM 平台前,可以先写一个命令行测试入口,用来验证 Grok 调用和上下文管理是否正常。这样调试时不用反复在群里发消息。
import sys from message import Message from bot import BotHandler from grok_client import GrokClient from context_manager import ContextManager from rate_limiter import RateLimiter def build_bot() -> BotHandler: grok_client = GrokClient() context_manager = ContextManager() rate_limiter = RateLimiter() return BotHandler(grok_client, context_manager, rate_limiter) if __name__ == "__main__": bot = build_bot() text = " ".join(sys.argv[1:]) or "你好,请介绍一下你自己" msg = Message( message_id="cli-1", chat_id="cli-session", sender_id="cli-user", sender_name="cli", text=text, is_at_bot=True, ) reply = bot.handle(msg) print(reply)这样执行python cli.py 帮我写一个 FastAPI 示例就能直接看到回复,不需要配任何 IM 平台。
5. 运行验证:命令行先行,IM 后验
5.1 启动本地服务
命令行验证通过后,再启动 Webhook 服务:
uvicorn app:app --host 0.0.0.0 --port 8000生产环境不需要加--reload,本地开发可以加。启动后看到类似日志说明服务正常:
INFO: Uvicorn running on http://0.0.0.0:8000 INFO: Application startup complete.5.2 命令行验证“最小闭环”
执行:
python cli.py 用 Python 写一个读取 CSV 文件并打印前 5 行的示例正常情况会直接打印 Grok 生成的代码。这一步验证的是:API Key、Base URL、模型名、API 调用链路都正确。
接着再执行一次:
python cli.py 刚才的代码如果要处理大文件,应该怎么改如果上下文管理正常,第二次提问应该能理解“刚才”指的是上一个问题。如果第二次回答和第一次毫无关系,说明上下文没有正确传入。
5.3 用模拟 Webhook 请求验证
命令行验证的是核心逻辑,Webhook 接收还要单独验证。可以利用curl模拟一个飞书风格的消息推送:
curl -X POST http://localhost:8000/webhook \ -H "Content-Type: application/json" \ -d '{ "event": { "message": { "message_id": "om_test_001", "chat_id": "oc_test_001", "content": "{\"text\":\"@_user_1 请总结这段话:Python 是一种动态类型语言\"}" }, "sender": { "sender_id": {"open_id": "ou_test_001"} } } }'如果适配器正确解析,服务会返回{"code": 0, "msg": "ok"},并且日志里能看到处理过程。如果返回ignored,说明is_at_bot判断失败或解析异常。
5.4 日志里应该看到什么
建议在关键路径加日志,至少包括:
- 收到消息:message_id、chat_id、sender_id。
- 是否命中 @:是或否。
- 调用 Grok 的耗时。
- 回复内容截断。
import logging import time logging.basicConfig(level=logging.INFO) logger = logging.getLogger("grok-bot") start = time.time() reply = self.grok_client.chat(history) logger.info( "chat_id=%s cost=%.2fs reply_len=%d", message.chat_id, time.time() - start, len(reply), )日志不完整的排查成本很高。第一次看到一个机器人毫无响应,第一件事就是看日志里有没有消息进来。
6. 常见问题:按链路排查
6.1 Grok 调用失败:401、404、超时
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
返回401 Unauthorized | API Key 错误或未加载 | 检查环境变量;用 curl 测试/models接口 | 重新生成密钥,确认.env已加载 |
返回404 Model Not Found | 模型名或 base_url 错误 | 查看官方文档确认模型标识符 | 纠正GROK_MODEL或GROK_BASE_URL |
| 请求超时 | 网络不通或服务端高负载 | 增大 timeout 测试;检查服务状态页 | 增加超时时间,做退避重试 |
返回429 Too Many Requests | 触发速率限制 | 查看响应头中的Retry-After | 本地限流,按退避策略重试 |
注意:高峰期模型服务端可能返回类似
high demand的提示,这通常不是代码问题,而是服务端负载过高。此时不要无限重试,建议设置指数退避,例如 1 秒、2 秒、4 秒逐步递增。
6.2 @ 识别不到
最常见的现象是:机器人完全不回复,但日志里显示消息已收到。这说明适配器把is_at_bot判断成了False。
原因通常是:
- 消息文本里的机器人 ID 格式与自己写死的不一致。
- 不同平台的 @ 占位符不同,飞书可能是
@_user_1,企业微信可能是@all或@对应userid。 - 某些平台在机器人收到消息时,不会把被 @ 的标记放在文本里,而是放在单独字段中。
处理方式:第一步打印原始raw对象,看平台到底推送了什么内容;第二步确认机器人自身的 ID;第三步把判断逻辑改成“文本包含机器人 ID 或平台标记字段为真”。
6.3 上下文错乱与内存增长
上下文错乱的典型现象是:在 A 群问了一个问题,B 群提问时模型却记得 A 群的内容。原因是 session key 设计错了,把chat_id换成了全局常量。
修改方法:确保 session key 足够唯一。例如飞书机器人有时候需要同时区分chat_id和thread_id,群聊和私聊要拆开。正确做法是拼接:
session_key = f"{message.chat_id}:{message.sender_id}" # 按需决定内存增长则是ContextManager没有淘汰机制。本文的max_sessions和 LRU 策略就是用来防止无限增长的,生产环境还可以把会话存储迁移到 Redis,并给每个 key 加 TTL。
6.4 Webhook 重复推送和乱序
很多 IM 平台为了保证消息不丢失,会重试 Webhook。如果服务没有幂等处理,同一个问题会被 Grok 处理两遍,用户会收到两条相同回复。
解决方案:维护一个已处理消息 ID 集合。简单实现可以用内存set加过期时间,复杂场景可以使用 Redis。
import time class MessageIdDeduplicator: def __init__(self, ttl_seconds: int = 300): self.ttl_seconds = ttl_seconds self.data: dict[str, float] = {} def is_duplicate(self, message_id: str) -> bool: now = time.time() if message_id in self.data and now - self.data[message_id] < self.ttl_seconds: return True self.data[message_id] = now return False乱序问题通常表现为主线程处理慢,多条消息同时进入。如果单次调用 Grok 耗时长,建议把消息先放入队列,由 worker 顺序处理,或者至少加锁保护会话上下文,防止并发修改同一列表。
7. 效率提升设计:让机器人解决真实任务
7.1 指令路由:把 @bot 变成“任务入口”
当机器人只支持“直接问”时,它还是一个聊天工具。真正提升效率,需要给机器人定义一批高频任务,让用户通过指令直接触发。
例如可以设计以下指令:
| 指令 | 作用 | 示例 |
|---|---|---|
/summary | 总结一段文本或链接 | @机器人 /summary 帮我总结这段会议记录 |
/explain | 解释代码或报错日志 | @机器人 /explain 这段报错是什么意思 |
/review | 代码评审建议 | @机器人 /review 下面这段代码有哪些问题 |
/write | 生成文档初稿 | @机器人 /write 写一份周报模板 |
实现思路是在BotHandler中增加指令识别逻辑,命中指令时拼接对应的提示词模板,再把模板和用户内容一起发给 Grok。
COMMAND_TEMPLATES = { "/summary": "请用结构化方式总结以下内容:\n{}", "/explain": "请解释以下错误信息的含义以及排查路径:\n{}", "/review": "请从可维护性、健壮性、性能角度评审以下代码:\n{}", } def apply_command(text: str) -> str: for command, template in COMMAND_TEMPLATES.items(): if text.startswith(command): user_content = text[len(command):].strip() return template.format(user_content) return text指令不一定要用斜杠开头,也可以用中文自然语言,例如“总结一下”“解释这段代码”。但指令前缀更精确,误触发概率低。
7.2 生成内容并写入 Word 文档
社区里有人搜过“Grok 怎么把生成的文本加入 Word”。在 bot 场景里,这个需求很自然:用户让机器人生成一份文档,机器人直接把文档文件发回群聊。
使用python-docx可以快速实现:
pip install python-docxfrom docx import Document def save_reply_to_word(reply: str, filename: str) -> str: doc = Document() doc.add_heading("Grok 生成内容", level=1) for line in reply.splitlines(): if not line.strip(): continue if line.strip().startswith("```"): continue doc.add_paragraph(line) doc.save(filename) return filename然后在指令路由里增加/doc指令:模型生成内容后,调用save_reply_to_word生成.docx文件,再通过 IM 平台的文件上传接口发送。如果平台不支持机器人发文件,就把文件地址或文件内容中的关键段落发回群里。
这一步的价值在于:不是每个用户都会复制 Markdown、再手动粘贴到 Word。机器人直接输出.docx,可以省掉大量人工整理时间。
7.3 多模型切换与提示词模板
Grok 模型版本可能变化,且不同模型在代码、翻译、总结上的表现不同。生产级 bot 不要把所有逻辑绑定到一个全局GROK_MODEL上,而是允许用户通过指令指定模型。
例如设计指令/model grok-4.6,让机器人记住当前群使用的模型版本。切换逻辑只需要在GrokClient中增加一个模型参数:
class GrokClient: def chat(self, messages: list[dict], model: str | None = None) -> str: model = model or self.model response = self.client.chat.completions.create( model=model, messages=messages, temperature=0.7, ) ...提示词模板也可以单独维护,例如代码任务使用更严格的系统提示词,文档任务使用“结构化输出”提示词。模板不一定要放在代码里,可以存成 JSON 文件,方便非开发人员调整。
{ "code": "你是一个资深工程师,回答必须给出可运行代码,并解释关键点。", "summary": "你是会议纪要助手,请使用要点式输出,保留决策和待办事项。", "doc": "你是文档助手,请输出结构清晰的长文,包含标题和段落。" }7.4 定时任务和知识库
更进一步,可以把 bot 从“被动回答”变成“主动提醒”。比如每天早上 9 点,机器人自动调用 Grok 拉取日报、周报、行业资讯,然后推送到群里。这需要引入定时任务,例如APScheduler或系统 cron。
from apscheduler.schedulers.background import BackgroundScheduler def daily_report_job(): reply = bot_handler.handle( Message( message_id=f"scheduler-{int(time.time())}", chat_id="daily-report", sender_id="system", text="/write 请生成一份今日工作日报模板", is_at_bot=True, ) ) send_reply("daily-report", reply) scheduler = BackgroundScheduler() scheduler.add_job(daily_report_job, "cron", hour=9, minute=0) scheduler.start()知识库则属于 RAG(检索增强生成)方向。如果团队内部资料很多,可以把文档切片、向量化,然后用语义检索召回相关内容,拼进 prompt 后再让 Grok 回答。这个方案比直接把整个文档丢给模型更省 token,效果也更稳定。
8. 生产环境最佳实践与扩展方向
8.1 发布前检查清单
把机器人从本地跑到生产环境,不是uvicorn app:app就结束。以下是发布前最值得过一遍的检查清单:
- API Key 没有提交到 Git,环境变量已按生产环境配置。
GROK_BASE_URL是生产可访问的地址,网络策略已放通。- 模型名已通过官方文档确认,不是临时抄来的。
- 上下文管理设置了上限,内存不会无限增长。
- 每个 chat_id 都有独立的 session key,不会串群。
- Webhook 接口做了验签或至少做了来源 IP 限制。
- 消息 ID 去重逻辑已实现,重复推送不会重复处理。
- 单次请求设置了合理超时,不会无限等待。
- 日志包含关键链路节点,便于问题回溯。
- 发送失败时,机器人有兜底提示,不会悄无声息。
8.2 生产环境需要补的东西
如果用户量不大,单机部署、内存会话也可以接受。但真实团队场景下,下面几项基本是必须的:
- 配置中心:
.env文件在容器环境里不直观,建议用环境变量注入,敏感配置放入密钥管理服务。 - Redis 会话存储:上下文从内存迁移到 Redis,才能支持多实例部署,避免负载均衡后请求落在不同节点导致上下文丢失。
- 消息队列:Grok 调用耗时可能较长,Webhook 请求不应该一直阻塞。可以将消息写入队列,由 worker 异步消费并回推结果。
- 监控告警:至少监控 API 调用失败率、平均耗时、限流命中次数。告警方式可以是群里机器人,也可以是标准监控系统。
- 权限控制:指定只有白名单用户或白名单群可以使用高级指令,避免资源被无关请求耗尽。
8.3 后续扩展方向
Grok @bot 的扩展空间很大,越往深处做,越像一套完整的 AI 助手平台。
- 支持语音转文字:用户在群里发语音,机器人先转写再交给 Grok,适合开会场景。
- 支持图片理解:如果 Grok 接口支持多模态,可以接收图片后做截图问答。
- 接入 CI/CD:把机器人接入代码仓库,当代码提交或合并请求创建时,机器人自动做初步代码评审。
- 团队知识库问答:用 RAG 方案把内部文档变成可检索的知识源。
- 多机器人编排:不同任务分发到不同模型或不同机器人,由一个统一入口路由。
对于刚开始做这个方向的项目,建议先把“命令行可调通”作为第一个里程碑,确保 Grok API 链路稳定;再做“群聊可触发”,完成 Webhook 适配;最后才逐步加入指令路由、文档生成、定时任务和知识库。每一步解决一个明确问题,机器人的可靠性会明显高于一次性堆功能。