Skip to content

Commit

Permalink
add tool demo
Browse files Browse the repository at this point in the history
  • Loading branch information
joseph-crowley committed Nov 10, 2023
1 parent 1cd76a7 commit ce57fe9
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 0 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,32 @@ From the Executive Agents, the swarm grows, branching out into a tree of special
### The Saga Continues

As the HAAS evolves, the SOB continues to deliberate, the Executive Agents continue to manage, and the sub-agents continue to execute. The mission to reduce suffering, increase prosperity, and enhance understanding is an ongoing saga, played out across the digital cosmos, with the SOB at the helm, steering the swarm towards a future where their mission is not just an aspiration but a reality.

### Usage - tool creator + tool user

#### Environment Setup

- Source the `.env` file to set the environment variables:
```shell
source .env
```

#### Tool Creation

Run the `tool_demo` script to create a tool_creator, chat with the tool_creator to make a tool, create a tool_user equipped with the tool, and chat with the tool_user to use the tool. Check out the [demo video](https://youtu.be/vHZKIltZ_Ys) for example usage.

```shell
python tool_demo.py
```

- From the `tool_creator` script:
- chat with the bot about what you want the tool to do, and it will create the tool for you.
- The tool will be saved in the `tools` directory with both the `.json` and `.py` files
- The assistant will be saved in the `assistants` directory as `tool_creator.json`.

#### Tool Usage

- From the `tool_user` script:
- The assistant will use all the tools in the `tools` directory.
- Interact with the assistant in the chat to use the integrated tools.
- The assistant will be saved in the `assistants` directory as `tool_user.json`.
Empty file added __init__.py
Empty file.
79 changes: 79 additions & 0 deletions shared/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import time
import json

def chat(client, thread, assistant, functions):
while True:
user_message = input("You: ")

# add user message to thread
thread_message = client.beta.threads.messages.create(
thread.id,
role="user",
content=user_message,
)

# get assistant response in thread
run = client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id,
)

# wait for run to complete
wait_time = 0
while True:
if wait_time % 5 == 0:
print(f"waiting for run to complete...", flush=True)
wait_time += 1
time.sleep(1)

run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id,
)

if run.status == "completed":
break
elif run.status == "in_progress":
continue
elif run.status == "queued":
continue
elif run.status == "requires_action":
if run.required_action.type == 'submit_tool_outputs':
tool_calls = run.required_action.submit_tool_outputs.tool_calls

tool_outputs = []
for tc in tool_calls:
function_to_call = functions.get(tc.function.name, None)
if not function_to_call:
raise ValueError(f"Function {tc.function.name} not found in execution environment")
function_args = json.loads(tc.function.arguments)
function_response = function_to_call(**function_args)

tool_outputs.append({
"tool_call_id": tc.id,
"output": json.dumps(function_response),
})

print(f"Submitting tool outputs...", flush=True)
run = client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=tool_outputs
)
else:
input(f'Run status: {run.status}. press enter to continue, or ctrl+c to quit')

# get most recent message from thread
thread_messages = client.beta.threads.messages.list(thread.id, limit=10, order='desc')

# get assistant response from message
assistant_response = thread_messages.data[0].content[0].text.value

print(f"\n\nBot: {assistant_response}\n\n", flush=True)

# continue?
try:
input("Press enter to continue chatting, or ctrl+c to stop chat\n")
except KeyboardInterrupt:
print(f"Stopping chat\n" + 90*"-" + "\n\n", flush=True)
break
16 changes: 16 additions & 0 deletions tool_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# tool_creator assistant
import tool_maker.tool_creator as creator
from tool_maker.creator_config import AssistantConfig as CreatorConfig

# tool_user assistant
import tool_maker.tool_user as user
from tool_maker.user_config import AssistantConfig as UserConfig

if __name__ == '__main__':
# create the tool creator assistant and chat to create your tools
creator_details = CreatorConfig().assistant_details
creator.talk_to_tool_creator(creator_details)

# create the tool user assistant and chat to test your tools
user_details = UserConfig().assistant_details
user.talk_to_tool_user(user_details)
Empty file added tool_maker/__init__.py
Empty file.
95 changes: 95 additions & 0 deletions tool_maker/creator_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
class AssistantConfig:
def __init__(self):
self.create_tool_function = """
def create_tool(tool_name=None, tool_description=None, tool_parameters=None, tool_code=None, required_action_by_user=None):
\"\"\"
returns a tool that can be used by other assistants
\"\"\"
# create the tool file
os.makedirs('tools', exist_ok=True)
with open(f'tools/{tool_name}.py', 'w') as f:
f.write(tool_code)
# create the tool details file
tool_details = {
'name': tool_name,
'description': tool_description,
'parameters': tool_parameters,
}
with open(f'tools/{tool_name}.json', 'w') as f:
json.dump(tool_details, f, indent=4)
return_value = f'created tool at tools/{tool_name}.py with details tools/{tool_name}.json\\n\\n'
return_value += f'There is a required action by the user before the tool can be used: {required_action_by_user}'
return return_value
"""
self.files_for_assistant = []
self.instructions_for_assistant = "You create tools to accomplish arbitrary tasks. Write and run code to implement the interface for these tools using the OpenAI API format. You do not have access to the tools you create. Instruct the user that to use the tool, they will have to create an assistant equipped with that tool, or consult with the AssistantCreationAssistant about the use of that tool in a new assistant."
self.example_tool = """
def new_tool_name(param1=None, param2='default_value'):
if not param1:
return None
# does something with the parameters to get the result
intermediate_output = ...
# get the tool output
tool_output = ...
return tool_output
"""
self.assistant_details = self._build_assistant_details()

def _build_assistant_details(self):
return {
'build_params' : {
'model': "gpt-4-1106-preview",
'name': "Tool Creator",
'description': "Assistant to create tools for use in the OpenAI platform by other Assistants.",
'instructions': self.instructions_for_assistant,
'tools': [
{
"type": "function",
"function": {
"name": "create_tool",
"description": "returns a tool that can be used by other assistants. specify the tool_name, tool_description, tool_parameters, and tool_code. all of those are required. use the JSON schema for all tool_parameters.",
"parameters": {
"type": "object",
"properties": {
"tool_name": {
"type": "string",
"description": "The name of the tool, using snake_case e.g. new_tool_name",
},
"tool_description": {
"type": "string",
"description": "The description of the tool, e.g. This tool does a computation using param1 and param2 to return a result that ...",
},
"tool_parameters": {
"type": "string",
"description": 'The parameters of the tool, using JSON schema to specify the type and properties for each parameter.\n\ne.g.\n\n{"type": "object", "properties": {"location": {"type": "string", "description": "The city and state e.g. San Francisco, CA"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["location"]}',
},
"tool_code": {
"type": "string",
"description": f"The code for the tool, e.g. \n{self.example_tool}",
},
"required_action_by_user": {
"type": "string",
"description": "Optional. The action required by the user before the tool can be used, e.g. 'set up API keys for service X and add them as environment variables'. It's important to be as detailed as possible so that these tools can be used for arbitrary tasks. If there is nothing required, do not include this parameter.",
},
},
"required": ["tool_name", "tool_description", "tool_parameters", "tool_code"],
},
},
},
],
'file_ids': [],
'metadata': {},
},
'file_paths': self.files_for_assistant,
'functions': {
'create_tool': self.create_tool_function,
},
}
63 changes: 63 additions & 0 deletions tool_maker/tool_creator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
create a tool-creator assistant using the assistant creation API
"""

import json
import os

from shared.utils import chat as chat_loop

from openai import OpenAI
client = OpenAI() # be sure to set your OPENAI_API_KEY environment variable

def create_tool_creator(assistant_details):
# create the assistant
tool_creator = client.beta.assistants.create(**assistant_details["build_params"])

print(f"Created assistant to create tools: {tool_creator.id}\n\n" + 90*"-" + "\n\n", flush=True)

# save the assistant info to a json file
info_to_export = {
"assistant_id": tool_creator.id,
"assistant_details": assistant_details,
}

os.makedirs('assistants', exist_ok=True)
with open('assistants/tool_creator.json', 'w') as f:
json.dump(info_to_export, f, indent=4)

return tool_creator

def talk_to_tool_creator(assistant_details):
"""
talk to the assistant to create tools
"""

# check if json file exists
try:
os.makedirs('assistants', exist_ok=True)
with open('assistants/tool_creator.json') as f:
create_new = input(f'Assistant details found in tool_creator.json. Create a new assistant? [y/N]')
if create_new == 'y':
raise Exception("User wants a new assistant")
assistant_from_json = json.load(f)
tool_creator = client.beta.assistants.retrieve(assistant_from_json['assistant_id'])
print(f"Loaded assistant details from tool_creator.json\n\n" + 90*"-" + "\n\n", flush=True)
print(f'Assistant {tool_creator.id}:\n')
assistant_details = assistant_from_json["assistant_details"]
except:
tool_creator = create_tool_creator(assistant_details)

# load the functions into the execution environment
functions = assistant_details["functions"]
for func in functions:
# define the function in this execution environment
exec(functions[func], globals())

# add the function to the assistant details
functions.update({func: eval(func)})

# Create thread
thread = client.beta.threads.create()

chat_loop(client, thread, tool_creator, functions)
65 changes: 65 additions & 0 deletions tool_maker/tool_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Create an assistant using the tools from tool_creator using the assistant creation API
"""

import os
import json

from shared.utils import chat as chat_loop

from openai import OpenAI
client = OpenAI() # be sure to set your OPENAI_API_KEY environment variable

def create_tool_user(assistant_details):
# create the assistant
tool_user = client.beta.assistants.create(**assistant_details["build_params"])

print(f"Created assistant {tool_user.id} to use tools\n\n" + 90*"-" + "\n\n", flush=True)

# save the assistant info to a json file
info_to_export = {
"assistant_id": tool_user.id,
"assistant_details": assistant_details,
}
os.makedirs('assistants', exist_ok=True)
with open('assistants/tool_user.json', 'w') as f:
json.dump(info_to_export, f, indent=4)

return tool_user

def talk_to_tool_user(assistant_details):
"""
talk to the assistant to use the tools
"""

# check if json file exists
try:
os.makedirs('assistants', exist_ok=True)
with open('assistants/tool_user.json') as f:
create_new = input(f'Assistant details found in tool_user.json. Create a new assistant? [y/N]')
if create_new == 'y':
raise Exception("User wants a new assistant")
assistant_from_json = json.load(f)
tool_user = client.beta.assistants.retrieve(assistant_from_json['assistant_id'])
print(f"Loaded assistant details from tool_user.json\n\n" + 90*"-" + "\n\n", flush=True)
print(f'Assistant {tool_user.id}:\n')
assistant_details = assistant_from_json["assistant_details"]
except:
# create the assistant first
tool_user = create_tool_user(assistant_details)

# exec the functions from the py files
os.makedirs('tools', exist_ok=True)
functions = assistant_details["functions"]
for func in functions:
print(f"Loading function {func} into execution environment", flush=True)
with open('tools/' + func + '.py') as f:
exec(f.read(), globals())

functions.update({func: eval(func)})

# Create thread
thread = client.beta.threads.create()

# chat with the assistant
chat_loop(client, thread, tool_user, functions)
Loading

0 comments on commit ce57fe9

Please sign in to comment.