Skip to content

Commit a6168de

Browse files
committed
Add function call feature, implement autonomous judgment on whether to search using function call, restructure code directory, add search command to enforce search.
1 parent ab63bbc commit a6168de

16 files changed

+194
-51
lines changed
File renamed without changes.

.github/fly_deploy.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ jobs:
2222
uses: actions/checkout@v2
2323
- name: Deploy
2424
run: |
25-
sh ./deploy.sh
25+
sh ./.github/deploy.sh

bot.py

+72-16
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
import config
44
import logging
55
import traceback
6-
import decorators
7-
from md2tgmd import escape
8-
from runasync import run_async
6+
import utils.decorators as decorators
7+
from utils.md2tgmd import escape
8+
from utils.runasync import run_async
99
from chatgpt2api.chatgpt2api import Chatbot as GPT
1010
from telegram.constants import ChatAction
11-
from agent import docQA, get_doc_from_local
11+
from utils.agent import docQA, get_doc_from_local
1212
from telegram import BotCommand, InlineKeyboardButton, InlineKeyboardMarkup
1313
from telegram.ext import CommandHandler, MessageHandler, ApplicationBuilder, filters, CallbackQueryHandler
1414

1515

1616
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
17+
# logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1718
logger = logging.getLogger()
1819

1920
# 获取 httpx 的 logger
@@ -97,12 +98,65 @@ async def getChatGPT(update, context, title, robot, message, use_search=config.S
9798
reply_to_message_id=update.message.message_id,
9899
)
99100
messageid = message.message_id
100-
if use_search and not has_command:
101-
get_answer = robot.search_summary
102-
else:
103-
get_answer = robot.ask_stream
101+
get_answer = robot.ask_stream
102+
# if use_search and not has_command:
103+
# get_answer = robot.search_summary
104+
# else:
105+
# get_answer = robot.ask_stream
106+
if not config.API or (config.USE_G4F and not config.SEARCH_USE_GPT):
107+
import utils.gpt4free as gpt4free
108+
get_answer = gpt4free.get_response
109+
110+
try:
111+
for data in get_answer(text, convo_id=str(update.message.chat_id), pass_history=config.PASS_HISTORY):
112+
result = result + data
113+
tmpresult = result
114+
modifytime = modifytime + 1
115+
if re.sub(r"```", '', result).count("`") % 2 != 0:
116+
tmpresult = result + "`"
117+
if result.count("```") % 2 != 0:
118+
tmpresult = result + "\n```"
119+
if modifytime % 20 == 0 and lastresult != tmpresult:
120+
if 'claude2' in title:
121+
tmpresult = re.sub(r",", ',', tmpresult)
122+
await context.bot.edit_message_text(chat_id=update.message.chat_id, message_id=messageid, text=escape(tmpresult), parse_mode='MarkdownV2', disable_web_page_preview=True)
123+
lastresult = tmpresult
124+
except Exception as e:
125+
print('\033[31m')
126+
print("response_msg", result)
127+
print("error", e)
128+
traceback.print_exc()
129+
print('\033[0m')
130+
if config.API:
131+
robot.reset(convo_id=str(update.message.chat_id), system_prompt=config.systemprompt)
132+
if "You exceeded your current quota, please check your plan and billing details." in str(e):
133+
print("OpenAI api 已过期!")
134+
await context.bot.delete_message(chat_id=update.message.chat_id, message_id=messageid)
135+
messageid = ''
136+
config.API = ''
137+
result += f"`出错啦!{e}`"
138+
print(result)
139+
if lastresult != result and messageid:
140+
if 'claude2' in title:
141+
result = re.sub(r",", ',', result)
142+
await context.bot.edit_message_text(chat_id=update.message.chat_id, message_id=messageid, text=escape(result), parse_mode='MarkdownV2', disable_web_page_preview=True)
143+
144+
async def search(update, context, title, robot):
145+
message = update.message.text if config.NICK is None else update.message.text[botNicKLength:].strip() if update.message.text[:botNicKLength].lower() == botNick else None
146+
result = title
147+
text = message
148+
modifytime = 0
149+
lastresult = ''
150+
message = await context.bot.send_message(
151+
chat_id=update.message.chat_id,
152+
text="搜索中💭",
153+
parse_mode='MarkdownV2',
154+
reply_to_message_id=update.message.message_id,
155+
)
156+
messageid = message.message_id
157+
get_answer = robot.search_summary
104158
if not config.API or (config.USE_G4F and not config.SEARCH_USE_GPT):
105-
import gpt4free
159+
import utils.gpt4free as gpt4free
106160
get_answer = gpt4free.get_response
107161

108162
try:
@@ -164,16 +218,16 @@ async def delete_message(update, context, messageid, delay=10):
164218
# [
165219
# InlineKeyboardButton("gpt-3.5-turbo-0613", callback_data="gpt-3.5-turbo-0613"),
166220
# ],
167-
[
168-
InlineKeyboardButton("gpt-4", callback_data="gpt-4"),
169-
InlineKeyboardButton("gpt-4-32k", callback_data="gpt-4-32k"),
170-
# InlineKeyboardButton("gpt-4-0314", callback_data="gpt-4-0314"),
171-
],
172221
[
173222
InlineKeyboardButton("gpt-4-1106-preview", callback_data="gpt-4-1106-preview"),
174223
# InlineKeyboardButton("gpt-4-32k", callback_data="gpt-4-32k"),
175224
# InlineKeyboardButton("gpt-4-32k-0314", callback_data="gpt-4-32k-0314"),
176225
],
226+
[
227+
InlineKeyboardButton("gpt-4", callback_data="gpt-4"),
228+
InlineKeyboardButton("gpt-4-32k", callback_data="gpt-4-32k"),
229+
# InlineKeyboardButton("gpt-4-0314", callback_data="gpt-4-0314"),
230+
],
177231
# [
178232
# InlineKeyboardButton("gpt-4-0613", callback_data="gpt-4-0613"),
179233
# InlineKeyboardButton("gpt-4-32k-0613", callback_data="gpt-4-32k-0613"),
@@ -373,7 +427,7 @@ async def info(update, context):
373427
messageid = message.message_id
374428
await context.bot.delete_message(chat_id=update.effective_chat.id, message_id=update.message.message_id)
375429

376-
from agent import pdfQA, getmd5, persist_emdedding_pdf
430+
from utils.agent import pdfQA, getmd5, persist_emdedding_pdf
377431
@decorators.Authorization
378432
async def handle_pdf(update, context):
379433
# 获取接收到的文件
@@ -456,14 +510,16 @@ def setup(token):
456510

457511
run_async(application.bot.set_my_commands([
458512
BotCommand('info', 'basic information'),
459-
BotCommand('qa', 'Document Q&A with Embedding Database Search'),
513+
BotCommand('search', 'search Google or duckduckgo'),
460514
BotCommand('en2zh', 'translate to Chinese'),
461515
BotCommand('zh2en', 'translate to English'),
516+
BotCommand('qa', 'Document Q&A with Embedding Database Search'),
462517
BotCommand('start', 'Start the bot'),
463518
BotCommand('reset', 'Reset the bot'),
464519
]))
465520

466521
application.add_handler(CommandHandler("start", start))
522+
application.add_handler(CommandHandler("search", lambda update, context: search(update, context, title=f"`🤖️ {config.GPT_ENGINE}`\n\n", robot=config.ChatGPTbot)))
467523
application.add_handler(CallbackQueryHandler(button_press))
468524
application.add_handler(CommandHandler("reset", reset_chat))
469525
application.add_handler(CommandHandler("en2zh", lambda update, context: command_bot(update, context, "simplified chinese", robot=config.ChatGPTbot)))

chatgpt2api/chatgpt2api.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import config
1414
import threading
1515
import time as record_time
16-
from agent import ThreadWithReturnValue, Web_crawler, pdf_search, getddgsearchurl, getgooglesearchurl, gptsearch, ChainStreamHandler, ChatOpenAI, CallbackManager, PromptTemplate, LLMChain, EducationalLLM
16+
from utils.agent import ThreadWithReturnValue, Web_crawler, pdf_search, getddgsearchurl, getgooglesearchurl, gptsearch, ChainStreamHandler, ChatOpenAI, CallbackManager, PromptTemplate, LLMChain, EducationalLLM
17+
from utils.function_call import function_call_list
1718

1819
def get_filtered_keys_from_object(obj: object, *keys: str) -> Set[str]:
1920
"""
@@ -240,10 +241,8 @@ def ask_stream(
240241
or "https://api.openai.com/v1/chat/completions"
241242
)
242243
headers = {"Authorization": f"Bearer {kwargs.get('api_key', self.api_key)}"}
243-
response = self.session.post(
244-
url,
245-
headers=headers,
246-
json={
244+
245+
json_post = {
247246
"model": os.environ.get("MODEL_NAME") or model or self.engine,
248247
"messages": self.conversation[convo_id] if pass_history else [{"role": "system","content": self.system_prompt},{"role": role, "content": prompt}],
249248
"stream": True,
@@ -264,7 +263,13 @@ def ask_stream(
264263
self.get_max_tokens(convo_id=convo_id),
265264
kwargs.get("max_tokens", self.max_tokens),
266265
),
267-
},
266+
}
267+
if config.SEARCH_USE_GPT:
268+
json_post.update(function_call_list["web_search"])
269+
response = self.session.post(
270+
url,
271+
headers=headers,
272+
json=json_post,
268273
timeout=kwargs.get("timeout", self.timeout),
269274
stream=True,
270275
)
@@ -274,6 +279,7 @@ def ask_stream(
274279
)
275280
response_role: str or None = None
276281
full_response: str = ""
282+
need_function_call = False
277283
for line in response.iter_lines():
278284
if not line:
279285
continue
@@ -290,11 +296,20 @@ def ask_stream(
290296
continue
291297
if "role" in delta:
292298
response_role = delta["role"]
293-
if "content" in delta:
299+
if "content" in delta and delta["content"]:
300+
need_function_call = False
294301
content = delta["content"]
295302
full_response += content
296303
yield content
297-
self.add_to_conversation(full_response, response_role, convo_id=convo_id)
304+
if "function_call" in delta and config.SEARCH_USE_GPT:
305+
need_function_call = True
306+
function_call_content = delta["function_call"]["arguments"]
307+
full_response += function_call_content
308+
if need_function_call:
309+
keywords = json.loads(full_response)["prompt"]
310+
yield from self.search_summary(keywords, convo_id=convo_id, need_function_call=True)
311+
else:
312+
self.add_to_conversation(full_response, response_role, convo_id=convo_id)
298313

299314
async def ask_stream_async(
300315
self,
@@ -426,13 +441,15 @@ def search_summary(
426441
convo_id: str = "default",
427442
model: str = None,
428443
pass_history: bool = True,
444+
need_function_call: bool = False,
429445
**kwargs,
430446
):
431447

432448
if convo_id not in self.conversation:
433449
self.reset(convo_id=convo_id, system_prompt=self.system_prompt)
434-
self.add_to_conversation(prompt, "user", convo_id=convo_id)
435-
self.__truncate_conversation(convo_id=convo_id)
450+
if need_function_call == False:
451+
self.add_to_conversation(prompt, "user", convo_id=convo_id)
452+
self.__truncate_conversation(convo_id=convo_id)
436453

437454
start_time = record_time.time()
438455

main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from bot import setup
44
from urllib import parse
55
from waitress import serve
6-
from runasync import run_async
6+
from utils.runasync import run_async
77
from flask import Flask, request, jsonify
88
from config import BOT_TOKEN, WEB_HOOK, PORT
99

test/test.py

+23-19
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
my_list = [
2-
{"role": "admin", "content": "This is admin content."},
3-
{"role": "user", "content": "This is user content."}
4-
]
1+
# my_list = [
2+
# {"role": "admin", "content": "This is admin content."},
3+
# {"role": "user", "content": "This is user content."}
4+
# ]
5+
a = {"role": "admin"}
6+
b = {"content": "This is user content."}
7+
a.update(b)
8+
print(a)
59

6-
content_list = [item["content"] for item in my_list]
7-
print(content_list)
10+
# content_list = [item["content"] for item in my_list]
11+
# print(content_list)
812

9-
engine = "gpt-3.5-turbo-1106"
10-
truncate_limit = (
11-
30500
12-
if "gpt-4-32k" in engine
13-
else 6500
14-
if "gpt-4" in engine
15-
else 14500
16-
if "gpt-3.5-turbo-16k" in engine or "gpt-3.5-turbo-1106" in engine
17-
else 98500
18-
if ("claude-2-web" or "claude-2") in engine
19-
else 3400
20-
)
13+
# engine = "gpt-3.5-turbo-1106"
14+
# truncate_limit = (
15+
# 30500
16+
# if "gpt-4-32k" in engine
17+
# else 6500
18+
# if "gpt-4" in engine
19+
# else 14500
20+
# if "gpt-3.5-turbo-16k" in engine or "gpt-3.5-turbo-1106" in engine
21+
# else 98500
22+
# if ("claude-2-web" or "claude-2") in engine
23+
# else 3400
24+
# )
2125

22-
print(truncate_limit)
26+
# print(truncate_limit)

test/test_Faucet.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ def gptsearch(result, llm):
66
response = response.content
77
return response
88

9-
chainllm = ChatOpenAI(temperature=0.5, openai_api_base="https://openkey.cloud/v1", model_name="gpt-4", openai_api_key="sk-ucUnnmqI9DdtsAXG8OKxOFxD5dnSrU3E3ZQh4PJa1dgQ7KzE")
9+
chainllm = ChatOpenAI(temperature=0.5, openai_api_base="https://openkey.cloud/v1", model_name="gpt-3.5-turbo", openai_api_key="sk-ucUnnmqI9DdtsAXG8OKxOFxD5dnSrU3E3ZQh4PJa1dgQ7KzE")
10+
# chainllm = ChatOpenAI(temperature=0.5, openai_api_base="https://openkey.cloud/v1", model_name="gpt-4-1106-preview", openai_api_key="sk-ucUnnmqI9DdtsAXG8OKxOFxD5dnSrU3E3ZQh4PJa1dgQ7KzE")
11+
# chainllm = ChatOpenAI(temperature=0.5, openai_api_base="https://openkey.cloud/v1", model_name="gpt-4", openai_api_key="sk-ucUnnmqI9DdtsAXG8OKxOFxD5dnSrU3E3ZQh4PJa1dgQ7KzE")
1012

1113
print(gptsearch("鲁迅和周树人为什么打架", chainllm))

test/test_keyword.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from langchain.chains import LLMChain
55
from langchain.prompts import PromptTemplate
66
from langchain.chat_models import ChatOpenAI
7-
from googlesearch import GoogleSearchAPIWrapper
7+
from utils.googlesearch import GoogleSearchAPIWrapper
88

99

1010
def getgooglesearchurl(result, numresults=3):

agent.py renamed to utils/agent.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from langchain.text_splitter import CharacterTextSplitter
3535
from langchain.tools import DuckDuckGoSearchRun, DuckDuckGoSearchResults, Tool
3636
from langchain.utilities import WikipediaAPIWrapper
37-
from googlesearch import GoogleSearchAPIWrapper
37+
from utils.googlesearch import GoogleSearchAPIWrapper
3838
from langchain.document_loaders import UnstructuredPDFLoader
3939

4040
def getmd5(string):
@@ -44,7 +44,7 @@ def getmd5(string):
4444
md5_hex = md5_hash.hexdigest()
4545
return md5_hex
4646

47-
from sitemap import SitemapLoader
47+
from utils.sitemap import SitemapLoader
4848
async def get_doc_from_sitemap(url):
4949
# https://www.langchain.asia/modules/indexes/document_loaders/examples/sitemap#%E8%BF%87%E6%BB%A4%E7%AB%99%E7%82%B9%E5%9C%B0%E5%9B%BE-url-
5050
sitemap_loader = SitemapLoader(web_path=url)
File renamed without changes.

utils/function_call.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
function_call_list = {
2+
"current_weather": {
3+
"functions": [
4+
{
5+
"name": "get_current_weather",
6+
"description": "Get the current weather in a given location",
7+
"parameters": {
8+
"type": "object",
9+
"properties": {
10+
"location": {
11+
"type": "string",
12+
"description": "The city and state, e.g. San Francisco, CA"
13+
},
14+
"unit": {
15+
"type": "string",
16+
"enum": ["celsius", "fahrenheit"]
17+
}
18+
},
19+
"required": ["location"]
20+
}
21+
}
22+
],
23+
"function_call": "auto"
24+
},
25+
"web_search": {
26+
"functions": [
27+
{
28+
"name": "get_web_search_results",
29+
"description": "Search Google to enhance knowledge.",
30+
"parameters": {
31+
"type": "object",
32+
"properties": {
33+
"prompt": {
34+
"type": "string",
35+
"description": "The prompt to search."
36+
}
37+
},
38+
"required": ["prompt"]
39+
}
40+
}
41+
],
42+
"function_call": "auto"
43+
},
44+
# "web_search": {
45+
# "functions": [
46+
# {
47+
# "name": "get_web_search_results",
48+
# "description": "Get the web page search results in a given keywords",
49+
# "parameters": {
50+
# "type": "object",
51+
# "properties": {
52+
# "keywords": {
53+
# "type": "string",
54+
# "description": "keywords that can yield better search results, keywords are connected with spaces, e.g. 1. The keywords of the sentence (How much does the zeabur software service cost per month?) is (zeabur price). 2. The keywords of the sentence (今天的微博热搜有哪些?) is (微博 热搜)"
55+
# }
56+
# },
57+
# "required": ["keywords"]
58+
# }
59+
# }
60+
# ],
61+
# "function_call": "auto"
62+
# },
63+
}
64+
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)