Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding example that uses a config file #9

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,93 @@ agents and tasks on the command line.

Run with `just example cli-cluster`.

## Example: Leftovers Chef (using template)

From the examples above it may have become clear that all of these examples have a recurring structure and mainly come down to configuration.

In order to make it easier for non-developers or frontend developers to set up new examples with minimal code interaction, you can **use a template structure** to easily define new example instances.

Let's give an overview of how to use the template to create the Leftovers Chef example.

### Copy the template files

To start a new project called "Leftovers Chef", first create new copies of the template files:

```bash
cp _example_template.py leftovers_chef.py
cp _config_template.py leftovers_chef_config.py
```

### Add configuration data

In the main script `leftovers_chef.py` you only need to update the import on line 15 with the name of the config file. That's all you need to do here, the main updates are isolated to the configuration file.

In the configuration file `leftovers_chef_config.py` you'll find a template with some dummy info that will help you configure the user input prompts and the cluster attributes they will populate, the initial propmpt for your example, the cluster name and description, the agents and the tasks. This is very similar to what happens in the [trip planner][trip_planner] and [IG post planner][ig_post_planner] examples.

### Add your example to the setup

When you've configured your example data, all that is left to do is to make sure you can run your example with the provided setup.

Add an import to `main.py`:
```python
from leftovers_chef import run_example as run_leftovers_chef_example
```
and add it to the supported examples map:
```python
EXAMPLES = {
"trip_planner": run_trip_planner_example,
"ig_post_planner": run_ig_post_planner_example,
"cli_cluster": run_cli_cluster_example,
# Add your example
"leftovers_chef": run_leftovers_chef_example,
}
```

And lastly, add a `just` recipe to run your example in `example.just`:
```sh
# Runs an example that prompts the user for a description of their meal to prepare
[no-cd]
leftovers-chef:
@__import__('os').system("just containers check")
@__import__('os').system("docker exec -it examples /bin/bash -c \"source .venv/bin/activate && python examples/main.py leftovers_chef\"")
```

### Test it out!

Now test out your cluster by running:
```sh
just example leftovers_chef
```

### FAQ

> What about the requirements to use the template?

The requirements are the same as mentioned in [Environment Setup](#environment-setup). Nothing additional is required.

> Why are the prompts in the configuration file regular strings instead of template strings?

The strings are dynamically formatted into template strings in example script when the cluster attributes can be resolved.

> What are the pros and cons of using the template?

You should use the template if you don't want to customise any behaviour. For example if you're not technical, you can still use the template to create agents. In that case it's simply updating configuration.

A downside is that you don't have the freedom to change anything. For example, if you wanted to add a frontend that delivers user input and displays the results of the cluster execution, you'll need to change the script you're using. You could create a new template for that!

### What's next?

To improve upon this example and keep learning, you could build:

- Add an agent / task that adds a sustainability score to each meal that is suggested.
- Use a tool to:
- Create a generated image of the meal
- Use a web search to find a list of meals
- Find and integrate a 3rd party tool that estimates sustainability impact.
- Further automation to use templates
- Make a template that uses the Python backend to interact with a Javascript frontend
- many more

## Tools

Agents can use tools to enhance their capabilities. Please refer to the [`tools` README][tools_README]
Expand All @@ -79,3 +166,4 @@ Events allow offchain systems to respond to onchain actions, automating tool exe
[trip_planner]: ./trip_planner.py
[cli_cluster]: ./cli_cluster.py
[design_cluster]: ../onchain/README.md#cluster
[leftovers_chef_config]: ./leftovers_chef_config.py
42 changes: 42 additions & 0 deletions examples/_config_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from colorama import Fore, Style

# User input: name of the arguments and the prompt to ask the user for input
# Example input: "name": "What is your name?",
input_config = {
"arg_name": f"{Fore.YELLOW}Put a prompt here for arg_name... {Style.RESET_ALL}",
"another_arg_name": f"{Fore.YELLOW}Put another prompt here for another_arg_name... {Style.RESET_ALL}",
}

# Initial prompt
# Refer to the the variables you've defined like so, "{variable_name}"".
# String will be formatted in the main program. Leave it as regular string here.
init_prompt = "Put the initial prompt to run the cluster execution here."

# Setting up the cluster
cluster_config = {
"name": "Put Cluster Name here",
"description": "Put Cluster Description here",
}

# Agent(s) Configuration
agent_configs = [
(
"name",
"role",
"goal",
"backstory",
),
]

# Task(s) Configuration
# Refer to the the variables you've defined like so, "{variable_name}"".
# String will be formatted in the main program. Leave it as regular string here.
task_configs = [
(
"Put the Task name here",
"Put the Agent name (performing the task) here",
""""
Put the task description here.
""",
),
]
144 changes: 144 additions & 0 deletions examples/_example_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Use [run_example] to run the example.
# It's a blocking function that takes a client and package ID as arguments
# and then prompts the user for input.

import textwrap
from colorama import Fore, Style
from nexus_sdk import (
create_cluster,
create_agent_for_cluster,
create_task,
execute_cluster,
get_cluster_execution_response,
)

from _config_template import input_config, agent_configs, task_configs, init_prompt, cluster_config

class ClusterName:
def __init__(
self,
client,
package_id,
model_id,
model_owner_cap_id,
# Dynamically set attributes based on provided inputs by the user
**kwargs
):
self.client = client
self.package_id = package_id
self.model_id = model_id
self.model_owner_cap_id = model_owner_cap_id

# Dynamically set attributes based on provided inputs
for key, value in kwargs.items():
setattr(self, key, value)

def setup_cluster(self):
# Create a cluster (equivalent to Crew in CrewAI)
cluster_id, cluster_owner_cap_id = create_cluster(
self.client,
self.package_id,
# Add in the cluster name and description from the config file
cluster_config["name"],
cluster_config["description"],
)
return cluster_id, cluster_owner_cap_id

def setup_agents(self, cluster_id, cluster_owner_cap_id):
# Create agents (assuming we have model_ids and model_owner_cap_ids)
# Agents are imported from the agent_configs list

for agent_name, role, goal in agent_configs:
create_agent_for_cluster(
self.client,
self.package_id,
cluster_id,
cluster_owner_cap_id,
self.model_id,
self.model_owner_cap_id,
agent_name,
role,
goal,
f"An AI agent specialized in {role.lower()}.",
)

def setup_tasks(self, cluster_id, cluster_owner_cap_id):
# Create tasks for the agents
# Tasks are imported from the task_configs list
task_ids = []
for task_name, agent_id, description in task_configs:
task_id = create_task(
self.client,
self.package_id,
cluster_id,
cluster_owner_cap_id,
task_name,
agent_id,
description,
f"Complete {task_name} task",
description,
"", # No specific context provided in this example
)
task_ids.append(task_id)

return task_ids

def run(self):
cluster_id, cluster_owner_cap_id = self.setup_cluster()
self.setup_agents(cluster_id, cluster_owner_cap_id)
self.setup_tasks(cluster_id, cluster_owner_cap_id)

# Execute the cluster, passing the initial prompt from the config file but formatting the string
execution_id = execute_cluster(
self.client,
self.package_id,
cluster_id,
init_prompt.format(**self.__dict__),
)

if execution_id is None:
return "Cluster execution failed"

print(f"Cluster execution started with ID: {execution_id}")
return get_cluster_execution_response(self.client, execution_id, 600)


# Runs the example using the provided Nexus package ID.
def run__example(client, package_id, model_id, mode_owner_cap):
print(f"{Fore.CYAN}## Welcome to Nexus{Style.RESET_ALL}")
print(f"{Fore.YELLOW}-------------------------------{Style.RESET_ALL}")

# Collect inputs in a dictionary
user_inputs = {key: input(prompt) for key, prompt in input_config.items()}

cluster = ClusterName(
client,
package_id,
model_id,
mode_owner_cap,
**user_inputs
)

print()
result = cluster.run()

print(f"\n\n{Fore.CYAN}########################{Style.RESET_ALL}")
print(f"{Fore.CYAN}## Here is your Solution{Style.RESET_ALL}")
print(f"{Fore.CYAN}########################\n{Style.RESET_ALL}")

paginate_output(result)


# Helper function to paginate the result output
def paginate_output(text, width=80):
lines = text.split("\n")

for i, line in enumerate(lines, 1):
wrapped_line = textwrap.fill(line, width)
print(wrapped_line)

# It's nice when this equals the number of lines in the terminal, using
# default value 32 for now.
pause_every_n_lines = 32
if i % pause_every_n_lines == 0:
input(f"{Fore.YELLOW}-- Press Enter to continue --{Style.RESET_ALL}")
6 changes: 6 additions & 0 deletions examples/example.just
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ trip-planner:
cli-cluster:
@__import__('os').system("just containers check")
@__import__('os').system("docker exec -it examples /bin/bash -c \"source .venv/bin/activate && python examples/main.py cli_cluster\"")

# Runs an example that prompts the user for a description of their meal to prepare
[no-cd]
leftovers-chef:
@__import__('os').system("just containers check")
@__import__('os').system("docker exec -it examples /bin/bash -c \"source .venv/bin/activate && python examples/main.py leftovers_chef\"")
Loading