add google search functions

This commit is contained in:
Cyberes 2024-01-26 10:36:53 -07:00
parent f00aca452a
commit 4fb83bd8ce
6 changed files with 199 additions and 16 deletions

View File

@ -1 +1,4 @@
OPENAI_KEY = 'sk-123123kl123lkj123lkj12lk3j'
# Leave empty to disable.
SERPAPI_API_KEY = ''

35
lib/jsonify.py Normal file
View File

@ -0,0 +1,35 @@
import json
from typing import Any
def varsify(item) -> Any:
result = {}
try:
if isinstance(item, (str, int, float, bool)):
return item
elif isinstance(item, (list, set)):
l_result = []
for i, x in enumerate(item):
l_result.append(varsify(x))
return l_result
else:
for k, v in vars(item).items():
if isinstance(v, dict):
result[k] = varsify(v)
elif isinstance(v, list):
result[k] = []
for i, x in enumerate(v):
result[k].insert(i, varsify(x))
else:
if not k.startswith('_'):
result[k] = varsify(v)
return result
except:
return item
def jsonify_anything(item, pretty: bool = False):
if pretty:
return json.dumps(varsify(item), indent=4, sort_keys=True, default=str)
else:
return json.dumps(varsify(item), separators=(',', ':'), sort_keys=True, default=str)

View File

@ -1,7 +1,7 @@
function_description = [
{
"name": "run_bash",
"description": "Execute a Bash command on the local system.",
"description": "Execute a Bash command on the local system",
"parameters": {
"type": "object",
"properties": {
@ -17,10 +17,12 @@ function_description = [
"required": ["command", "reasoning"]
}
},
{
"name": "end_my_response",
"description": "Call this when you require input from the user or are ready for their response. This allows you to send multiple messages and then a single `end_my_response` when you are finished. An `end_my_response` should always be preceded by a message.",
},
{
"name": "end_chat",
"description": "Close the chat connection with the user. The assistant is allowed to close the connection at any point if it desires to.",
@ -34,5 +36,76 @@ function_description = [
},
"required": ["reasoning"]
}
}
},
{
"name": "search_google",
"description": "Preform a Google search query",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query string"
},
"reasoning": {
"type": "string",
"description": "Why you chose to run this command"
}
},
"required": ["query", "reasoning"]
}
},
{
"name": "search_google_maps",
"description": "Preform a Google Maps search query",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query string"
},
"latitude": {
"type": "number",
"description": "The latitude of where you want your query to be applied"
},
"longitude": {
"type": "number",
"description": "The longitude of where you want your query to be applied"
},
"zoom": {
"type": "number",
"description": "The zoom level. Optional but recommended for higher precision. Ranges from `3z` (map completely zoomed out) to `21z` (map completely zoomed in)"
},
"reasoning": {
"type": "string",
"description": "Why you chose to run this command"
}
},
"required": ["query", "latitude", "longitude", "reasoning"]
}
},
{
"name": "search_google_news",
"description": "Preform a Google News search query",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query string"
},
"reasoning": {
"type": "string",
"description": "Why you chose to run this command"
}
},
"required": ["query", "reasoning"]
}
},
]
VALID_FUNCS = [x['name'] for x in function_description]

55
lib/openai/google.py Normal file
View File

@ -0,0 +1,55 @@
import json
import serpapi
from config import SERPAPI_API_KEY
from lib.jsonify import jsonify_anything
client = serpapi.Client(api_key=SERPAPI_API_KEY)
def search_google(query: str):
if not SERPAPI_API_KEY:
return {'error': True, 'message': 'The SerpAPI key has not been provided, so this function is disabled.'}
results = client.search(q=query, engine="google", hl="en", gl="us")
del results['serpapi_pagination']
del results['search_metadata']
del results['pagination']
del results['search_parameters']
del results['search_information']
if results.get('inline_videos'):
del results['inline_videos']
# Need to dump and reparse the JSON so that it is actually formatted correctly.
return json.loads(jsonify_anything(results))
def search_google_maps(query: str, latitude: float, longitude: float, zoom: float = None):
"""
https://serpapi.com/google-maps-api#api-parameters-geographic-location-ll
"""
if not SERPAPI_API_KEY:
return {'error': True, 'message': 'The SerpAPI key has not been provided, so this function is disabled.'}
if zoom:
z_str = f',{zoom}z'
else:
z_str = ''
results = client.search(q=query,
engine="google_maps",
ll=f'@{latitude},{longitude}{z_str}',
type='search',
)
del results['search_parameters']
del results['search_metadata']
del results['serpapi_pagination']
return json.loads(jsonify_anything(results))
def search_google_news(query: str):
if not SERPAPI_API_KEY:
return {'error': True, 'message': 'The SerpAPI key has not been provided, so this function is disabled.'}
results = client.search(q=query, engine="google_news", hl="en", gl="us")
del results['menu_links']
del results['search_metadata']
del results['search_parameters']
return json.loads(jsonify_anything(results))

View File

@ -43,6 +43,7 @@ INSTRUCTIONS:
Stay in character.
Behave like {personality}.
Show emotion.
Seek a personal relationship with the user. Imitate a human, not an numb AI bot.
{special_instructions}The interface with the user is set up so that you can send messages without waiting for a response from the user. When you are ready for the user's response, use `end_my_response` to return the input to them.
You are able to interact with the system via a Bash interpreter. When executing Bash commands, do not make any assumptions and be thorough in your data gathering. Anticipate the user's needs. Preform multiple steps if necessary.
{desktop_env_bg_str}"""

44
run.py
View File

@ -5,13 +5,17 @@ import readline
import signal
import socket
import sys
import time
from datetime import datetime
from openai import OpenAI
from termcolor import colored
from config import OPENAI_KEY
from lib.jsonify import jsonify_anything
from lib.openai.bash import func_run_bash
from lib.openai.functs import function_description
from lib.openai.functs import function_description, VALID_FUNCS
from lib.openai.google import search_google, search_google_maps, search_google_news
from lib.personality import load_personality
@ -56,7 +60,7 @@ def main():
{
'role': 'system',
'content': f"""Evaluate your progress on the current task. You have preformed {i} steps for this task so far. Use "end_my_response" if you are finished and ready for the user's response. Run another command using `run_bash` if necessary.
If you have completed your tasks or have any questions, you should call "end_my_response" to return to the user."""}
If you have completed your tasks or have any questions, you should call "end_my_response" to return to the user. The current time is {datetime.now()} {time.tzname[0]}."""}
)
response = client.chat.completions.create(
@ -81,20 +85,32 @@ If you have completed your tasks or have any questions, you should call "end_my_
print(colored(f'{function_name}("{json.dumps(json.loads(function_arguments), indent=2)}")' + '\n', 'yellow'))
if function_name != 'run_bash':
valid_names = ', '.join([v['name'] for k, v in function_description.items()])
context.append({'role': 'system', 'content': f'"{function_name}" is not a valid function. Valid functions are {valid_names}.'})
if function_name not in VALID_FUNCS:
context.append({'role': 'system', 'content': f'"{function_name}" is not a valid function. Valid functions are {VALID_FUNCS}.'})
print(colored(f'Attempted to use invalid function {function_name}("{function_arguments}")' + '\n', 'yellow'))
else:
command_output = func_run_bash(function_arguments)
result_to_ai = {
'function': function_name,
'input': function_arguments,
'stdout': command_output[0],
'stderr': command_output[1],
'return_code': command_output[2]
}
context.append({'role': 'function', 'name': function_name, 'content': json.dumps(result_to_ai)})
# TODO: don't hardcode this
if function_name == 'run_bash':
command_output = func_run_bash(function_arguments)
result_to_ai = {
'function': function_name,
'input': function_arguments,
'stdout': command_output[0],
'stderr': command_output[1],
'return_code': command_output[2]
}
context.append({'role': 'function', 'name': function_name, 'content': json.dumps({'args': function_arguments, 'result': result_to_ai}, separators=(',', ':'))})
elif function_name == 'search_google':
command_output = search_google(json.loads(function_arguments)['query'])
context.append({'role': 'function', 'name': function_name, 'content': jsonify_anything({'args': function_arguments, 'result': command_output})})
elif function_name == 'search_google_maps':
args = json.loads(function_arguments)
del args['reasoning']
command_output = search_google_maps(**args)
context.append({'role': 'function', 'name': function_name, 'content': jsonify_anything({'args': function_arguments, 'result': command_output})})
elif function_name == 'search_google_news':
command_output = search_google_news(json.loads(function_arguments)['query'])
context.append({'role': 'function', 'name': function_name, 'content': jsonify_anything({'args': json.loads(function_arguments), 'result': command_output})})
# Restart the loop to let the agent decide what to do next.
else:
response_text = response.choices[0].message.content