---
title: Agent Bridge
code-annotations: select
---

## Overview

While Inspect provides facilities for native agent development, you can also very easily integrate agents created with 3rd party frameworks like [AutoGen](https://microsoft.github.io/autogen/stable/) or [LangChain](https://python.langchain.com/docs/introduction/), or use fully custom agents you have developed or ported from a research paper. The basic mechanism for integrating external agents works like this:

1.  Write an agent function that takes a sample `dict` as input and a returns a results `dict` with output. This function won't have any dependencies on Inspect, rather it will depend on whatever agent framework or custom code you are using.

2.  This function should use the OpenAI API for model access, however calls to the OpenAI API will be *redirected* to Inspect (using whatever model is configured for the current task).

3.  Use the agent function with Inspect by passing it to the `bridge()` function, which will turn it into a standard Inspect `Agent`.

## Agent Function

An external agent function is similar to an Inspect `Agent` but without `AgentState`. Rather, it takes a sample `dict` as input and returns a result `dict` as output. 

Here is a very simple agent function definition (it just calls generate and returns the output). It is structured similar to an Inspect `Agent` where an enclosing function returns the function that handles the sample (this enables you to share initialisation code and pass options to configure the behaviour of the agent):
         
```{.python filename="agent.py"}
from openai import AsyncOpenAI

def my_agent():

    async def run(sample: dict[str, Any]) -> dict[str, Any]:
        client = AsyncOpenAI()
        completion = await client.chat.completions.create(
            model="inspect",
            messages=sample["input"],
        )
        return {
            "output": completion.choices[0].message.content
        }

    return run
```

We use the OpenAI API with `model="inspect"`, which enables Inspect to intercept the request and send it to the Inspect
model being evaluated for the task.

We read the input from `sample["input"]` (a list of OpenAI compatible messages) and return `output` as a string in the result `dict`.

Here is how you can use the `bridge()` function to use this agent as a solver:

```{.python filename="task.py"}
from inspect_ai import Task, task
from inspect_ai.agent import bridge
from inspect_ai.dataset import Sample
from inspect_ai.scorer import includes

from agents import my_agent # <1>

@task
def hello():
    return Task(
        dataset=[Sample(input="Please print the word 'hello'?", target="hello")],
        solver=bridge(my_agent()), # <2>
        scorer=includes(),
    )
```

1.  Import custom agent from `agent.py` file (shown above)
2.  Adapt custom agent into an Inspect agent with the `bridge()` function.

For more in-depth examples that make use of popular agent frameworks, see:

-   [AutoGen Example](https://github.com/UKGovernmentBEIS/inspect_ai/tree/main/examples/bridge/autogen)

-   [LangChain Example](https://github.com/UKGovernmentBEIS/inspect_ai/tree/main/examples/bridge/langchain)

We'll walk through the AutoGen example in more depth below.

### Example: AutoGen 

Here is an agent written with the [AutoGen](https://microsoft.github.io/autogen/stable/) framework. You'll notice that it is structured similar to an Inspect `Agent` where an enclosing function returns the function which handles the sample (this enables you to share initialisation code and pass options to configure the behaviour of the agent):

``` {.python filename="agent.py"}
from typing import Any, cast

from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import SourceMatchTermination
from autogen_agentchat.messages import TextMessage
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_core.models import ModelInfo
from autogen_ext.agents.web_surfer import MultimodalWebSurfer
from autogen_ext.models.openai import OpenAIChatCompletionClient

def web_surfer_agent():
   
    # Use OpenAI interface (redirected to Inspect model)
    model = OpenAIChatCompletionClient(                                   # <1>
        model="inspect",                                                  # <1>
        model_info=ModelInfo(                                             # <1>
            vision=True, function_calling=True,                           # <1>
            json_output=False, family="unknown"                           # <1>
        ),
    )

    # Sample handler
    async def run(sample: dict[str, Any]) -> dict[str, Any]:              # <2>
        # Read input (convert from OpenAI format)                         
        input = [                                                         # <3>
            TextMessage(source=msg["role"], content=str(msg["content"]))  # <3>
            for msg in sample["input"]                                    # <3>
        ]                                                                 # <3>

        # Create agents and team
        web_surfer = MultimodalWebSurfer("web_surfer", model)             # <4>
        assistant = AssistantAgent("assistant", model)                    # <4>
        termination = SourceMatchTermination("assistant")                 # <4> 
        team = RoundRobinGroupChat(                                       # <4>
            [web_surfer, assistant],                                      # <4>
            termination_condition=termination                             # <4>
        )

        # Run team
        result = await team.run(task=input)

        # Extract output from last message and return
        message = cast(TextMessage, result.messages[-1])                  # <5>
        return dict(output=message.content)                               # <5>

    return run
```

1.  Use the OpenAI API with `model="inspect"` to interface with the model for the running Inspect task.
2.  The `sample` includes `input` (chat messages) and the `result` includes model `output` as a string.
3.  Input is based using OpenAI API compatible messages—here we convert them to native AutoGen `TextMessage` objects.
4.  Configure and create AutoGen multi-agent team. This can use any combination of agents and any team structure including custom ones.
5.  Extract content from final assistant message and return it as `output`.

To use this agent in an Inspect `Task`, import it and use the `bridge()` function:

``` {.python filename="task.py"}
from inspect_ai import Task, task
from inspect_ai.agent import bridge
from inspect_ai.dataset import json_dataset
from inspect_ai.scorer import model_graded_fact

from agent import web_surfer_agent    # <1>

@task
def research() -> Task:
    return Task(
        dataset=json_dataset("dataset.json"),
        solver=bridge(web_surfer_agent()),    # <2>
        scorer=model_graded_fact(),
    )
```

1.  Import custom agent from `agent.py` file (shown above)
2.  Adapt custom agent into an Inspect agent with the `bridge()` function.

The `bridge()` function takes the agent function and hooks it up to a standard Inspect `Agent`, updating the `AgentState` and providing the means of redirecting OpenAI calls to the current Inspect model.


## Bridge Types

In the examples above we reference two `dict` fields from the agent function interface:

|                    |                                    |
|--------------------|------------------------------------|
| `sample["input"]`  | `list[ChatCompletionMessageParam]` |
| `result["output"]` | `str`                              |

: {tbl-colwidths=\[35,65\]}

Here are the full type declarations for the `sample` and `result`:

``` python
from typing import NotRequired, TypedDict

from openai.types.chat import ChatCompletionMessageParam

class SampleDict(TypedDict):
    messages: list[ChatCompletionMessageParam]

class ResultDict(TypedDict):
    output: str
    messages: NotRequired[list[ChatCompletionMessageParam]]
```

You aren't required to use these types exactly (they merely document the interface) so long as you consume and produce `dict` values that match their declarations (the result `dict` is type validated at runtime).

Returning `messages` is not required as messages are automatically synced to the agent state during generate (return `messages` only if you want to customise the default behaviour).


## CLI Usage

Above we import the `web_surfer_agent()` directly as a Python function. It's also possible to reference external agents at the command line using the `--solver` parameter. For example:

``` bash
inspect eval task.py --solver agent.py
```

This also works with `--solver` arguments passed via `-S`. For example:

``` bash
inspect eval task.py --solver agent.py -S max_requests=5
```

The `agent.py` source file will be searched for public top level functions that include `agent` in their name. If you want to explicitly reference an agent function you can do this as follows:

``` bash
inspect eval task.py --solver agent.py@web_surfer_agent
```

## Models

As demonstrated above, communication with Inspect models is done by using the OpenAI API with `model="inspect"`. You can use the same technique to interface with other Inspect models. To do this, preface the model name with "inspect" followed by the rest of the fully qualified model name.

For example, in a LangChain agent, you would do this to utilise the Inspect interface to Gemini:

``` python
model = ChatOpenAI(model="inspect/google/gemini-1.5-pro")
```

## Sandboxes

If you need to execute untrusted LLM generated code in your agent, you can still use the Inspect [`sandbox()`](sandboxing.qmd) within bridged agent functions. Typically agent tools that can run code are customisable with an executor, and this is where you would plug in the Inspect `sandbox()`.

For example, the AutoGen [`PythonCodeExecutionTool`](https://microsoft.github.io/autogen/stable/reference/python/autogen_ext.tools.code_execution.html#autogen_ext.tools.code_execution.PythonCodeExecutionTool) takes a [`CodeExecutor`](https://microsoft.github.io/autogen/stable/reference/python/autogen_core.code_executor.html#autogen_core.code_executor.CodeExecutor) in its constructor. AutoGen provides several built in code executors (e.g. local, docker, azure, etc.) and you can create custom ones. For example, you could create an `InspectSandboxCodeExecutor` which in turn delegates to the `sandbox().exec()` function.

## Transcript

Custom agents run through the `bridge()` function still get most of the benefit of the Inspect transcript and log viewer. All model calls are captured and produce the same transcript output as when using conventional agents. The message history is also automatically captured and logged.

Calls to the Python `logging` module for levels `info` and above are also handled as normal and show up within sample transcripts.

If you want to use additional features of Inspect transcripts (e.g. steps, markdown output, etc.) you can still import and use the `transcript` function as normal. For example:

``` python
from inspect_ai.log import transcript

transcript().info("custom *markdown* content")
```

This code will no-op when running outside of Inspect to it is safe to include in agents that are also run in other environments.
