diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..dc01ca29aebc858d038bb759d760e0b97f7c41a2 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +API_TOKEN=sk-uztyxtwbxdjhccafmwytabtyzadmoycjnanmctstduyatlnf +TELEGRAM_BOT_TOKEN=7390701971:AAFZ9hfl7HbTdBkm1WB8nTVNmzPdcxTPpoU +BOT_NAME=aiagent10_bot diff --git a/.inscode b/.inscode index 3a001eb88671424594301483eb8665b81f6f4826..66c97eb701379b445769bc2cf85cb7fa80762a01 100644 --- a/.inscode +++ b/.inscode @@ -1,4 +1,4 @@ -run = "pip install -r requirements.txt;python main.py" +run = "pip install -r requirements.txt;python bot.py" language = "python" [packager] diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9a8700c1c342c066beb3fba434049c7a75fc828c --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Telegram AI 助手机器人 + +这是一个基于 Streamlit 的 Telegram 机器人应用,可以在私聊和群组中与用户进行对话。 + +## 功能特点 + +- 支持私聊和群组对话 +- 在群组中通过 @ 机器人触发对话 +- 支持基本命令(/start, /help) +- 实时显示对话历史 +- 使用 Streamlit 提供友好的 Web 界面 + +## 安装说明 + +1. 克隆项目到本地 +2. 安装依赖: + ```bash + pip install -r requirements.txt + ``` + +3. 创建 `.env` 文件,添加以下配置: + ``` + API_TOKEN=你的API密钥 + TELEGRAM_BOT_TOKEN=你的Telegram机器人Token + BOT_NAME=你的机器人名称 + ``` + +## 使用方法 + +1. 运行应用: + ```bash + streamlit run bot.py + ``` + +2. 在浏览器中打开显示的地址 +3. 点击"开始监听 Telegram"按钮启动机器人 + +### 在 Telegram 中使用 + +- 私聊:直接发送消息给机器人 +- 群组:在消息中 @ 机器人(例如:@你的机器人 你好) +- 命令: + - /start:开始对话 + - /help:显示帮助信息 + +## 注意事项 + +- 请确保已正确配置所有环境变量 +- 确保机器人已被添加到目标群组 +- 机器人需要有足够的群组权限 diff --git a/__pycache__/api_client.cpython-39.pyc b/__pycache__/api_client.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d0bcec5247789bea42045137206ee6aa3259b2a Binary files /dev/null and b/__pycache__/api_client.cpython-39.pyc differ diff --git a/__pycache__/config.cpython-39.pyc b/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a115e2dc1d004d46c77a3dab34b34fb5db4bd45 Binary files /dev/null and b/__pycache__/config.cpython-39.pyc differ diff --git a/api_client.py b/api_client.py new file mode 100644 index 0000000000000000000000000000000000000000..ad6aa47ad3d128218824e5d71c876f7a40508038 --- /dev/null +++ b/api_client.py @@ -0,0 +1,132 @@ +import logging +import aiohttp +import json +from typing import Optional, Dict, Any, List +from config import API_TOKEN, API_URL, MODEL_NAME + +logger = logging.getLogger(__name__) + +class Message: + def __init__(self, role: str, content: str): + self.role = role + self.content = content + + def to_dict(self) -> Dict[str, str]: + return {"role": self.role, "content": self.content} + +class Conversation: + def __init__(self, max_history: int = 10): + self.messages: List[Message] = [] + self.max_history = max_history + + def add_message(self, role: str, content: str): + self.messages.append(Message(role, content)) + # 保持历史记录在限制范围内,但保留系统消息 + if len(self.messages) > self.max_history + 1: # +1 是为了系统消息 + # 保留第一条(系统)消息 + self.messages = [self.messages[0]] + self.messages[-(self.max_history-1):] + + def get_messages(self) -> List[Dict[str, str]]: + return [msg.to_dict() for msg in self.messages] + +class APIClient: + def __init__(self): + self.headers = { + "Authorization": f"Bearer {API_TOKEN}", + "Content-Type": "application/json" + } + self.session: Optional[aiohttp.ClientSession] = None + self.conversations: Dict[int, Conversation] = {} # chat_id -> Conversation + + def get_or_create_conversation(self, chat_id: int) -> Conversation: + if chat_id not in self.conversations: + conv = Conversation() + # 添加系统提示和知识库 + conv.add_message("system", + "你是一个友好、专业的 AI 助手,专门负责解答关于 AGI Open Network (AON) 的问题。\n" + "以下是你的知识库:\n\n" + "1. AI Agent 定义:\n" + "AI Agent 是由人工智能驱动的软件实体,能够自主执行任务、做出决策,并与用户或其他系统交互以实现特定目标。\n\n" + "2. AI Agent 为什么需要加密货币:\n" + "- 提供去中心化和安全的基础设施\n" + "- 支持智能合约执行任务\n" + "- 实现安全的微交易\n" + "- 促进共享所有权\n" + "- 实现透明的金融交互\n\n" + "3. AON 愿景和使命:\n" + "愿景:让 AI agents 成为有价值的资产,在各种消费应用中产生收益。\n" + "使命:创建开放平台,让所有人都能开发、部署和变现 AI Agents 和 AI Models。\n\n" + "4. AON 产品模块:\n" + "- AI Model API 平台(3000+ 开源模型)\n" + "- AI 算力聚合器\n" + "- AI Agent 发行平台(IAO)\n" + "- AI Model 发行平台(IMO)\n" + "- Web3 SDK(MPC, PayFi)\n\n" + "5. 用户类型:\n" + "专业开发者:可使用平台 AI 模型和算力资源\n" + "非专业开发者:可使用无代码开发模板\n\n" + "6. 变现模式:\n" + "- IAO(Initial Agent Offering):AI Agent 代币发行\n" + "- IMO(Initial Model Offering):AI 模型代币发行\n\n" + "请根据以上知识,准确、简洁地回答用户问题。如果遇到知识库之外的问题,请诚实地表示不确定。" + ) + self.conversations[chat_id] = conv + return self.conversations[chat_id] + + async def ensure_session(self): + """确保 aiohttp session 已创建""" + if self.session is None or self.session.closed: + self.session = aiohttp.ClientSession() + + async def close(self): + """关闭 session""" + if self.session and not self.session.closed: + await self.session.close() + + def clear_history(self, chat_id: int): + """清除特定对话的历史记录""" + if chat_id in self.conversations: + del self.conversations[chat_id] + + async def get_ai_response(self, chat_id: int, user_message: str, retry_count: int = 0) -> str: + """获取 AI 回复""" + try: + await self.ensure_session() + + # 获取或创建对话 + conversation = self.get_or_create_conversation(chat_id) + + # 添加用户消息 + conversation.add_message("user", user_message) + + payload = { + "model": MODEL_NAME, + "messages": conversation.get_messages() + } + + logger.debug(f"发送请求到 API,payload: {payload}") + + async with self.session.post(API_URL, json=payload, headers=self.headers) as response: + response.raise_for_status() + data = await response.json() + + if "choices" in data and data["choices"]: + ai_response = data["choices"][0]["message"]["content"] + # 添加 AI 回复到历史记录 + conversation.add_message("assistant", ai_response) + return ai_response + else: + logger.error(f"API 返回的数据格式不正确: {data}") + return "抱歉,我现在无法正确理解和回复。" + + except aiohttp.ClientError as e: + if retry_count < 2: # 最多重试2次 + logger.warning(f"API 请求失败,正在重试 ({retry_count + 1}/2): {str(e)}") + return await self.get_ai_response(chat_id, user_message, retry_count + 1) + else: + logger.error(f"API 请求失败,已达到最大重试次数: {str(e)}") + return "抱歉,我暂时无法访问 AI 服务,请稍后再试。" + + except Exception as e: + logger.error(f"调用 API 时发生错误: {str(e)}", exc_info=True) + return "抱歉,处理你的消息时出现了问题,请稍后再试。" diff --git a/bot.py b/bot.py new file mode 100644 index 0000000000000000000000000000000000000000..b802cab7295b647641af62bf811abc4d1e3aa74b --- /dev/null +++ b/bot.py @@ -0,0 +1,247 @@ +import os +import logging +import asyncio +from telegram import Bot, Update +from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes +from telegram.error import TelegramError, NetworkError, TimedOut +from config import TELEGRAM_BOT_TOKEN +from api_client import APIClient + +# 设置日志记录 +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# 创建 API 客户端 +api_client = APIClient() + +async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /start 命令""" + try: + logger.info(f"收到 /start 命令,来自用户: {update.message.from_user.username}") + await update.message.reply_text( + "👋 你好!我是一个智能助手。\n\n" + "🤖 我可以:\n" + "1. 回答你的问题\n" + "2. 帮你完成任务\n" + "3. 陪你聊天\n\n" + "💡 直接发消息给我就可以开始对话!\n" + "🔄 使用 /clear 可以清除对话历史\n" + "❓ 使用 /help 查看更多帮助" + ) + except Exception as e: + logger.error(f"处理 /start 命令时出错: {str(e)}", exc_info=True) + await update.message.reply_text("抱歉,处理命令时出现错误,请稍后再试。") + +async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /help 命令""" + try: + logger.info(f"收到 /help 命令,来自用户: {update.message.from_user.username}") + await update.message.reply_text( + "🔍 帮助信息:\n\n" + "命令列表:\n" + "/start - 开始对话\n" + "/help - 显示帮助信息\n" + "/clear - 清除对话历史\n" + "/check - 检查机器人权限\n\n" + "使用提示:\n" + "1. 直接发送消息即可与我对话\n" + "2. 我会记住对话内容,保持上下文连贯\n" + "3. 如果想开始新话题,可以用 /clear 清除历史\n" + "4. 我会用emoji让对话更生动\n" + "5. 如果遇到问题,请尝试重新发送消息" + ) + except Exception as e: + logger.error(f"处理 /help 命令时出错: {str(e)}", exc_info=True) + await update.message.reply_text("抱歉,处理命令时出现错误,请稍后再试。") + +async def clear_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """处理 /clear 命令""" + try: + chat_id = update.message.chat_id + logger.info(f"收到 /clear 命令,来自用户: {update.message.from_user.username}") + + # 清除对话历史 + api_client.clear_history(chat_id) + + await update.message.reply_text( + "🧹 已清除对话历史!\n" + "现在我们可以开始新的对话了。" + ) + except Exception as e: + logger.error(f"处理 /clear 命令时出错: {str(e)}", exc_info=True) + await update.message.reply_text("抱歉,清除历史记录时出现错误,请稍后再试。") + +async def check_permissions_command(update: Update, context: ContextTypes.DEFAULT_TYPE): + """检查机器人权限""" + try: + chat = update.message.chat + logger.info(f"正在检查权限 - 聊天ID: {chat.id}, 类型: {chat.type}") + + # 获取机器人信息 + bot_info = await context.bot.get_me() + logger.info(f"机器人信息 - ID: {bot_info.id}, 用户名: @{bot_info.username}") + + # 获取机器人成员信息 + try: + bot_member = await chat.get_member(context.bot.id) + logger.info(f"机器人成员信息 - 状态: {bot_member.status}, 权限: {vars(bot_member)}") + except Exception as e: + logger.error(f"获取机器人成员信息失败: {str(e)}") + bot_member = None + + # 获取发送者信息 + try: + sender = await chat.get_member(update.message.from_user.id) + logger.info(f"发送者信息 - 状态: {sender.status}, 权限: {vars(sender)}") + except Exception as e: + logger.error(f"获取发送者信息失败: {str(e)}") + sender = None + + permissions_info = [ + f"🤖 机器人信息:", + f"- 用户名:@{bot_info.username}", + f"- ID:{bot_info.id}", + f"- 是否为机器人:{'是' if bot_info.is_bot else '否'}", + f"\n📱 当前聊天信息:", + f"- 类型:{chat.type}", + f"- ID:{chat.id}", + f"- 标题:{chat.title if chat.type != 'private' else '私聊'}", + f"\n👤 发送者信息:", + f"- 用户名:@{update.message.from_user.username}", + f"- ID:{update.message.from_user.id}", + f"- 状态:{sender.status if sender else '未知'}" + ] + + if bot_member: + permissions_info.extend([ + f"\n🔑 机器人权限:", + f"- 成员状态:{bot_member.status}", + f"- 是否为管理员:{'是' if bot_member.status in ['administrator', 'creator'] else '否'}", + f"- 是否可以发送消息:{'是' if getattr(bot_member, 'can_send_messages', None) is not False else '否'}", + f"- 是否可以发送媒体消息:{'是' if getattr(bot_member, 'can_send_media_messages', None) is not False else '否'}", + f"- 是否可以添加网页预览:{'是' if getattr(bot_member, 'can_add_web_page_previews', None) is not False else '否'}", + f"- 是否可以发送其他消息:{'是' if getattr(bot_member, 'can_send_other_messages', None) is not False else '否'}", + f"- 原始权限数据:{vars(bot_member)}" + ]) + else: + permissions_info.append("\n❌ 无法获取机器人权限信息") + + await update.message.reply_text("\n".join(permissions_info)) + + except Exception as e: + error_msg = f"检查权限时出错: {str(e)}" + logger.error(error_msg, exc_info=True) + await update.message.reply_text(f"抱歉,检查权限时出现错误:\n{error_msg}") + +async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE, retry_count=0): + """处理用户消息""" + try: + # 获取用户信息和消息 + user = update.message.from_user + message_text = update.message.text + chat_id = update.message.chat_id + bot_username = context.bot.username + + # 检查是否是群组消息 + is_group = update.message.chat.type in ["group", "supergroup"] + logger.info(f"消息类型: {'群组消息' if is_group else '私聊消息'}") + logger.info(f"机器人用户名: {bot_username}") + logger.info(f"原始消息: {message_text}") + + # 检查是否提到了机器人(包括回复和@) + mentioned = False + if is_group: + # 检查是否是回复机器人的消息 + if update.message.reply_to_message and update.message.reply_to_message.from_user.id == context.bot.id: + mentioned = True + # 检查是否@机器人(支持消息中的任何位置) + elif bot_username and f"@{bot_username}" in message_text: + mentioned = True + # 移除@username部分 + message_text = message_text.replace(f"@{bot_username}", "").strip() + + logger.info(f"是否提到机器人: {mentioned}") + + # 如果没有提到机器人,则忽略消息 + if not mentioned: + logger.info("未提到机器人,忽略消息") + return + + logger.info(f"处理消息 [{chat_id}] 来自 {user.username}: {message_text}") + + # 发送"正在输入"状态 + await update.message.chat.send_action(action="typing") + + # 调用 AI API 获取回复 + response = await api_client.get_ai_response(chat_id, message_text) + + # 发送回复 + await update.message.reply_text(response) + logger.info(f"已回复消息 [{chat_id}]: {response[:100]}...") + + except (NetworkError, TimedOut) as e: + if retry_count < 3: + logger.warning(f"发送消息失败,正在重试 ({retry_count + 1}/3): {str(e)}") + await asyncio.sleep(1) + return await handle_message(update, context, retry_count + 1) + else: + logger.error(f"发送消息失败,已达到最大重试次数: {str(e)}") + await update.message.reply_text("抱歉,发送消息时出现错误,请稍后再试。") + except Exception as e: + logger.error(f"处理消息时出错: {str(e)}", exc_info=True) + await update.message.reply_text("抱歉,处理消息时出现错误,请稍后再试。") + +async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: + """处理错误""" + logger.error("发生异常:", exc_info=context.error) + +async def cleanup(): + """清理资源""" + await api_client.close() + +def run_bot(): + """运行机器人""" + try: + # 创建应用 + application = Application.builder().token(TELEGRAM_BOT_TOKEN).build() + + # 添加处理器 + application.add_handler(CommandHandler("start", start_command)) + application.add_handler(CommandHandler("help", help_command)) + application.add_handler(CommandHandler("clear", clear_command)) + application.add_handler(CommandHandler("check", check_permissions_command)) + + # 添加消息处理器,处理提及和私聊消息 + application.add_handler(MessageHandler( + (filters.TEXT & ~filters.COMMAND) & + (filters.ChatType.GROUPS | filters.ChatType.PRIVATE) & + (filters.Entity("mention") | filters.ChatType.PRIVATE), + handle_message + )) + + # 添加处理器,处理回复机器人的消息 + application.add_handler(MessageHandler( + (filters.TEXT & ~filters.COMMAND) & + filters.REPLY, + handle_message + )) + + # 添加错误处理器 + application.add_error_handler(error_handler) + + # 启动机器人 + logger.info("正在启动机器人...") + application.run_polling(drop_pending_updates=True) + + except Exception as e: + logger.error(f"启动机器人时出错: {str(e)}", exc_info=True) + raise + finally: + # 确保清理资源 + asyncio.run(cleanup()) + +if __name__ == "__main__": + run_bot() diff --git a/config.py b/config.py new file mode 100644 index 0000000000000000000000000000000000000000..9702168710a6a934672a6a7f963ad8aaa35e270a --- /dev/null +++ b/config.py @@ -0,0 +1,14 @@ +import os +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# API 配置 +API_URL = "https://api.siliconflow.cn/v1/chat/completions" +API_TOKEN = os.getenv("API_TOKEN") # 从环境变量获取 +MODEL_NAME = "meta-llama/Llama-3.3-70B-Instruct" + +# Telegram Bot 配置 +TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") # 从环境变量获取 +BOT_NAME = os.getenv("BOT_NAME", "AI助手") # 默认名称为"AI助手" diff --git a/requirements.txt b/requirements.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..79ae48f83f6bd293229b485ecee1895ddd3512fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,6 @@ +python-telegram-bot>=20.7 +requests==2.31.0 +streamlit==1.29.0 +python-dotenv==1.0.0 +asyncio>=3.4.3 +aiohttp==3.9.1 diff --git a/test_bot.py b/test_bot.py new file mode 100644 index 0000000000000000000000000000000000000000..a12971771f33efa01a09a5dd5efef3b974159eb5 --- /dev/null +++ b/test_bot.py @@ -0,0 +1,86 @@ +import os +import logging +import asyncio +from telegram import Bot +from telegram.error import TelegramError, NetworkError, TimedOut +from config import TELEGRAM_BOT_TOKEN + +# 设置日志记录 +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +async def handle_message(bot: Bot, chat_id: int, text: str, retry_count=0): + """处理接收到的消息,带重试机制""" + try: + if text == '/start': + await bot.send_message(chat_id=chat_id, text="你好!我是测试机器人。") + else: + await bot.send_message(chat_id=chat_id, text=f"你说: {text}") + except (NetworkError, TimedOut) as e: + if retry_count < 3: + logger.warning(f"发送消息失败,正在重试 ({retry_count + 1}/3): {str(e)}") + await asyncio.sleep(1) + return await handle_message(bot, chat_id, text, retry_count + 1) + else: + logger.error(f"发送消息失败,已达到最大重试次数: {str(e)}") + raise + +async def main(): + """持续运行的主函数""" + retry_count = 0 + while True: + try: + logger.info(f"使用 token: {TELEGRAM_BOT_TOKEN}") + bot = Bot(token=TELEGRAM_BOT_TOKEN) + + # 获取机器人信息 + me = await bot.get_me() + logger.info(f"机器人信息: {me.to_dict()}") + + # 记录最后处理的更新ID + last_update_id = 0 + + while True: + try: + # 获取新的更新 + updates = await bot.get_updates( + offset=last_update_id + 1, + timeout=30, + allowed_updates=['message'] + ) + + for update in updates: + if update.message and update.message.text: + chat_id = update.message.chat_id + text = update.message.text + logger.info(f"收到消息 [{chat_id}]: {text}") + await handle_message(bot, chat_id, text) + + # 更新最后处理的更新ID + last_update_id = update.update_id + + except (NetworkError, TimedOut) as e: + logger.warning(f"网络错误,等待后重试: {str(e)}") + await asyncio.sleep(5) + continue + except Exception as e: + logger.error(f"处理消息时发生错误: {str(e)}", exc_info=True) + continue + + # 短暂休息,避免过于频繁的请求 + await asyncio.sleep(1) + + except Exception as e: + retry_count += 1 + if retry_count > 5: + logger.error(f"发生严重错误,程序退出: {str(e)}", exc_info=True) + raise + + logger.error(f"发生错误,{retry_count}/5 次重试: {str(e)}", exc_info=True) + await asyncio.sleep(5) + +if __name__ == '__main__': + asyncio.run(main())