From e353a0f854f8ecb1e6760154475cbca50361e9da Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Wed, 18 Oct 2023 11:21:55 +0800 Subject: [PATCH 01/11] feat: support LangChain Implement local services: search, mathematical operations, email summary --- xiaogpt/bot/__init__.py | 4 +- xiaogpt/bot/langchain_bot.py | 40 ++++++ xiaogpt/cli.py | 14 ++- xiaogpt/config.py | 3 + xiaogpt/langchain/chain.py | 49 ++++++++ xiaogpt/langchain/mail_box.py | 161 ++++++++++++++++++++++++ xiaogpt/langchain/mail_summary_tools.py | 35 ++++++ xiaogpt/langchain/stream_call_back.py | 15 +++ 8 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 xiaogpt/bot/langchain_bot.py create mode 100644 xiaogpt/langchain/chain.py create mode 100644 xiaogpt/langchain/mail_box.py create mode 100644 xiaogpt/langchain/mail_summary_tools.py create mode 100755 xiaogpt/langchain/stream_call_back.py diff --git a/xiaogpt/bot/__init__.py b/xiaogpt/bot/__init__.py index 5e1ded77..02435e1c 100644 --- a/xiaogpt/bot/__init__.py +++ b/xiaogpt/bot/__init__.py @@ -6,6 +6,7 @@ from xiaogpt.bot.newbing_bot import NewBingBot from xiaogpt.bot.glm_bot import GLMBot from xiaogpt.bot.bard_bot import BardBot +from xiaogpt.bot.langchain_bot import LangChainBot from xiaogpt.config import Config BOTS: dict[str, type[BaseBot]] = { @@ -14,6 +15,7 @@ "chatgptapi": ChatGPTBot, "glm": GLMBot, "bard": BardBot, + "langchain": LangChainBot, } @@ -24,4 +26,4 @@ def get_bot(config: Config) -> BaseBot: raise ValueError(f"Unsupported bot {config.bot}, must be one of {list(BOTS)}") -__all__ = ["GPT3Bot", "ChatGPTBot", "NewBingBot", "GLMBot", "BardBot", "get_bot"] +__all__ = ["GPT3Bot", "ChatGPTBot", "NewBingBot", "GLMBot", "BardBot", "get_bot", "LangChainBot"] diff --git a/xiaogpt/bot/langchain_bot.py b/xiaogpt/bot/langchain_bot.py new file mode 100644 index 00000000..10e4279d --- /dev/null +++ b/xiaogpt/bot/langchain_bot.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import openai +from rich import print + +from xiaogpt.bot.base_bot import BaseBot +from xiaogpt.utils import split_sentences + +from xiaogpt.langchain.chain import agent_search +from xiaogpt.langchain.stream_call_back import streaming_call_queue + +class LangChainBot(BaseBot): + + def __init__( + self, + ) -> None: + # todo,建议在langchain内实现聊天历史对话 + self.history = [] + + @classmethod + def from_config(cls, config): + pass + + async def ask(self, query, **options): + # todo,目前仅支持stream + raise Exception("The bot does not support it. Please use 'ask_stream'.") + + async def ask_stream(self, query, **options): + agent_search(query) + try: + while True: + if not streaming_call_queue.empty(): + token = streaming_call_queue.get() + print(token, end='') + yield token + else: + break + except Exception as e: + # 处理异常的代码 + print("An error occurred:", str(e)) diff --git a/xiaogpt/cli.py b/xiaogpt/cli.py index c856d223..160501bd 100644 --- a/xiaogpt/cli.py +++ b/xiaogpt/cli.py @@ -27,6 +27,11 @@ def main(): dest="openai_key", help="openai api key", ) + parser.add_argument( + "--serpapi_api_key", + dest="serpapi_api_key", + help="serp api key see https://serpapi.com/", + ) parser.add_argument( "--glm_key", dest="glm_key", @@ -97,6 +102,13 @@ def main(): const="chatgptapi", help="if use openai chatgpt api", ) + group.add_argument( + "--use_langchain", + dest="bot", + action="store_const", + const="langchain", + help="if use langchain", + ) group.add_argument( "--use_newbing", dest="bot", @@ -127,7 +139,7 @@ def main(): "--bot", dest="bot", help="bot type", - choices=["gpt3", "chatgptapi", "newbing", "glm", "bard"], + choices=["gpt3", "chatgptapi", "newbing", "glm", "bard", "langchain"], ) parser.add_argument( "--config", diff --git a/xiaogpt/config.py b/xiaogpt/config.py index b6063cfb..d007e27b 100644 --- a/xiaogpt/config.py +++ b/xiaogpt/config.py @@ -60,6 +60,7 @@ class Config: account: str = os.getenv("MI_USER", "") password: str = os.getenv("MI_PASS", "") openai_key: str = os.getenv("OPENAI_API_KEY", "") + serpapi_api_key: str = os.getenv("SERPAPI_API_KEY", "") glm_key: str = os.getenv("CHATGLM_KEY", "") bard_token: str = os.getenv("BARD_TOKEN", "") proxy: str | None = None @@ -147,5 +148,7 @@ def read_from_file(cls, config_path: str) -> dict: key, value = "bot", "glm" elif key == "use_bard": key, value = "bot", "bard" + elif key == "use_langchain": + key, value = "bot", "langchain" result[key] = value return result diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py new file mode 100644 index 00000000..a3e84211 --- /dev/null +++ b/xiaogpt/langchain/chain.py @@ -0,0 +1,49 @@ +from langchain.agents import initialize_agent, Tool +from langchain.agents import AgentType +from langchain.tools import BaseTool +from langchain.llms import OpenAI +from langchain import LLMMathChain, SerpAPIWrapper +from langchain.chat_models import ChatOpenAI +from langchain.memory import ChatMessageHistory +from xiaogpt.langchain.stream_call_back import StreamCallbackHandler +from langchain.agents.agent_toolkits import ZapierToolkit +from langchain.utilities.zapier import ZapierNLAWrapper +from xiaogpt.langchain.mail_box import Mailbox +from xiaogpt.langchain.mail_summary_tools import MailSummaryTool +from langchain.memory import ConversationBufferMemory +from xiaogpt.config import Config + +def agent_search(query): + llm = ChatOpenAI(streaming=True, temperature=0, model="gpt-3.5-turbo-0613", callbacks=[StreamCallbackHandler()]) + + # 初始化:搜索链、数学计算链、自定义总结邮件链 + search = SerpAPIWrapper() + llm_math_chain = LLMMathChain(llm=llm, verbose=False) + mail_summary = MailSummaryTool() + + # 工具列表:搜索、数学计算、自定义总结邮件 + tools = [ + Tool( + name = "Search", + func=search.run, + description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案" + ), + Tool( + name="Calculator", + func=llm_math_chain.run, + description="在需要回答数学问题时非常有用" + ), + Tool( + name="MailSummary", + func=mail_summary.run, + description="这是一个工具,订阅每天收到的邮件,总结并发送到我邮箱" + ) + ] + + # 初始化 agent + agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS, verbose=False) + + # query eg:'杭州亚运会中国队获得了多少枚金牌?' // '计算3的2次方' // '帮我总结今天收到的邮件' + agent.run(query) + + diff --git a/xiaogpt/langchain/mail_box.py b/xiaogpt/langchain/mail_box.py new file mode 100644 index 00000000..0fab200f --- /dev/null +++ b/xiaogpt/langchain/mail_box.py @@ -0,0 +1,161 @@ +import imaplib +import email +from datetime import datetime, timedelta +import html +from bs4 import BeautifulSoup +import re +import openai +import smtplib +from email.mime.text import MIMEText + +class Mailbox: + # 需要配置Gmail帐号设置 + gmail_address = '' + gmail_password = '' + + # 连接到IMAP服务器 + imap_server = 'imap.gmail.com' + imap_port = 993 + + # 定义邮件接收人,支持添加多个收件人邮箱地址 + to_addresses = [''] + + # 定义总结的邮件数目 + max_emails = 3 + + def get_all_work_summary(self): + print('正在获取邮件...') + try: + # 建立IMAP连接 + mailbox = imaplib.IMAP4_SSL(self.imap_server, self.imap_port) + # 登录Gmail帐号 + mailbox.login(self.gmail_address, self.gmail_password) + # 选择邮箱 + mailbox.select('INBOX') + # 获取今天的日期 + today = datetime.now().strftime('%d-%b-%Y') + # 构建搜索条件 + search_criteria = f'(SINCE "{today}")' + # 搜索符合条件的邮件 + status, email_ids = mailbox.search(None, search_criteria) + + if status == 'OK': + email_ids = email_ids[0].split() + print(f"今天收到的邮件数量:{len(email_ids)}") + # 限制最多获取15封邮件 + max_emails = min(len(email_ids), self.max_emails) + all_email_content = '' + + for i in range(max_emails): + email_id = email_ids[i] + email_content = self.get_email_content(mailbox, email_id) + if email_content: + all_email_content += f"{i+1}、{email_content}\n" + + # print(all_email_content) + + # 关闭连接 + mailbox.logout() + + return all_email_content + except Exception as e: + print('获取邮件失败:', str(e)) + + def get_email_content(self, mailbox, email_id): + # 获取邮件内容 + status, email_data = mailbox.fetch(email_id, '(RFC822)') + if status == 'OK': + raw_email = email_data[0][1] + msg = email.message_from_bytes(raw_email) + + # 获取发件人 + sender = msg['From'] + # 提取尖括号(<>)内的发件人 + sender = re.findall(r'<(.*?)>', sender) + sender = sender[0] if sender else '' + + # 检查发件人的邮箱地址是否以 '@addcn.com' 结尾 + if sender.lower().endswith('@addcn.com') and not msg['In-Reply-To']: + # 获取邮件内容 + email_content = '' + if msg.is_multipart(): + for part in msg.walk(): + content_type = part.get_content_type() + if content_type == 'text/plain': + email_content = part.get_payload(decode=True).decode('utf-8') + break + elif content_type == 'text/html': + email_content = part.get_payload(decode=True).decode('utf-8') + email_content = html.unescape(email_content) # 过滤HTML代码 + break + else: + email_content = msg.get_payload(decode=True).decode('utf-8') + + # 如果仍然包含html代码,则使用BeautifulSoup过滤html代码 + if 'html' in email_content.lower(): + soup = BeautifulSoup(email_content, 'html.parser') + email_content = soup.get_text() + + # 输出文本格式 + email_content = re.sub(r'\s+', '', email_content) + # 过滤 = 号之间的内容 + email_content = re.sub(r'=\?.*?\?=', '', email_content) + # 过滤 --符号之后的内容 + email_content = re.sub(r'---.*', '', email_content) + + return f"{sender}发送邮件,内容是{email_content}" + + return '' + + def get_summary_by_ai(self, email_content: str, prompt: str) -> str: + print('正在请AI总结邮件内容...') + + # 请求ChatGPT进行总结 + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo-0613", + messages=[ + {"role": "system", "content": prompt}, + {"role": "user", "content": email_content} + ] + ) + + # 提取ChatGPT生成的总结 + summary = response.choices[0].message.content.strip() + # print(summary) + return summary + + def send_mail(self, summary, theme='邮件摘要汇总'): + # 设置发件人和收件人 + from_address = self.gmail_address + to_addresses = self.to_addresses # 添加多个收件人邮箱地址 + + # 构建邮件内容 + yesterday = (datetime.now() - timedelta(days=0)).strftime('%Y-%m-%d') + subject = yesterday + theme + body = summary + + try: + # 连接到SMTP服务器 + smtp_server = smtplib.SMTP('smtp.gmail.com', 587) + smtp_server.ehlo() + smtp_server.starttls() + # 登录邮箱 + smtp_server.login(self.gmail_address, self.gmail_password) + + for to_address in to_addresses: + # 创建纯文本邮件消息对象 + message = MIMEText(body, 'plain', 'utf-8') + message['Subject'] = subject + message['From'] = from_address + message['To'] = to_address + + # 发送邮件 + smtp_server.sendmail(from_address, to_address, message.as_string()) + print('邮件发送成功至:', to_address) + + # 关闭连接 + smtp_server.quit() + print('所有邮件已成功发送!') + return True + except Exception as e: + print('邮件发送失败:', str(e)) \ No newline at end of file diff --git a/xiaogpt/langchain/mail_summary_tools.py b/xiaogpt/langchain/mail_summary_tools.py new file mode 100644 index 00000000..2981ff8e --- /dev/null +++ b/xiaogpt/langchain/mail_summary_tools.py @@ -0,0 +1,35 @@ +from langchain.tools import BaseTool +from xiaogpt.langchain.mail_box import Mailbox + +# 总结日志工具 +class MailSummaryTool(BaseTool): + name = "MailSumary" + description = "当被问到总结邮件相关时,会触发这个工具,进行今日邮件总结和发送。当调用工具完毕,只需要回复总结成功或失败即可,立即结束本次回答" + + # 新增日志总结工具 + def get_mail_summary(self) -> str: + """ + 总结邮件:对邮箱内收到的邮件进行总结,并发送到指定邮箱 + """ + mailbox = Mailbox() + all_email_content = mailbox.get_all_work_summary() + prompt = """ + 要求你作为一名总编辑。根据输入的多封邮件,对每封做简明扼要的摘要。要求如下: + 1、对每封邮件摘要总结,摘要总结字数在25字以内 + 2、排版按照 发送人:xx 内容:xx (换一行) + 3、注意换行,要求全文美观简洁 + 4、展示邮件内提到项目名,不用额外扩展讲项目内容和进度 + """ + gpt_content = mailbox.get_summary_by_ai(all_email_content, prompt) + is_success = mailbox.send_mail(gpt_content) + if is_success: + return "总结邮件成功" + else: + return "总结邮件失败,请检查邮箱配置" + + def _run(self, query: str) -> str: + return self.get_mail_summary() + + async def _arun(self, query: str) -> str: + """Use the tool asynchronously.""" + raise NotImplementedError("MailSummaryTool does not support async") \ No newline at end of file diff --git a/xiaogpt/langchain/stream_call_back.py b/xiaogpt/langchain/stream_call_back.py new file mode 100755 index 00000000..83b1dd70 --- /dev/null +++ b/xiaogpt/langchain/stream_call_back.py @@ -0,0 +1,15 @@ +import chardet +from queue import Queue +from typing import Any, Dict, List, Optional +from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler + +DEFAULT_ANSWER_PREFIX_TOKENS = [""] +streaming_call_queue = Queue() + +class StreamCallbackHandler(StreamingStdOutCallbackHandler): + def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + token_copy = token + code = chardet.detect(token_copy.encode())['encoding'] + if code is not None: + # 接收流消息入队,在ask_stream时出队 + streaming_call_queue.put(token) \ No newline at end of file From b5de01680d2a194a0600cc1b7e3a6f6efd69e679 Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Wed, 18 Oct 2023 11:37:06 +0800 Subject: [PATCH 02/11] feat: support LangChain Implement local services: search, mathematical operations, email summary --- xiaogpt/bot/langchain_bot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xiaogpt/bot/langchain_bot.py b/xiaogpt/bot/langchain_bot.py index 10e4279d..09f5c333 100644 --- a/xiaogpt/bot/langchain_bot.py +++ b/xiaogpt/bot/langchain_bot.py @@ -14,12 +14,12 @@ class LangChainBot(BaseBot): def __init__( self, ) -> None: - # todo,建议在langchain内实现聊天历史对话 - self.history = [] + # todo,建议在langchain内实现 + self.history = [] @classmethod def from_config(cls, config): - pass + return cls() async def ask(self, query, **options): # todo,目前仅支持stream @@ -37,4 +37,4 @@ async def ask_stream(self, query, **options): break except Exception as e: # 处理异常的代码 - print("An error occurred:", str(e)) + print("An error occurred:", str(e)) \ No newline at end of file From a2d02f63c4100ab047b39ad414cafa6d6ab12be1 Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Wed, 18 Oct 2023 12:42:11 +0800 Subject: [PATCH 03/11] Fix code formatting --- xiaogpt/bot/__init__.py | 10 ++- xiaogpt/bot/langchain_bot.py | 6 +- xiaogpt/cli.py | 2 +- xiaogpt/langchain/chain.py | 30 ++++----- xiaogpt/langchain/mail_box.py | 87 +++++++++++++------------ xiaogpt/langchain/mail_summary_tools.py | 4 +- xiaogpt/langchain/stream_call_back.py | 5 +- 7 files changed, 78 insertions(+), 66 deletions(-) diff --git a/xiaogpt/bot/__init__.py b/xiaogpt/bot/__init__.py index 02435e1c..7c3a0cf4 100644 --- a/xiaogpt/bot/__init__.py +++ b/xiaogpt/bot/__init__.py @@ -26,4 +26,12 @@ def get_bot(config: Config) -> BaseBot: raise ValueError(f"Unsupported bot {config.bot}, must be one of {list(BOTS)}") -__all__ = ["GPT3Bot", "ChatGPTBot", "NewBingBot", "GLMBot", "BardBot", "get_bot", "LangChainBot"] +__all__ = [ + "GPT3Bot", + "ChatGPTBot", + "NewBingBot", + "GLMBot", + "BardBot", + "get_bot", + "LangChainBot", +] diff --git a/xiaogpt/bot/langchain_bot.py b/xiaogpt/bot/langchain_bot.py index 09f5c333..8b84709a 100644 --- a/xiaogpt/bot/langchain_bot.py +++ b/xiaogpt/bot/langchain_bot.py @@ -9,8 +9,8 @@ from xiaogpt.langchain.chain import agent_search from xiaogpt.langchain.stream_call_back import streaming_call_queue -class LangChainBot(BaseBot): +class LangChainBot(BaseBot): def __init__( self, ) -> None: @@ -31,10 +31,10 @@ async def ask_stream(self, query, **options): while True: if not streaming_call_queue.empty(): token = streaming_call_queue.get() - print(token, end='') + print(token, end="") yield token else: break except Exception as e: # 处理异常的代码 - print("An error occurred:", str(e)) \ No newline at end of file + print("An error occurred:", str(e)) diff --git a/xiaogpt/cli.py b/xiaogpt/cli.py index 160501bd..870e2deb 100644 --- a/xiaogpt/cli.py +++ b/xiaogpt/cli.py @@ -28,7 +28,7 @@ def main(): help="openai api key", ) parser.add_argument( - "--serpapi_api_key", + "--serpapi_api_key", dest="serpapi_api_key", help="serp api key see https://serpapi.com/", ) diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py index a3e84211..059aafdb 100644 --- a/xiaogpt/langchain/chain.py +++ b/xiaogpt/langchain/chain.py @@ -13,8 +13,14 @@ from langchain.memory import ConversationBufferMemory from xiaogpt.config import Config + def agent_search(query): - llm = ChatOpenAI(streaming=True, temperature=0, model="gpt-3.5-turbo-0613", callbacks=[StreamCallbackHandler()]) + llm = ChatOpenAI( + streaming=True, + temperature=0, + model="gpt-3.5-turbo-0613", + callbacks=[StreamCallbackHandler()], + ) # 初始化:搜索链、数学计算链、自定义总结邮件链 search = SerpAPIWrapper() @@ -23,27 +29,19 @@ def agent_search(query): # 工具列表:搜索、数学计算、自定义总结邮件 tools = [ - Tool( - name = "Search", - func=search.run, - description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案" - ), - Tool( - name="Calculator", - func=llm_math_chain.run, - description="在需要回答数学问题时非常有用" - ), + Tool(name="Search", func=search.run, description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案"), + Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), Tool( name="MailSummary", func=mail_summary.run, - description="这是一个工具,订阅每天收到的邮件,总结并发送到我邮箱" - ) + description="这是一个工具,订阅每天收到的邮件,总结并发送到我邮箱", + ), ] # 初始化 agent - agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS, verbose=False) + agent = initialize_agent( + tools, llm, agent=AgentType.OPENAI_FUNCTIONS, verbose=False + ) # query eg:'杭州亚运会中国队获得了多少枚金牌?' // '计算3的2次方' // '帮我总结今天收到的邮件' agent.run(query) - - diff --git a/xiaogpt/langchain/mail_box.py b/xiaogpt/langchain/mail_box.py index 0fab200f..06ce253d 100644 --- a/xiaogpt/langchain/mail_box.py +++ b/xiaogpt/langchain/mail_box.py @@ -8,43 +8,44 @@ import smtplib from email.mime.text import MIMEText + class Mailbox: # 需要配置Gmail帐号设置 - gmail_address = '' - gmail_password = '' + gmail_address = "" + gmail_password = "" # 连接到IMAP服务器 - imap_server = 'imap.gmail.com' + imap_server = "imap.gmail.com" imap_port = 993 # 定义邮件接收人,支持添加多个收件人邮箱地址 - to_addresses = [''] + to_addresses = [""] # 定义总结的邮件数目 max_emails = 3 def get_all_work_summary(self): - print('正在获取邮件...') + print("正在获取邮件...") try: # 建立IMAP连接 mailbox = imaplib.IMAP4_SSL(self.imap_server, self.imap_port) # 登录Gmail帐号 mailbox.login(self.gmail_address, self.gmail_password) # 选择邮箱 - mailbox.select('INBOX') + mailbox.select("INBOX") # 获取今天的日期 - today = datetime.now().strftime('%d-%b-%Y') + today = datetime.now().strftime("%d-%b-%Y") # 构建搜索条件 search_criteria = f'(SINCE "{today}")' # 搜索符合条件的邮件 status, email_ids = mailbox.search(None, search_criteria) - if status == 'OK': + if status == "OK": email_ids = email_ids[0].split() print(f"今天收到的邮件数量:{len(email_ids)}") # 限制最多获取15封邮件 max_emails = min(len(email_ids), self.max_emails) - all_email_content = '' + all_email_content = "" for i in range(max_emails): email_id = email_ids[i] @@ -59,64 +60,68 @@ def get_all_work_summary(self): return all_email_content except Exception as e: - print('获取邮件失败:', str(e)) + print("获取邮件失败:", str(e)) def get_email_content(self, mailbox, email_id): # 获取邮件内容 - status, email_data = mailbox.fetch(email_id, '(RFC822)') - if status == 'OK': + status, email_data = mailbox.fetch(email_id, "(RFC822)") + if status == "OK": raw_email = email_data[0][1] msg = email.message_from_bytes(raw_email) # 获取发件人 - sender = msg['From'] + sender = msg["From"] # 提取尖括号(<>)内的发件人 - sender = re.findall(r'<(.*?)>', sender) - sender = sender[0] if sender else '' + sender = re.findall(r"<(.*?)>", sender) + sender = sender[0] if sender else "" # 检查发件人的邮箱地址是否以 '@addcn.com' 结尾 - if sender.lower().endswith('@addcn.com') and not msg['In-Reply-To']: + if sender.lower().endswith("@addcn.com") and not msg["In-Reply-To"]: # 获取邮件内容 - email_content = '' + email_content = "" if msg.is_multipart(): for part in msg.walk(): content_type = part.get_content_type() - if content_type == 'text/plain': - email_content = part.get_payload(decode=True).decode('utf-8') + if content_type == "text/plain": + email_content = part.get_payload(decode=True).decode( + "utf-8" + ) break - elif content_type == 'text/html': - email_content = part.get_payload(decode=True).decode('utf-8') + elif content_type == "text/html": + email_content = part.get_payload(decode=True).decode( + "utf-8" + ) email_content = html.unescape(email_content) # 过滤HTML代码 break else: - email_content = msg.get_payload(decode=True).decode('utf-8') + email_content = msg.get_payload(decode=True).decode("utf-8") # 如果仍然包含html代码,则使用BeautifulSoup过滤html代码 - if 'html' in email_content.lower(): - soup = BeautifulSoup(email_content, 'html.parser') + if "html" in email_content.lower(): + soup = BeautifulSoup(email_content, "html.parser") email_content = soup.get_text() # 输出文本格式 - email_content = re.sub(r'\s+', '', email_content) + email_content = re.sub(r"\s+", "", email_content) # 过滤 = 号之间的内容 - email_content = re.sub(r'=\?.*?\?=', '', email_content) + email_content = re.sub(r"=\?.*?\?=", "", email_content) # 过滤 --符号之后的内容 - email_content = re.sub(r'---.*', '', email_content) + email_content = re.sub(r"---.*", "", email_content) return f"{sender}发送邮件,内容是{email_content}" - return '' + return "" def get_summary_by_ai(self, email_content: str, prompt: str) -> str: - print('正在请AI总结邮件内容...') + print("正在请AI总结邮件内容...") # 请求ChatGPT进行总结 response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=[ {"role": "system", "content": prompt}, - {"role": "user", "content": email_content} - ] + {"role": "user", "content": email_content}, + ], ) # 提取ChatGPT生成的总结 @@ -124,19 +129,19 @@ def get_summary_by_ai(self, email_content: str, prompt: str) -> str: # print(summary) return summary - def send_mail(self, summary, theme='邮件摘要汇总'): + def send_mail(self, summary, theme="邮件摘要汇总"): # 设置发件人和收件人 from_address = self.gmail_address to_addresses = self.to_addresses # 添加多个收件人邮箱地址 # 构建邮件内容 - yesterday = (datetime.now() - timedelta(days=0)).strftime('%Y-%m-%d') + yesterday = (datetime.now() - timedelta(days=0)).strftime("%Y-%m-%d") subject = yesterday + theme body = summary try: # 连接到SMTP服务器 - smtp_server = smtplib.SMTP('smtp.gmail.com', 587) + smtp_server = smtplib.SMTP("smtp.gmail.com", 587) smtp_server.ehlo() smtp_server.starttls() # 登录邮箱 @@ -144,18 +149,18 @@ def send_mail(self, summary, theme='邮件摘要汇总'): for to_address in to_addresses: # 创建纯文本邮件消息对象 - message = MIMEText(body, 'plain', 'utf-8') - message['Subject'] = subject - message['From'] = from_address - message['To'] = to_address + message = MIMEText(body, "plain", "utf-8") + message["Subject"] = subject + message["From"] = from_address + message["To"] = to_address # 发送邮件 smtp_server.sendmail(from_address, to_address, message.as_string()) - print('邮件发送成功至:', to_address) + print("邮件发送成功至:", to_address) # 关闭连接 smtp_server.quit() - print('所有邮件已成功发送!') + print("所有邮件已成功发送!") return True except Exception as e: - print('邮件发送失败:', str(e)) \ No newline at end of file + print("邮件发送失败:", str(e)) diff --git a/xiaogpt/langchain/mail_summary_tools.py b/xiaogpt/langchain/mail_summary_tools.py index 2981ff8e..519efd6d 100644 --- a/xiaogpt/langchain/mail_summary_tools.py +++ b/xiaogpt/langchain/mail_summary_tools.py @@ -24,7 +24,7 @@ def get_mail_summary(self) -> str: is_success = mailbox.send_mail(gpt_content) if is_success: return "总结邮件成功" - else: + else: return "总结邮件失败,请检查邮箱配置" def _run(self, query: str) -> str: @@ -32,4 +32,4 @@ def _run(self, query: str) -> str: async def _arun(self, query: str) -> str: """Use the tool asynchronously.""" - raise NotImplementedError("MailSummaryTool does not support async") \ No newline at end of file + raise NotImplementedError("MailSummaryTool does not support async") diff --git a/xiaogpt/langchain/stream_call_back.py b/xiaogpt/langchain/stream_call_back.py index 83b1dd70..6be52f21 100755 --- a/xiaogpt/langchain/stream_call_back.py +++ b/xiaogpt/langchain/stream_call_back.py @@ -6,10 +6,11 @@ DEFAULT_ANSWER_PREFIX_TOKENS = [""] streaming_call_queue = Queue() + class StreamCallbackHandler(StreamingStdOutCallbackHandler): def on_llm_new_token(self, token: str, **kwargs: Any) -> None: token_copy = token - code = chardet.detect(token_copy.encode())['encoding'] + code = chardet.detect(token_copy.encode())["encoding"] if code is not None: # 接收流消息入队,在ask_stream时出队 - streaming_call_queue.put(token) \ No newline at end of file + streaming_call_queue.put(token) From 489d25422a212877d36942302c283b60829773ce Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Wed, 18 Oct 2023 19:29:08 +0800 Subject: [PATCH 04/11] =?UTF-8?q?Fix=EF=BC=9Acode=20formatting=20black=20.?= =?UTF-8?q?=20docs=EF=BC=9AConvert=20to=20English=20annotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 +++- xiao_config.json.example | 1 + xiaogpt/bot/langchain_bot.py | 13 +++- xiaogpt/cli.py | 10 +-- xiaogpt/config.py | 2 +- xiaogpt/langchain/chain.py | 8 +-- xiaogpt/langchain/mail_box.py | 86 +++++++++++++------------ xiaogpt/langchain/mail_summary_tools.py | 1 + 8 files changed, 76 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index f9012605..5888150b 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Play ChatGPT and other LLM with Xiaomi AI Speaker - 如果你遇到了墙需要用 Cloudflare Workers 替换 api_base 请使用 `--api_base ${url}` 来替换。 **请注意,此处你输入的api应该是'`https://xxxx/v1`'的字样,域名需要用引号包裹** - 可以跟小爱说 `开始持续对话` 自动进入持续对话状态,`结束持续对话` 结束持续对话状态。 - 可以使用 `--enable_edge_tts` 来获取更好的 tts 能力 +- 可以使用 `--use_langchain` 替代 `--use_chatgpt_api` 来调用LangChain(默认chatgpt)服务,实现上网检索、数学运算.. e.g. @@ -76,6 +77,10 @@ export OPENAI_API_KEY=${your_api_key} xiaogpt --hardware LX06 --mute_xiaoai --use_gpt3 # 如果你想用 edge-tts xiaogpt --hardware LX06 --cookie ${cookie} --use_chatgpt_api --enable_edge_tts +# 如果你想使用 LangChain + SerpApi 实现上网检索或其他本地服务(目前仅支持stream模式) +export OPENAI_API_KEY=${your_api_key} +export SERPAPI_API_KEY=${your_serpapi_key} +xiaogpt --hardware Lx06 --use_langchain --mute_xiaoai --stream --openai_key ${your_api_key} --serpapi_api_key ${your_serpapi_key} ``` 使用 git clone 运行 @@ -93,11 +98,14 @@ python3 xiaogpt.py --hardware LX06 --mute_xiaoai --stream # 如果你想使用 gpt3 ai export OPENAI_API_KEY=${your_api_key} python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_gpt3 - # 如果你想使用 ChatGLM api python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_glm --glm_key ${glm_key} # 如果你想使用 google 的 bard python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_bard --bard_token ${bard_token} +# 如果你想使用 LangChain+SerpApi 实现上网检索或其他本地服务(目前仅支持stream模式) +export OPENAI_API_KEY=${your_api_key} +export SERPAPI_API_KEY=${your_serpapi_key} +python3 xiaogpt.py --hardware Lx06 --use_langchain --mute_xiaoai --stream --openai_key ${your_api_key} --serpapi_api_key ${your_serpapi_key} ``` ## config.json @@ -139,6 +147,7 @@ Bard-API [参考](https://github.com/dsdanielpark/Bard-API) | account | 小爱账户 | | | password | 小爱账户密码 | | | openai_key | openai的apikey | | +| serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | | glm_key | chatglm 的 apikey | | | bard_token | bard 的 token 参考 [Bard-API](https://github.com/dsdanielpark/Bard-API) | | | cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | diff --git a/xiao_config.json.example b/xiao_config.json.example index 2a98f099..e3129e7e 100644 --- a/xiao_config.json.example +++ b/xiao_config.json.example @@ -5,6 +5,7 @@ "openai_key": "", "glm_key": "", "bard_token": "", + "serpapi_api_key": "", "cookie": "", "mi_did": "", "use_command": false, diff --git a/xiaogpt/bot/langchain_bot.py b/xiaogpt/bot/langchain_bot.py index 8b84709a..080b0e47 100644 --- a/xiaogpt/bot/langchain_bot.py +++ b/xiaogpt/bot/langchain_bot.py @@ -9,20 +9,27 @@ from xiaogpt.langchain.chain import agent_search from xiaogpt.langchain.stream_call_back import streaming_call_queue +import os + class LangChainBot(BaseBot): def __init__( self, + openai_key: str, + serpapi_api_key: str, ) -> None: - # todo,建议在langchain内实现 + # Set environment indicators + os.environ["OPENAI_API_KEY"] = openai_key + os.environ["SERPAPI_API_KEY"] = serpapi_api_key + # todo,Plan to implement within langchain self.history = [] @classmethod def from_config(cls, config): - return cls() + return cls(openai_key=config.openai_key, serpapi_api_key=config.serpapi_api_key) async def ask(self, query, **options): - # todo,目前仅支持stream + # todo,Currently only supports stream raise Exception("The bot does not support it. Please use 'ask_stream'.") async def ask_stream(self, query, **options): diff --git a/xiaogpt/cli.py b/xiaogpt/cli.py index 870e2deb..dfa83bc8 100644 --- a/xiaogpt/cli.py +++ b/xiaogpt/cli.py @@ -27,11 +27,6 @@ def main(): dest="openai_key", help="openai api key", ) - parser.add_argument( - "--serpapi_api_key", - dest="serpapi_api_key", - help="serp api key see https://serpapi.com/", - ) parser.add_argument( "--glm_key", dest="glm_key", @@ -42,6 +37,11 @@ def main(): dest="bard_token", help="google bard token see https://github.com/dsdanielpark/Bard-API", ) + parser.add_argument( + "--serpapi_api_key", + dest="serpapi_api_key", + help="serp api key see https://serpapi.com/", + ) parser.add_argument( "--proxy", dest="proxy", diff --git a/xiaogpt/config.py b/xiaogpt/config.py index d007e27b..85bd6ac7 100644 --- a/xiaogpt/config.py +++ b/xiaogpt/config.py @@ -60,9 +60,9 @@ class Config: account: str = os.getenv("MI_USER", "") password: str = os.getenv("MI_PASS", "") openai_key: str = os.getenv("OPENAI_API_KEY", "") - serpapi_api_key: str = os.getenv("SERPAPI_API_KEY", "") glm_key: str = os.getenv("CHATGLM_KEY", "") bard_token: str = os.getenv("BARD_TOKEN", "") + serpapi_api_key: str = os.getenv("SERPAPI_API_KEY", "") proxy: str | None = None mi_did: str = os.getenv("MI_DID", "") keyword: Iterable[str] = KEY_WORD diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py index 059aafdb..38537b56 100644 --- a/xiaogpt/langchain/chain.py +++ b/xiaogpt/langchain/chain.py @@ -11,7 +11,8 @@ from xiaogpt.langchain.mail_box import Mailbox from xiaogpt.langchain.mail_summary_tools import MailSummaryTool from langchain.memory import ConversationBufferMemory -from xiaogpt.config import Config + +# from xiaogpt.config import Config def agent_search(query): @@ -22,12 +23,12 @@ def agent_search(query): callbacks=[StreamCallbackHandler()], ) - # 初始化:搜索链、数学计算链、自定义总结邮件链 + # Initialization: search chain, mathematical calculation chain, custom summary email chain search = SerpAPIWrapper() llm_math_chain = LLMMathChain(llm=llm, verbose=False) mail_summary = MailSummaryTool() - # 工具列表:搜索、数学计算、自定义总结邮件 + # Tool list: search, mathematical calculations, custom summary emails tools = [ Tool(name="Search", func=search.run, description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案"), Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), @@ -38,7 +39,6 @@ def agent_search(query): ), ] - # 初始化 agent agent = initialize_agent( tools, llm, agent=AgentType.OPENAI_FUNCTIONS, verbose=False ) diff --git a/xiaogpt/langchain/mail_box.py b/xiaogpt/langchain/mail_box.py index 06ce253d..88a053e3 100644 --- a/xiaogpt/langchain/mail_box.py +++ b/xiaogpt/langchain/mail_box.py @@ -10,40 +10,40 @@ class Mailbox: - # 需要配置Gmail帐号设置 + # Gmail account settings need to be configured gmail_address = "" gmail_password = "" - # 连接到IMAP服务器 + # Connect to IMAP server imap_server = "imap.gmail.com" imap_port = 993 - # 定义邮件接收人,支持添加多个收件人邮箱地址 + # Define email recipients and support adding multiple required email addresses to_addresses = [""] - # 定义总结的邮件数目 + # Define the number of emails to summarize max_emails = 3 def get_all_work_summary(self): - print("正在获取邮件...") + print("Getting mail...") try: - # 建立IMAP连接 + # Establish an IMAP connection mailbox = imaplib.IMAP4_SSL(self.imap_server, self.imap_port) - # 登录Gmail帐号 + # Log in to your Gmail account mailbox.login(self.gmail_address, self.gmail_password) - # 选择邮箱 + # Select email mailbox.select("INBOX") - # 获取今天的日期 + # Get today's date today = datetime.now().strftime("%d-%b-%Y") - # 构建搜索条件 + # Build search criteria search_criteria = f'(SINCE "{today}")' - # 搜索符合条件的邮件 + # Search for matching messages status, email_ids = mailbox.search(None, search_criteria) if status == "OK": email_ids = email_ids[0].split() - print(f"今天收到的邮件数量:{len(email_ids)}") - # 限制最多获取15封邮件 + print(f"Number of emails received today: {len(email_ids)}") + # Limit fetching up to max_emails emails max_emails = min(len(email_ids), self.max_emails) all_email_content = "" @@ -55,29 +55,29 @@ def get_all_work_summary(self): # print(all_email_content) - # 关闭连接 + # close connection mailbox.logout() return all_email_content except Exception as e: - print("获取邮件失败:", str(e)) + print("Failed to get email:", str(e)) def get_email_content(self, mailbox, email_id): - # 获取邮件内容 + # Get email content status, email_data = mailbox.fetch(email_id, "(RFC822)") if status == "OK": raw_email = email_data[0][1] msg = email.message_from_bytes(raw_email) - # 获取发件人 + # Get sender sender = msg["From"] - # 提取尖括号(<>)内的发件人 + # Extract senders within angle brackets (<>) sender = re.findall(r"<(.*?)>", sender) sender = sender[0] if sender else "" - # 检查发件人的邮箱地址是否以 '@addcn.com' 结尾 - if sender.lower().endswith("@addcn.com") and not msg["In-Reply-To"]: - # 获取邮件内容 + # Check whether the sender's email address ends with '.com', expand the field, and use it for email sender filtering + if sender.lower().endswith(".com") and not msg["In-Reply-To"]: + # Get email content email_content = "" if msg.is_multipart(): for part in msg.walk(): @@ -91,31 +91,33 @@ def get_email_content(self, mailbox, email_id): email_content = part.get_payload(decode=True).decode( "utf-8" ) - email_content = html.unescape(email_content) # 过滤HTML代码 + email_content = html.unescape( + email_content + ) # Filter HTML code break else: email_content = msg.get_payload(decode=True).decode("utf-8") - # 如果仍然包含html代码,则使用BeautifulSoup过滤html代码 + # Use BeautifulSoup to filter the html code if it still contains it if "html" in email_content.lower(): soup = BeautifulSoup(email_content, "html.parser") email_content = soup.get_text() - # 输出文本格式 + # Output text format email_content = re.sub(r"\s+", "", email_content) - # 过滤 = 号之间的内容 + # Filter content between = signs email_content = re.sub(r"=\?.*?\?=", "", email_content) - # 过滤 --符号之后的内容 + # Filter --content after the symbol email_content = re.sub(r"---.*", "", email_content) - return f"{sender}发送邮件,内容是{email_content}" + return f"{sender}Send an email with the content{email_content}" return "" def get_summary_by_ai(self, email_content: str, prompt: str) -> str: - print("正在请AI总结邮件内容...") + print("Asking AI to summarize email content...") - # 请求ChatGPT进行总结 + # Request ChatGPT for summary response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0613", messages=[ @@ -124,43 +126,43 @@ def get_summary_by_ai(self, email_content: str, prompt: str) -> str: ], ) - # 提取ChatGPT生成的总结 + # Extract summary generated by ChatGPT summary = response.choices[0].message.content.strip() # print(summary) return summary - def send_mail(self, summary, theme="邮件摘要汇总"): - # 设置发件人和收件人 + def send_mail(self, summary, theme="Email summary summary"): + # Set senders and recipients from_address = self.gmail_address - to_addresses = self.to_addresses # 添加多个收件人邮箱地址 + to_addresses = self.to_addresses # Add multiple recipient email addresses - # 构建邮件内容 + # Build email content yesterday = (datetime.now() - timedelta(days=0)).strftime("%Y-%m-%d") subject = yesterday + theme body = summary try: - # 连接到SMTP服务器 + # Connect to SMTP server smtp_server = smtplib.SMTP("smtp.gmail.com", 587) smtp_server.ehlo() smtp_server.starttls() - # 登录邮箱 + # Login E-mail smtp_server.login(self.gmail_address, self.gmail_password) for to_address in to_addresses: - # 创建纯文本邮件消息对象 + # Create a plain text mail message object message = MIMEText(body, "plain", "utf-8") message["Subject"] = subject message["From"] = from_address message["To"] = to_address - # 发送邮件 + # send email smtp_server.sendmail(from_address, to_address, message.as_string()) - print("邮件发送成功至:", to_address) + print("Email sent successfully to:", to_address) - # 关闭连接 + # close connection smtp_server.quit() - print("所有邮件已成功发送!") + print("All emails have been sent successfully!") return True except Exception as e: - print("邮件发送失败:", str(e)) + print("Email sending failed:", str(e)) diff --git a/xiaogpt/langchain/mail_summary_tools.py b/xiaogpt/langchain/mail_summary_tools.py index 519efd6d..9c29a77e 100644 --- a/xiaogpt/langchain/mail_summary_tools.py +++ b/xiaogpt/langchain/mail_summary_tools.py @@ -1,6 +1,7 @@ from langchain.tools import BaseTool from xiaogpt.langchain.mail_box import Mailbox + # 总结日志工具 class MailSummaryTool(BaseTool): name = "MailSumary" From 8719b007989573f45452753ce48d27fda3b5fcd3 Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Wed, 18 Oct 2023 19:34:03 +0800 Subject: [PATCH 05/11] =?UTF-8?q?Fix=EF=BC=9Acode=20formatting=20black=20.?= =?UTF-8?q?=20docs=EF=BC=9AConvert=20to=20English=20annotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xiaogpt/langchain/mail_summary_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/xiaogpt/langchain/mail_summary_tools.py b/xiaogpt/langchain/mail_summary_tools.py index 9c29a77e..9336c456 100644 --- a/xiaogpt/langchain/mail_summary_tools.py +++ b/xiaogpt/langchain/mail_summary_tools.py @@ -2,12 +2,10 @@ from xiaogpt.langchain.mail_box import Mailbox -# 总结日志工具 class MailSummaryTool(BaseTool): name = "MailSumary" description = "当被问到总结邮件相关时,会触发这个工具,进行今日邮件总结和发送。当调用工具完毕,只需要回复总结成功或失败即可,立即结束本次回答" - # 新增日志总结工具 def get_mail_summary(self) -> str: """ 总结邮件:对邮箱内收到的邮件进行总结,并发送到指定邮箱 From 35f2bcfdf8cd250e017651844ef439e7178dedd9 Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Thu, 19 Oct 2023 09:53:44 +0800 Subject: [PATCH 06/11] =?UTF-8?q?fix=EF=BC=9Arequirement.txt=20adds=20pack?= =?UTF-8?q?age=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 3 +++ xiaogpt/langchain/chain.py | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 58fb17d8..d40601f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,3 +60,6 @@ wcwidth==0.2.6 websockets==11.0 yarl==1.8.2 zhipuai==1.0.7 +langchain==0.0.314 +datetime==5.2 +bs4=0.0.1 diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py index 38537b56..0016c8cc 100644 --- a/xiaogpt/langchain/chain.py +++ b/xiaogpt/langchain/chain.py @@ -12,8 +12,6 @@ from xiaogpt.langchain.mail_summary_tools import MailSummaryTool from langchain.memory import ConversationBufferMemory -# from xiaogpt.config import Config - def agent_search(query): llm = ChatOpenAI( From 31c8a7eabc23b269d6d79727c7b61dbd6d5f036f Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Thu, 19 Oct 2023 10:38:48 +0800 Subject: [PATCH 07/11] =?UTF-8?q?Fix=EF=BC=9AAdjust=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c580705f..2be168ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,9 @@ dependencies = [ "bardapi", "edge-tts>=6.1.3", "EdgeGPT==0.1.26", + "langchain", + "datetime", + "bs4", ] license = {text = "MIT"} dynamic = ["version"] From 7739047da6854f40ea1e0a3935c9c123bf042bab Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Thu, 19 Oct 2023 10:44:09 +0800 Subject: [PATCH 08/11] =?UTF-8?q?Fix=EF=BC=9AAdjust=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 ++ requirements.txt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2be168ad..2e3b6646 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,8 @@ dependencies = [ "langchain", "datetime", "bs4", + "chardet", + "typing" ] license = {text = "MIT"} dynamic = ["version"] diff --git a/requirements.txt b/requirements.txt index d40601f2..05cc3103 100644 --- a/requirements.txt +++ b/requirements.txt @@ -62,4 +62,6 @@ yarl==1.8.2 zhipuai==1.0.7 langchain==0.0.314 datetime==5.2 -bs4=0.0.1 +bs4==0.0.1 +chardet==5.1.0 +typing==3.7.4.3 From 630e5630dca45af59460ac1bb8402caa12b14333 Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Fri, 20 Oct 2023 12:08:47 +0800 Subject: [PATCH 09/11] =?UTF-8?q?docs=EF=BC=9Adocument=20optimization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- pyproject.toml | 12 +++++++----- requirements.txt | 5 ++++- xiaogpt/bot/langchain_bot.py | 5 +++-- xiaogpt/langchain/chain.py | 8 +++++--- xiaogpt/langchain/stream_call_back.py | 2 +- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5888150b..d9991c68 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Play ChatGPT and other LLM with Xiaomi AI Speaker - 如果你遇到了墙需要用 Cloudflare Workers 替换 api_base 请使用 `--api_base ${url}` 来替换。 **请注意,此处你输入的api应该是'`https://xxxx/v1`'的字样,域名需要用引号包裹** - 可以跟小爱说 `开始持续对话` 自动进入持续对话状态,`结束持续对话` 结束持续对话状态。 - 可以使用 `--enable_edge_tts` 来获取更好的 tts 能力 -- 可以使用 `--use_langchain` 替代 `--use_chatgpt_api` 来调用LangChain(默认chatgpt)服务,实现上网检索、数学运算.. +- 可以使用 `--use_langchain` 替代 `--use_chatgpt_api` 来调用 LangChain(默认 chatgpt)服务,实现上网检索、数学运算.. e.g. @@ -77,7 +77,7 @@ export OPENAI_API_KEY=${your_api_key} xiaogpt --hardware LX06 --mute_xiaoai --use_gpt3 # 如果你想用 edge-tts xiaogpt --hardware LX06 --cookie ${cookie} --use_chatgpt_api --enable_edge_tts -# 如果你想使用 LangChain + SerpApi 实现上网检索或其他本地服务(目前仅支持stream模式) +# 如果你想使用 LangChain + SerpApi 实现上网检索或其他本地服务(目前仅支持 stream 模式) export OPENAI_API_KEY=${your_api_key} export SERPAPI_API_KEY=${your_serpapi_key} xiaogpt --hardware Lx06 --use_langchain --mute_xiaoai --stream --openai_key ${your_api_key} --serpapi_api_key ${your_serpapi_key} @@ -102,7 +102,7 @@ python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_gpt3 python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_glm --glm_key ${glm_key} # 如果你想使用 google 的 bard python3 xiaogpt.py --hardware LX06 --mute_xiaoai --use_bard --bard_token ${bard_token} -# 如果你想使用 LangChain+SerpApi 实现上网检索或其他本地服务(目前仅支持stream模式) +# 如果你想使用 LangChain+SerpApi 实现上网检索或其他本地服务(目前仅支持 stream 模式) export OPENAI_API_KEY=${your_api_key} export SERPAPI_API_KEY=${your_serpapi_key} python3 xiaogpt.py --hardware Lx06 --use_langchain --mute_xiaoai --stream --openai_key ${your_api_key} --serpapi_api_key ${your_serpapi_key} diff --git a/pyproject.toml b/pyproject.toml index 2e3b6646..60d3008f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,11 +20,13 @@ dependencies = [ "bardapi", "edge-tts>=6.1.3", "EdgeGPT==0.1.26", - "langchain", - "datetime", - "bs4", - "chardet", - "typing" + "langchain==0.0.301", + "datetime==5.2", + "bs4==0.0.1", + "chardet==5.1.0", + "typing==3.7.4.3", + "google-search-results==2.4.2", + "numexpr==2.8.6" ] license = {text = "MIT"} dynamic = ["version"] diff --git a/requirements.txt b/requirements.txt index 05cc3103..3d6015db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,8 +60,11 @@ wcwidth==0.2.6 websockets==11.0 yarl==1.8.2 zhipuai==1.0.7 -langchain==0.0.314 +langchain==0.0.301 datetime==5.2 bs4==0.0.1 chardet==5.1.0 typing==3.7.4.3 +google-search-results==2.4.2 +numexpr==2.8.6 + diff --git a/xiaogpt/bot/langchain_bot.py b/xiaogpt/bot/langchain_bot.py index 080b0e47..24d5a9f9 100644 --- a/xiaogpt/bot/langchain_bot.py +++ b/xiaogpt/bot/langchain_bot.py @@ -30,7 +30,9 @@ def from_config(cls, config): async def ask(self, query, **options): # todo,Currently only supports stream - raise Exception("The bot does not support it. Please use 'ask_stream'.") + raise Exception( + "The bot does not support it. Please use 'ask_stream,add: --stream'" + ) async def ask_stream(self, query, **options): agent_search(query) @@ -43,5 +45,4 @@ async def ask_stream(self, query, **options): else: break except Exception as e: - # 处理异常的代码 print("An error occurred:", str(e)) diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py index 0016c8cc..a7ffe094 100644 --- a/xiaogpt/langchain/chain.py +++ b/xiaogpt/langchain/chain.py @@ -2,7 +2,9 @@ from langchain.agents import AgentType from langchain.tools import BaseTool from langchain.llms import OpenAI -from langchain import LLMMathChain, SerpAPIWrapper + +# from langchain.chains import LLMMathChain +from langchain.utilities import SerpAPIWrapper from langchain.chat_models import ChatOpenAI from langchain.memory import ChatMessageHistory from xiaogpt.langchain.stream_call_back import StreamCallbackHandler @@ -23,13 +25,13 @@ def agent_search(query): # Initialization: search chain, mathematical calculation chain, custom summary email chain search = SerpAPIWrapper() - llm_math_chain = LLMMathChain(llm=llm, verbose=False) + # llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=False) mail_summary = MailSummaryTool() # Tool list: search, mathematical calculations, custom summary emails tools = [ Tool(name="Search", func=search.run, description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案"), - Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), + # Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), Tool( name="MailSummary", func=mail_summary.run, diff --git a/xiaogpt/langchain/stream_call_back.py b/xiaogpt/langchain/stream_call_back.py index 6be52f21..007edd52 100755 --- a/xiaogpt/langchain/stream_call_back.py +++ b/xiaogpt/langchain/stream_call_back.py @@ -12,5 +12,5 @@ def on_llm_new_token(self, token: str, **kwargs: Any) -> None: token_copy = token code = chardet.detect(token_copy.encode())["encoding"] if code is not None: - # 接收流消息入队,在ask_stream时出队 + # Receive stream messages into the queue and dequeue when ask_stream streaming_call_queue.put(token) From 45abf28b35c53b61dc8960eab2cd6406fb47ac8b Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Fri, 20 Oct 2023 12:14:39 +0800 Subject: [PATCH 10/11] =?UTF-8?q?docs=EF=BC=9Arestore=20commented=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xiaogpt/langchain/chain.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py index a7ffe094..e9bcbd3e 100644 --- a/xiaogpt/langchain/chain.py +++ b/xiaogpt/langchain/chain.py @@ -2,8 +2,7 @@ from langchain.agents import AgentType from langchain.tools import BaseTool from langchain.llms import OpenAI - -# from langchain.chains import LLMMathChain +from langchain.chains import LLMMathChain from langchain.utilities import SerpAPIWrapper from langchain.chat_models import ChatOpenAI from langchain.memory import ChatMessageHistory @@ -25,13 +24,13 @@ def agent_search(query): # Initialization: search chain, mathematical calculation chain, custom summary email chain search = SerpAPIWrapper() - # llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=False) + llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=False) mail_summary = MailSummaryTool() # Tool list: search, mathematical calculations, custom summary emails tools = [ Tool(name="Search", func=search.run, description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案"), - # Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), + Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), Tool( name="MailSummary", func=mail_summary.run, From ee41c7dc0ac62905cd27da990cd20c29031a09cb Mon Sep 17 00:00:00 2001 From: dongjing <18819816978@163.com> Date: Sat, 21 Oct 2023 22:13:39 +0800 Subject: [PATCH 11/11] =?UTF-8?q?refactor=EF=BC=9A=20-=20sample=20deletion?= =?UTF-8?q?=20email=20summary=20-=20directory=20optimization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: - the default value should be 'chatgptapi' instead of 'chatgpt' --- xiaogpt/config.py | 2 +- xiaogpt/langchain/chain.py | 14 +++----------- xiaogpt/langchain/{ => examples/email}/mail_box.py | 0 .../{ => examples/email}/mail_summary_tools.py | 2 +- 4 files changed, 5 insertions(+), 13 deletions(-) rename xiaogpt/langchain/{ => examples/email}/mail_box.py (100%) rename xiaogpt/langchain/{ => examples/email}/mail_summary_tools.py (96%) diff --git a/xiaogpt/config.py b/xiaogpt/config.py index 85bd6ac7..cb6bc87a 100644 --- a/xiaogpt/config.py +++ b/xiaogpt/config.py @@ -69,7 +69,7 @@ class Config: change_prompt_keyword: Iterable[str] = CHANGE_PROMPT_KEY_WORD prompt: str = PROMPT mute_xiaoai: bool = False - bot: str = "chatgpt" + bot: str = "chatgptapi" cookie: str = "" api_base: str | None = None deployment_id: str | None = None diff --git a/xiaogpt/langchain/chain.py b/xiaogpt/langchain/chain.py index e9bcbd3e..5cda4888 100644 --- a/xiaogpt/langchain/chain.py +++ b/xiaogpt/langchain/chain.py @@ -9,8 +9,6 @@ from xiaogpt.langchain.stream_call_back import StreamCallbackHandler from langchain.agents.agent_toolkits import ZapierToolkit from langchain.utilities.zapier import ZapierNLAWrapper -from xiaogpt.langchain.mail_box import Mailbox -from xiaogpt.langchain.mail_summary_tools import MailSummaryTool from langchain.memory import ConversationBufferMemory @@ -22,25 +20,19 @@ def agent_search(query): callbacks=[StreamCallbackHandler()], ) - # Initialization: search chain, mathematical calculation chain, custom summary email chain + # Initialization: search chain, mathematical calculation chain search = SerpAPIWrapper() llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=False) - mail_summary = MailSummaryTool() - # Tool list: search, mathematical calculations, custom summary emails + # Tool list: search, mathematical calculations tools = [ Tool(name="Search", func=search.run, description="如果你不知道或不确定答案,可以使用这个搜索引擎检索答案"), Tool(name="Calculator", func=llm_math_chain.run, description="在需要回答数学问题时非常有用"), - Tool( - name="MailSummary", - func=mail_summary.run, - description="这是一个工具,订阅每天收到的邮件,总结并发送到我邮箱", - ), ] agent = initialize_agent( tools, llm, agent=AgentType.OPENAI_FUNCTIONS, verbose=False ) - # query eg:'杭州亚运会中国队获得了多少枚金牌?' // '计算3的2次方' // '帮我总结今天收到的邮件' + # query eg:'杭州亚运会中国队获得了多少枚金牌?' // '计算3的2次方' agent.run(query) diff --git a/xiaogpt/langchain/mail_box.py b/xiaogpt/langchain/examples/email/mail_box.py similarity index 100% rename from xiaogpt/langchain/mail_box.py rename to xiaogpt/langchain/examples/email/mail_box.py diff --git a/xiaogpt/langchain/mail_summary_tools.py b/xiaogpt/langchain/examples/email/mail_summary_tools.py similarity index 96% rename from xiaogpt/langchain/mail_summary_tools.py rename to xiaogpt/langchain/examples/email/mail_summary_tools.py index 9336c456..0c4a7c16 100644 --- a/xiaogpt/langchain/mail_summary_tools.py +++ b/xiaogpt/langchain/examples/email/mail_summary_tools.py @@ -1,5 +1,5 @@ from langchain.tools import BaseTool -from xiaogpt.langchain.mail_box import Mailbox +from xiaogpt.langchain.examples.email.mail_box import Mailbox class MailSummaryTool(BaseTool):