Skip to main content

TinyAgents

drawing

LICENSE BUILD VERSION

A tiny, lightweight and unintrusive library for orchestrating agentic applications.

Here's the big idea:

  1. 😶‍🌫️ Less than 1000 lines of code
  2. 😨 Lightweight - "Ray Is All You Need"
  3. 🚀 No need to change your code, just decorate!

Recent updates:

  1. (18/10/24) As of version 1.2, TinyAgents supports tracing 🔍 using OpenTelemetry. As traces follow the OpenInference semantic conventions, traces can be visualised using Arize Phoenix. See Tracing for more information.
  2. (17/07/24) As of version 1.1, you can now run and deploy TinyAgents graphs using Ray Serve 🎉 see this notebook for an example. More information can also be found here Run and deploy using Ray Serve.

Contents

  1. Installation
  2. How it works!

Installation

pip install tinyagents

How it works!

Define your graph using standard operators

Parallelisation

Use the & operator to create a Parallel node.

Note: when using Ray you can configure resource allocation by passing ray_options when compiling your graph (more information provided here). When you are not using Ray, you can set the maximum number of workers used by the ThreadPoolExecutor by using the node.set_max_workers() method.

from tinyagents import chainable

@chainable
def tool1(inputs: dict):
    return ...

@chaianble
def tool2(inputs: dict):
    return ...

@chainable
class Agent:
    def __init__(self):
        ...

    def run(self, inputs: list):
        return ...

# run `tool1` and `tool2` in parallel, then pass outputs to `Agent`
graph = (tool1 & tool2) | Agent()
runner = graph.compile()

runner.invoke("Hello!")

Branching

Use / operator to create a ConditionalBranch node.

Note: If a (callable) router is bound to the node, it will be used to determine which of the subnodes should be executed (by returning the name of the selected node). If no router is provided, the input to the ConditionalBranch node must be the name of the node to execute.

jailbreak_check = ...
agent1 = ...
agent2 = ...
guardrail = ...

def my_router(inputs: str):
    if inputs == "jailbreak_attempt":
        return agent2.name

    return agent1.name

# check for a jailbreak attempt, then run either `agent1` or `agent2`, then finally run `guardrail`
graph = jailbreak_check | (agent1 / agent2).bind_router(my_router) | guardrail

print(graph)
## jailbreak_check -> ConditionalBranch(agent1, agent2) -> guardrail

Looping

Use the loop function to define a Recursive node.

Note: loops can be exited early by overriding the output_handler method for a node and using the end_loop function instead of the default passthrough.

from tinyagents import chainable, loop, end_loop, passthrough
from typing import TypedDict

class State(TypedDict):
    approved: bool
    findings: List[str]

@chainable
class Supervisor:
    def __init__(self):
        ...

    def run(self, state: dict) ->:
        # get findings from the state and determine if the research is sufficient
        if len(state.findings) > 3:
            state.approved = True

        return state

    def output_handler(self, state: dict):
        # override the default output handler which returns `passthrough(..)` 
        # to move to the next node in the graph.
        if state.approved == True:
            return end_loop(...final output...)
        
        return passthrough(state)

@chainable
class Researcher:
    def __init__(self):
        ...

    def __run__(self, state: State) -> State:
        # do research and add findings to the state
        state.findings.append("...new finding...")
        return state

graph = loop(Researcher(), Supervisor(), max_iter=8).as_graph()

print(graph)
## Recursive(researcher, supervisor)

Subgraphs

You can use Graph objects as if they were nodes, which creates SubGraph nodes.

from tinyagents import chainable, loop
from pydantic import BaseModel

class State(BaseModel):
    messages: List[Dict[str, str]]

@chainable
def tool1(inputs: str):
    return ...

@chainable
def tool2(inputs: str):
    return ...

@chainable
def tool3(inputs: str):
    return ...

@chainable
def tool4(inputs: str):
    return ...

@chainable
class Agent:
    def __init__(self):
        ...

    def run(self, inputs: State):
        # use tools and generate a response
        return ...

# parallelise all toolkits
toolkit1 = tool1 & tool2
toolkit2 = tool3 & tool4
agent = Agent()

# define Graph 1
graph1 = loop(toolkit1, agent, max_iter=2).as_graph()
# define Graph 2
graph2 = toolkit2 | agent

graph1_runner = graph1.compile()
graph2_runner = graph2.compile()

state = State(messages=[{"role": "user", "content": "Hello!"}])

# invoke Graph 1 
result1 = graph1.invoke(state)
# invoke Graph 2
result2 = graph2.invoke(state)

# invoke a combined and parallelised graph
combined_graph_runner = (graph1 & graph2).compile()
combined_result = combined_graph_runner.invoke(state)
print(combined_result)
# [result1, result2]

Serve your application using Ray Serve

See Using Ray for more information.

from ray import serve

@chainable(
    node_name="my_agent",
    kind="agent",
    ray_options={
        "num_replicas": 1,
        "max_ongoing_requests": 50
    }
)
class MyAgent:
    def __init__(self):
        ...

    def run(self, inputs: str):
        ...

@chainable(
    node_name="my_tool",
    kind="tool",
    ray_options={
        "num_replicas": 3,
        "max_ongoing_requests": 100
    }
)
class MyTool:
    def __init__(self):
        ...

    def run(self, inputs: str):
        ...

graph = loop(MyTool(), MyAgent(), max_iter=3).as_graph()

# set single_deployment to True to include all nodes within a single Ray Deployment
runner = graph.compile(use_ray=True, single_deployment=False)

app = serve.run(runner, name="my_application")

result = await app.ainvoke.remote(...)

Tracing

To enable tracing, you simply need to set the TINYAGENTS_ENABLE_TRACING environment variable to true.

import phoenix as px
session = px.launch_app()

import os
os.environ["TINYAGENTS_ENABLE_TRACING"] = "true"

# you can also set the collector endpoint as follows, otherwise the default endpoint for Phoenix will be used
# os.environ["COLLECTOR_ENDPOINT"] = "..."

Release files for tinyagents 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tinyagents 1.2.0
File Size Uploaded
tinyagents-1.2.0.tar.gz 15.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tinyagents 1.2.0
File Interpreter ABI Platform
tinyagents-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 33.6 kB

Release files / tinyagents-1.2.0.tar.gz

Download URL tinyagents-1.2.0.tar.gz
Size 15.2 kB
Tags Source
SHA-256 checksum
How to use checksums
23441cdb5896491281926a983a4525a57b26975772864ba0388be1b0b92031a5
BLAKE2b-256 checksum
How to use checksums
4f0fdec3ab253d8eda8ed24acdd0da0c109f4bd7ea90d69b25eb76b92f10a279
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.4 CPython/3.12.1 Linux/6.5.0-1025-azure

Release files / tinyagents-1.2.0-py3-none-any.whl

Download URL tinyagents-1.2.0-py3-none-any.whl
Size 18.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
de7e26f1dfc0a321bedb7a8c7d7e3fc27b68c5231a363a478232bb1b542a0e9b
BLAKE2b-256 checksum
How to use checksums
18901d10bcd650492e5827a50fbe9defb65c82ccfd2cc7d60ed85b17dbe7e182
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.4 CPython/3.12.1 Linux/6.5.0-1025-azure

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page