Complexity: 🟨 Intermediate
And example tool in the NeMo Agent Toolkit that makes use of an Object Store to retrieve data.
- Key Features
- Function Groups Overview
- Installation and Setup
- NeMo Agent Toolkit File Server
- Run the Workflow
- Function Group Implementation: Demonstrates the new function groups feature in NeMo Agent Toolkit for sharing configurations and resources across multiple functions.
- Shared Configuration: All user report functions share the same object store reference and configuration settings.
- Resource Sharing: Functions within the group share the same object store client connection, reducing resource overhead.
- Object Store Integration: Demonstrates comprehensive integration with object storage systems including AWS S3 and MinIO for storing and retrieving user report data.
- Multi-Database Support: Shows support for object stores (S3-compatible), relational databases (MySQL), and key-value stores (Redis) for flexible data storage architectures.
- File Server Backend: Provides a complete file server implementation with object store backing, supporting REST API operations for upload, download, update, and delete.
- Real-Time Report Management: Enables dynamic creation, retrieval, and management of user reports through natural language interfaces with automatic timestamp handling.
- Mock Data Pipeline: Includes complete setup scripts and mock data for testing object store workflows without requiring production data sources.
This example demonstrates using function groups in NeMo Agent Toolkit. Function groups allow you to:
- Share configurations across multiple related functions
- Share resources such as database connections or API clients
- Reduce duplication in both Python code and YAML configurations
- Maintain compatibility with existing function interfaces
The user report function group (user_report) contains four functions that all share the same configuration.
It also takes advantage of:
- Shared Configuration: All functions use the same
object_storereference and function descriptions - Shared Resources: All functions share the same object store client connection
Refer to Function Groups for more information on the benefits of Function Groups compared to Functions, including code and configuration comparisons when using Function Groups.
function_groups:
user_report:
_type: user_report
include: [get, put, update, delete]
object_store: report_object_store
get_description: "Description for get function..."
put_description: "Description for put function..."
update_description: "Description for update function..."
delete_description: "Description for delete function..."In the workflow configuration, you can reference individual functions or the entire group:
workflow:
_type: react_agent
# Reference individual functions
tool_names: [user_report__get, user_report__put, user_report__update, user_report__delete]workflow:
_type: react_agent
# Reference entire group
tool_names: [user_report]If you have not already done so, follow the instructions in the Install Guide to create the development environment and install NeMo Agent Toolkit, and follow the Obtaining API Keys instructions to obtain an NVIDIA API key.
From the root directory of the NeMo Agent Toolkit repository, run the following commands:
uv pip install -e examples/object_store/user_reportIf you have not already done so, follow the Obtaining API Keys instructions to obtain an NVIDIA API key. You need to set your NVIDIA API key as an environment variable to access NVIDIA AI services:
export NVIDIA_API_KEY=<YOUR_API_KEY>You must choose an object store to use for this example. The in-memory object store is useful for transient use cases, but is not particularly useful for this example due to the lack of persistence.
If you want to run this example in a local setup without creating a bucket in AWS, you can set up MinIO in your local machine. MinIO is an object storage system and acts as drop-in replacement for AWS S3.
You can use the docker-compose.minio.yml file to start a MinIO server in a local docker container.
docker compose -f examples/deploy/docker-compose.minio.yml up -dNote
This is not a secure configuration and should not be used in production systems.
If you want to use a MySQL server, you can use the docker-compose.mysql.yml file to start a MySQL server in a local docker container.
You should first specify the MYSQL_ROOT_PASSWORD environment variable.
export MYSQL_ROOT_PASSWORD=<password>Then start the MySQL server.
docker compose -f examples/deploy/docker-compose.mysql.yml up -dNote
This is not a secure configuration and should not be used in production systems.
If you want to use a Redis server, you can use the docker-compose.redis.yml file to start a Redis server in a local docker container.
docker compose -f examples/deploy/docker-compose.redis.yml up -dNote
This is not a secure configuration and should not be used in production systems.
This example uses mock data to demonstrate the functionality of the object store. Mock data can be loaded to the object store by running the following commands based on the object store selected.
# Load mock data to MinIO
nat object-store \
s3 --endpoint-url http://127.0.0.1:9000 --access-key minioadmin --secret-key minioadmin my-bucket \
upload ./examples/object_store/user_report/data/object_store/
# Load mock data to MySQL
nat object-store \
mysql --host 127.0.0.1 --username root --password ${MYSQL_ROOT_PASSWORD} --port 3306 my-bucket \
upload ./examples/object_store/user_report/data/object_store/
# Load mock data to Redis
nat object-store \
redis --host 127.0.0.1 --port 6379 --db 0 my-bucket \
upload ./examples/object_store/user_report/data/object_store/There are additional command-line arguments that can be used to specify authentication credentials for some object stores.
By adding the object_store field in the general.front_end block of the configuration, clients directly download and
upload files to the connected object store. An example configuration looks like:
general:
front_end:
object_store: my_object_store
...
object_stores:
my_object_store:
...You can start the file server by running the following command with the appropriate configuration file:
nat serve --config_file examples/object_store/user_report/configs/config_s3.ymlThe above command will use the S3-compatible object store. Other configuration files are available in the configs directory for the different object stores.
Note
The only way to populate the in-memory object store is through nat serve followed by the appropriate PUT or POST request. All subsequent interactions must be done through the REST API rather than through nat run.
- Download an object:
curl -X GET http://<hostname>:<port>/static/{file_path} -o {filename} - Upload an object:
curl -X POST http://<hostname>:<port>/static/{file_path} --data-binary @{filename} - Upsert an object:
curl -X PUT http://<hostname>:<port>/static/{file_path} --data-binary @{filename} - Delete an object:
curl -X DELETE http://<hostname>:<port>/static/{file_path}
If any of the loading scripts were run and the files are in the object store, example commands are:
- Get an object:
curl -X GET http://localhost:8000/static/reports/67890/latest.json - Delete an object:
curl -X DELETE http://localhost:8000/static/reports/67890/latest.json
For each of the following examples, a command is provided to run the workflow with the specified input. Run the following command from the root of the NeMo Agent Toolkit repo to execute the workflow.
You have three options for running the workflow:
- Using the S3-compatible object store (
config_s3.yml) - Using the MySQL object store (
config_mysql.yml) - Using the Redis object store (
config_redis.yml)
The configuration file used in the examples below is config_s3.yml which uses an S3-compatible object store.
You can change the configuration file by changing the --config_file argument to config_mysql.yml for the MySQL server
or config_redis.yml for the Redis server.
nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input "Give me the latest report of user 67890"
Expected Workflow Output
<snipped for brevity>
[AGENT]
Calling tools: user_report__get
Tool's input: {"user_id": "67890", "date": null}
<snipped for brevity>
Workflow Result:
['The latest report of user 67890 is:\n\n{\n "user_id": "35791",\n "timestamp": "2025-05-02T14:27:45Z",\n "system": {\n "os": "Windows 11",\n "cpu_usage": "73%",\n "memory_usage": "9.2 GB / 16 GB",\n "disk_space": "400 GB free of 500 GB"\n },\n "network": {\n "latency_ms": 67,\n "packet_loss": "0.0%",\n "vpn_connected": false\n },\n "errors": [],\n "recommendations": [\n "Regular system check completed",\n "All services running optimally"\n ]\n}']In the case of a non-existent report, the workflow will return an error message.
nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input "Give me the latest report of user 12345"
Expected Workflow Output
<snipped for brevity>
Workflow Result:
['The report for user 12345 is not available.']nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input 'Create a latest report for user 6789 with the following JSON contents:
{
"recommendations": [
"Update graphics driver",
"Check for overheating hardware",
"Enable automatic crash reporting"
]
}
'Expected Workflow Output
<snipped for brevity>
[AGENT]
Calling tools: user_report__put
Tool's input: {"report": "{\n \"recommendations\": [\n \"Update graphics driver\",\n \"Check for overheating hardware\",\n \"Enable automatic crash reporting\"\n ]\n}", "user_id": "6789", "date": null}
Tool's response:
User report for 6789 with date latest added successfully
<snipped for brevity>
Workflow Result:
['The latest report for user 6789 has been created with the provided JSON contents.']If you attempt to put a report for a user and date that already exists, the workflow will return an error message. Rerunning the workflow should produce the following output:
Expected Workflow Output
<snipped for brevity>
[AGENT]
Calling tools: user_report__put
Tool's input: {"report": "{\"recommendations\": [\"Update graphics driver\", \"Check for overheating hardware\", \"Enable automatic crash reporting\"]}", "user_id": "6789", "date": null}
Tool's response:
User report for 6789 with date latest already exists
<snipped for brevity>
Workflow Result:
['The report for user 6789 with date "latest" already exists and cannot be replaced.']nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input 'Update the latest report for user 6789 with the following JSON contents:
{
"recommendations": [
"Update graphics driver",
"Check for overheating hardware",
"Reboot the system"
]
}
'Expected Workflow Output
<snipped for brevity>
[AGENT]
Calling tools: user_report__update
Tool's input: {"report": "{\"recommendations\": [\"Update graphics driver\", \"Check for overheating hardware\", \"Reboot the system\"]}", "user_id": "6789", "date": null}
Tool's response:
User report for 6789 with date latest updated
<snipped for brevity>
Workflow Result:
['The latest report for user 6789 has been updated with the provided JSON contents.']nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input 'Delete the latest report for user 6789'Expected Workflow Output
<snipped for brevity>
[AGENT]
Calling tools: user_report__delete
Tool's input: {"user_id": "6789", "date": null}
Tool's response:
User report for 6789 with date latest deleted
<snipped for brevity>
Workflow Result:
['The latest report for user 6789 has been successfully deleted.']If you attempt to delete a report that does not exist, the workflow will return an error message. Rerunning the workflow should produce the following output:
Expected Workflow Output
<snipped for brevity>
[AGENT]
Calling tools: user_report__delete
Tool's input: {"user_id": "6789", "date": null}
Tool's response:
Tool call failed after all retry attempts. Last error: No object found with key: /reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
<snipped for brevity>
Workflow Result:
['The report for user 6789 does not exist, so it cannot be deleted.']