diff --git a/docs/.index b/docs/.index index 03f3e36b..7d6a5c7a 100644 --- a/docs/.index +++ b/docs/.index @@ -7,10 +7,5 @@ nav: - Observability and Evaluation: obs-and-eval - CSIT: csit - CoffeeAGNTCY: coffee-agntcy - - Semantic SDK: semantic - - Syntactic SDK: syntactic - - Agent Workflow Server: agws - - Agent Manifest: manifest - - How-To Guides: how-to-guides - Glossary: glossary.md - How to Contribute: contributing.md diff --git a/docs/agws/.index b/docs/agws/.index deleted file mode 100644 index bfcd485b..00000000 --- a/docs/agws/.index +++ /dev/null @@ -1,3 +0,0 @@ -nav: - - Agent Workflow Server: workflow-server.md - - Workflow Server Manager: workflow-server-manager.md diff --git a/docs/agws/workflow-server-manager.md b/docs/agws/workflow-server-manager.md deleted file mode 100644 index 22a35cfe..00000000 --- a/docs/agws/workflow-server-manager.md +++ /dev/null @@ -1,146 +0,0 @@ -# Workflow Server Manager - -!!! info - This component is not currently under active development. While it remains available, updates and new features are not planned at this time. - -The Workflow Server Manager (WFSM) is a command line tool that streamlines the process of wrapping an agent into a container image, starting the container, and exposing the agent functionality through the Agent Connect Protocol (ACP). - -The WFSM tool takes an [Agent Manifest](../manifest/manifest.md) as input and based on it spins up a web server container exposing the agent through ACP through REST api. - -## Getting Started - -### Prerequisites - -The utility requires Docker engine, `docker`, and `docker-compose` to be present on the host. - -To make sure the docker setup is correct on the host, execute the following: - -```bash -wfsm check -``` - -In case the command signals error you can pass the `-v` flag to display verbose information about the failure. - -## Installation - -Download and unpack the executable binary from the [releases page](https://github.com/agntcy/workflow-srv-mgr/releases). - -Alternatively you can execute the installer script by running the following command: - -```bash -curl -L https://raw.githubusercontent.com/agntcy/workflow-srv-mgr/refs/heads/main/install.sh | bash -``` - -The installer script will download the latest release and unpack it into the `bin` folder in the current directory. -The output of the execution looks like this: - -```bash - curl -L https://raw.githubusercontent.com/agntcy/workflow-srv-mgr/refs/heads/install/install.sh | bash [16:05:58] - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 1034 100 1034 0 0 2597 0 --:--:-- --:--:-- --:--:-- 2597 -Installing the Workflow Server Manager tool: - -OS: darwin -ARCH: arm64 -AG: 0.0.1-dev.23 -TARGET: /Users/johndoe/.wfsm/bin -ARCHIVE_URL: https://github.com/agntcy/workflow-srv-mgr/releases/download/v0.0.1-dev.23/wfsm0.0.1-dev.23_darwin_arm64.tar.gz - - -Installation complete. The 'wfsm' binary is located at /Users/johndoe/.wfsm/bin/wfsm -``` - -Listed variables can be overridden by providing the values as variables to the script - -## Run - -Execute the unpacked binary. This outputs the usage string with the available flags and options. - -```bash - -$ wfsm - -ACP Workflow Server Manager Tool - -Wraps an agent into a web server and exposes the agent functionality through ACP. -It also provides commands for managing existing deployments and cleanup tasks - -Usage: - wfsm [command] - -Available Commands: - check Checks the prerequisites for the command - completion Generate the autocompletion script for the specified shell - deploy Build an ACP agent - help Help about any command - list List an ACP agents running in the deployment - logs Show logs of an ACP agent deployment(s) - stop Stop an ACP agent deployment - -Flags: - -h, --help help for wfsm - -v, --version version for wfsm - -Use "wfsm [command] --help" for more information about a command. - -``` - -## Test the Results - -The exposed REST endpoints can be accessed with regular tools (for example, Curl or Postman). - -## Examples - -Example manifests can be found in the [WFSM Tool](https://github.com/agntcy/workflow-srv-mgr/tree/main/examples) repository. - -> Note: -> Paths to the manifests and the paths inside the manifest definitions in the example commands need to be correct on the environment they are executed in! - -### Expose the [Mail Composer](https://github.com/agntcy/workflow-srv-mgr/tree/main/examples) LangGraph agent through ACP workflow server - -```bash -wfsm deploy -m examples/langgraph_manifest.json -e examples/env_vars.yaml -``` - -### Expose the [Email Reviewer](https://github.com/agntcy/workflow-srv-mgr/tree/main/examples) llama deploy workflow agent through ACP workflow server - -```bash -wfsm deploy -m examples/llama_manifest.json -e examples/env_vars.yaml -``` - -### Expose an agent with dependencies through the ACP workflow server - -```bash -wfsm deploy -m examples/manifest_with_deps.json -e examples/env_vars_with_deps.yaml -``` - ->Make sure the url to the manifest of the dependent agent is either an absolute path or a relative path to the directory you are running `wfsm` tool. - -### Run agent from docker image - -Run deploy with `--dryRun` to build images. - -```bash -wfsm deploy -m examples/langgraph_manifest.json -e examples/env_vars.yaml --dryRun -``` - -Get the image tag from console athset in the manifest. - -``` - "deployment_options": [ - - { - "type": "docker", - "name": "docker", - "image": "agntcy/wfsm-mailcomposer:" - } - ... - ] -``` - -Run `wfsm` again now with `--deploymentOption=docker`: - -```bash -wfsm deploy -m examples/langgraph_manifest.json -e examples/env_vars.yaml --deploymentOption=docker -``` diff --git a/docs/agws/workflow-server.md b/docs/agws/workflow-server.md deleted file mode 100644 index a7d169e3..00000000 --- a/docs/agws/workflow-server.md +++ /dev/null @@ -1,152 +0,0 @@ -# Agent Workflow Server - -!!! info - This component is not currently under active development. While it remains available, updates and new features are not planned at this time. - -The [Agent Workflow Server](https://github.com/agntcy/workflow-srv) -enables participation in the Internet of Agents. It accommodates AI -Agents from diverse frameworks and exposes them through Agent Connect -Protocol ([ACP](../syntactic/agntcy_acp_sdk.md)), regardless of -their underlying implementation. - -!!! note - If you wish to quickly deploy and run your Agent, please check out the - user-facing [Workflow Server Manager](../agws/workflow-server-manager.md) - instead. - -## Getting Started - -### Prerequisites - -You need to have installed the following software to run the Agent - -- Python 3.12 (or above) -- Poetry 2.0 (or above) - -### Local development - -1. Clone Agent Workflow Server repository: - ```sh - git clone https://github.com/agntcy/workflow-srv.git - ``` - -2. Copy example env file and adapt if necessary: - ```sh - cp .env.example .env - ``` - -3. Create a virtual environment and install the server dependencies: - ```sh - poetry install - ``` - -4. Install an agent ([See - examples](https://github.com/agntcy/acp-sdk/tree/main/examples)). - E.g.: - ```sh - pip install agntcy/acp-sdk/examples/mailcomposer - ``` - -5. Start the server: - ```sh - poetry run server - ``` - -### Generating API - -1. If it's the first time you're cloning this repo, initialize - submodule: - ```sh - git submodule update --init --recursive - ``` - -2. Run: - ```sh - make generate-api - ``` - -Generated code (API routes template and models) is under -`src/agent_workflow_server/generated`. - -- If needed, API routes template could be manually copied and - implemented under `src/agent_workflow_server/apis` -- Models should not be copied over different places nor modified, but - referenced as they are - -## Authentication - -The Agent Workflow Server, and the underlying Agent, could be optionally -authenticated via a pre-defined API Key: - -- Set `API_KEY` environment variable with a pre-defined value to enable - authentication -- Include the same value in requests from clients via `x-api-key` header - -## API Documentation - -Once the Agent Workflow Server is running, interactive API docs are -available under `/docs` endpoint, redoc documentation under `/redoc` -endpoint - -## Current Support - -**Supported frameworks and features** - -| Framework | Supported versions | Invoke | Streaming | Threads | Callbacks | Interrupts (Human-in-the-loop) | -|------------|--------------------|--------|-----------|---------|-----------|-------------------------------| -| LangGraph | >=0.2.60,<0.4.0 | โœ”๏ธ | ๐Ÿšง | ๐Ÿšง | ๐Ÿšง | โœ”๏ธ | -| LlamaIndex | >=0.12.0,<0.13.0 | โœ”๏ธ | ๐Ÿšง | ๐Ÿšง | ๐Ÿšง | ๐Ÿšง | - -## Contributing - -### ACP API Contribution - -Agent Workflow Server implements ACP specification to expose Agents -functionalities. To contribute to the ACP API, check out [Agent Connect -Protocol Specification](https://github.com/agntcy/acp-spec). - -### Adapters SDK Contribution - -Agent Workflow Server supports different agentic frameworks via -`Adapters`. - -The process of implementing support of a new framework is pretty -straightforward, as the server dynamically loads `Adapters` at runtime. - -`Adapters` are placed under `src/agent_workflow_server/agents/adapters` -and must implement `BaseAdapter` class. - -To support a new framework, or extend functionality, one must implement -the `load_agent` method. To invoke that agent, one must implement the -`astream` method. - -See example below, supposing support to a new framework `MyFramework` -should be added. - -``` python -# src/agent_workflow_server/agents/adapters/myframework.py - -class MyAgent(BaseAgent): - def __init__(self, agent: object): - self.agent = agent - - async def astream(self, input: dict, config: dict): - # Call your agent here (and stream events) - # e.g.: - async for event in self.agent.astream( - input=input, config=config - ): - yield event - - -class MyAdapter(BaseAdapter): - def load_agent(self, agent: object): - # Check if `agent` is supported by MyFramework and if so return it - # e.g.: - if isinstance(agent, MyAgentType): - return MyAgent(agent) - # Optionally add support to other Agent Types: - # e.g.: - # if isinstance(agent, MyOtherAgentType): - # return MyAgent(MyAgentTypeConv(agent)) -``` diff --git a/docs/how-to-guides/.index b/docs/how-to-guides/.index deleted file mode 100644 index 600c312b..00000000 --- a/docs/how-to-guides/.index +++ /dev/null @@ -1,9 +0,0 @@ -nav: - - How-To Guides: - - Setting Up the Agent Directory: agent-directory.md - - Creating an Agent Record: agent-record-guide.md - - Getting Started with Identity: identity-quickstart.md - - Getting Started with API Bridge Agent: bridge-howto.md - - Build your first app: mas-creation-tutorial - - Building Applications with ACP Threads: thread.md - - Building Applications with ACP Interrupts: interrupts.md diff --git a/docs/how-to-guides/agent-directory.md b/docs/how-to-guides/agent-directory.md deleted file mode 100644 index d1a81938..00000000 --- a/docs/how-to-guides/agent-directory.md +++ /dev/null @@ -1,104 +0,0 @@ -# Using the Agent Directory - -The Agent Directory (dir) allows publication, exchange and discovery of information about AI agents over a distributed peer-to-peer network. - -For detailed information in the Agent Directory Service, see the [Agent Directory Service documentation](../dir/overview.md#features). - -## Prerequisites - -To build the project and work with the code, you need the following components installed: - -- [Taskfile](https://taskfile.dev/) -- [Docker](https://www.docker.com/) -- [Golang](https://go.dev/doc/devel/release#go1.24.0) - -!!! note - Make sure Docker is installed with Buildx. - -## Development Workflow - -Use `Taskfile` for all related development operations such as testing, validating, deploying, and working with the project. - -### Clone the repository - -```bash -git clone https://github.com/agntcy/dir -cd dir -``` - -### Initialize the project - -This step will fetch all project dependencies and prepare the environment for development. - -```bash -task deps -``` - -### Make changes - -Make the changes to the source code and rebuild for later testing. - -```bash -task build -``` - -### Test changes - -The local testing pipeline relies on Golang to perform unit tests, and -Docker to perform E2E tests in an isolated Kubernetes environment using Kind. - -```bash -task test:unit -task test:e2e -``` - -## Artifacts distribution - -All artifacts are tagged using the [Semantic Versioning](https://semver.org/) and follow the checked out source code tags. -It is not advised to use artifacts with mismatching versions. - -### Container images - -All container images are distributed via [GitHub Packages](https://github.com/orgs/agntcy/packages?repo_name=dir). - -```bash -docker pull ghcr.io/agntcy/dir-ctl:v0.2.0 -docker pull ghcr.io/agntcy/dir-apiserver:v0.2.0 -``` - -### Helm charts - -All helm charts are distributed as OCI artifacts via [GitHub Packages](https://github.com/agntcy/dir/pkgs/container/dir%2Fhelm-charts%2Fdir). - -```bash -helm pull oci://ghcr.io/agntcy/dir/helm-charts/dir --version v0.2.0 -``` - -### Binaries - -All release binaries are distributed via [GitHub Releases](https://github.com/agntcy/dir/releases). - -### SDKs - -- **Golang** - [github.com/agntcy/dir/api](https://pkg.go.dev/github.com/agntcy/dir/api), [github.com/agntcy/dir/cli](https://pkg.go.dev/github.com/agntcy/dir/cli), [github.com/agntcy/dir/server](https://pkg.go.dev/github.com/agntcy/dir/server) - -## Deployment - -Directory API services can be deployed either using the `Taskfile` or directly via released Helm chart. - -### Using Taskfile - -This will start the necessary components such as storage and API services. - -```bash -task server:start -``` - -### Using Helm chart - -This will deploy Directory services into an existing Kubernetes cluster. - -```bash -helm pull oci://ghcr.io/agntcy/dir/helm-charts/dir --version v0.2.0 -helm upgrade --install dir oci://ghcr.io/agntcy/dir/helm-charts/dir --version v0.2.0 -``` diff --git a/docs/how-to-guides/bridge-howto.md b/docs/how-to-guides/bridge-howto.md deleted file mode 100644 index d114eb2a..00000000 --- a/docs/how-to-guides/bridge-howto.md +++ /dev/null @@ -1,1360 +0,0 @@ -# Getting Started with API Bridge Agent - -For a detailed description on the API Bridge Agent, see [API Bridge Agent documentation](../syntactic/api_bridge_agent.md). - -## Prerequisites - -To build the plugin you need the following dependencies: -- Go -- CMake -- Git -- jq - -Get the source code by running the following command: - -``` -git clone https://github.com/agntcy/api-bridge-agnt -``` - -Tyk requires also a Redis database. Deploy it with the following command: - -```shell -make start_redis -``` - -### Local Development - -Built with: - -- [Search](https://github.com/kelindar/search) for the semantic router. -- [Tyk](https://github.com/TykTechnologies/tyk.git) for the gateway. -We use these dependencies inside the project. However, you don't need to download it or to build it, -everything is managed by the Makefile. - -#### Set Environment Variables - -For OpenAI: - -```shell -export OPENAI_API_KEY=REPLACE_WITH_YOUR_KEY -export OPENAI_MODEL=gpt-4o-mini -``` - -For Azure OpenAI: - -```shell -export OPENAI_API_KEY=REPLACE_WITH_YOUR_KEY -export OPENAI_ENDPOINT=https://REPLACE_WITH_YOUR_ENDPOINT.openai.azure.com -export OPENAI_MODEL=gpt-4o-mini -``` - -#### Build the Plugin and Start Tyk Locally on Tyk - -Dependencies are managed so that you can just run: - -```shell -make start_tyk -``` - -This will automatically build "Tyk", "search" and the plugin, then install the plugin and start Tyk gateway at [http://localhost:8080](http://localhost:8080). - -#### Load and Configure Tyk with an [Example API](https://httpbin.org/) - -```shell -make load_plugin -``` - -### Other Installation - -#### Linux - -For Linux (Ubuntu) you can use: - -```shell -TARGET_OS=linux TARGET_ARCH=amd64 SEARCH_LIB=libllama_go.so make start_tyk -``` - -#### Individual Steps for Building if Needed: - -If you need to decompose each task individually, you can split into: - -```shell -make build_tyk # build tyk -make build_search_lib # build the "search" library, used as semantic router -make build_plugin # build the plugin -make install_plugin # Install the plugin -``` - -## Tyk Configuration - -This plugin relies on [Tyk OAS API Definition](https://tyk.io/docs/api-management/gateway-config-tyk-oas/). - -You need to apply some configuration when: -- you add a new service -- you want to activate the new cross-api interface -- you want to activate the mcp interface -- you add the support for a new MCP server - -### Adding a new Service - -Add the plugin to the `postPlugins` and `responsePlugins` sections of the `x-tyk-api-gateway` section: - -```json -"x-tyk-api-gateway": { - "middleware": { - "global": { - "pluginConfig": { - "data": { - "enabled": true, - "value": { - } - }, - "driver": "goplugin" - }, - "postPlugins": [ - { - "enabled": true, - "functionName": "SelectAndRewrite", - "path": "middleware/agent-bridge-plugin.so" - }, - { - "enabled": true, - "functionName": "RewriteQueryToOas", - "path": "middleware/agent-bridge-plugin.so" - } - ], - "responsePlugins": [ - { - "enabled": true, - "functionName": "RewriteResponseToNl", - "path": "middleware/agent-bridge-plugin.so" - } - ] - } - } -} -``` - -Then add your OpenAPI specification: - -For example, adding the httpbin.org service can be done using the `configs/httpbin.org.oas.json` file. - -```shell -curl http://localhost:8080/tyk/apis/oas \ - --header "x-tyk-authorization: foo" \ - --header 'Content-Type: text/plain' \ - -d@configs/httpbin.org.oas.json - -curl http://localhost:8080/tyk/reload/group \ - --header "x-tyk-authorization: foo" -``` - -It's then possible to do a query like this: - -```shell -curl http://localhost:8080/httpbin/json \ - --header "X-Nl-Query-Enabled: yes" \ - --header "X-Nl-Response-Type: nl" \ - --header "Content-Type: text/plain" \ - -d "Hello" -``` - -In this example `http://localhost:8080/httpbin/json`: - -- "/httpbin/" is the listen path defined on the `x-tyk-api-gateway` part of the specification -- "json" is the endpoint on the specification - -### Activate the new Cross-API interface - -The new Cross-API interface, available at the `/aba/` endpoint, is not activated by default. - -To activate it, use the provided `configs/api-bridge-agent.oas.json` file: -then restart - -```shell -curl http://localhost:8080/tyk/apis/oas \ - --header 'x-tyk-authorization: foo' \ - --header 'Content-Type: text/plain' \ - -d@configs/api-bridge-agent.oas.json - -curl http://localhost:8080/tyk/reload/group --header 'x-tyk-authorization: foo' -``` - -It's then possible to do a query like this: - -```shell -curl http://localhost:8080/aba/ \ - --header "Content-Type: application/nlq" \ - -d "Send email to . The content is 'Hello from Mr Smith', The subject is 'This is a test' and the reply-to address is . No BCC, no CC" -``` - -If you have a service that support action to send an email, the Cross-API interface will route this request to that service. -Otherwise, a "404 Not found" will be answered. - -Note: it is possible to change the listen path `/aba/` by editing the `configs/api-bridge-agent.oas.json` file before activate it. - -### Activate the new MCP interface - -The new MCP interface, available across the `/mcp/` endpoint, is not activated by default. - -To activate it, use the provided `configs/mcp.oas.json` file - -```shell -curl http://localhost:8080/tyk/apis/oas \ - --header 'x-tyk-authorization: foo' \ - --header 'Content-Type: text/plain' \ - -d@configs/mcp.oas.json - -curl http://localhost:8080/tyk/reload/group --header 'x-tyk-authorization: foo' - -curl http://localhost:8080/mcp/init -``` - -Note: do not change the listen path `/mcp/` on the `configs/mcp.oas.json` file. It is used internally. - -### Adding support for a new MCP server - -You have to edit the `configs/mcp.oas.json` file, then reload the Tyk configuration. - -Inside the `configs/mcp.oas.json` file, add the new MCP server to the `mcpServers` list. -A valid configuration need a `command` and `args`, or a SSE address. This is some examples of such configuration: - -```json -"mcpServers": { - "weather": { - "command": "poetry", - "args": [ - "run", - "python", - "../../../mcp/weather/weather.py" - ] - }, - "git": { - "command": "poetry", - "args": [ - "run", - "python", - "../../../mcp/servers/git/src/mcp_server_git/server.py", - "--repository", - "/media/sf_vmshared/my_repository" - ] - }, - "github": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "-e", - "GITHUB_PERSONAL_ACCESS_TOKEN", - "ghcr.io/github/github-mcp-server" - ], - "env": ["GITHUB_PERSONAL_ACCESS_TOKEN=${GITHUB_PERSONAL_ACCESS_TOKEN}"] - }, - "resend": { - "command": "node", - "args": [ - ".../mcp/mcp-send-email/build/index.js", - "--key=", - "--sender=john.doe@resend.dev" - ] - }, - "weather-sse": { - "sse": "http://127.0.0.1:8000/sse" - }, -} -``` - -then refresh Tyk with the new configuration: - -```shell -curl http://localhost:8080/tyk/apis/oas \ - --header 'x-tyk-authorization: foo' \ - --header 'Content-Type: text/plain' \ - -d@configs/mcp.oas.json - -curl http://localhost:8080/tyk/reload/group --header 'x-tyk-authorization: foo' - -curl http://localhost:8080/mcp/init -``` - -### Using API Bridge Agent - -When using API Bridge Agent, you *MUST* use the content type `application/nlq` on your request - -```shell -curl http://localhost:8080/aba/ \ - --header "Content-Type: application/nlq" \ - -d "Send email to . The content is "Hello from Mr Smith", The subject is "This is a test" and - the reply-to address is . No BCC, no CC" -``` - -## An Example with Github - -The `configs/api.github.com.gist.deref.oas.json` file is a subset of the Github API, already configured with a few `x-nl-input-examples`: - -```shell -curl 'http://localhost:8080/github/' \ - --header 'Content-Type: application/nlq' \ - -d 'List the first issue for the repository named tyk owned by TykTechnologies with the label bug' -``` - -## An Example with Sendgrid API - -As a usage example, we will use the API Bridge Agent to send email via SENGRID API. - -### Prerequisites - -- Get an API Key for free from sendgrid [sengrid by twilio](https://sendgrid.com/en-us). -- Retrieve the open api spec here [tsg_mail_v3.json](https://github.com/twilio/sendgrid-oai/blob/main/spec/json/tsg_mail_v3.json). -- Make sure redis is running (otherwise, use `make start_redis`). -- Make sure you properly export `OPENAI_*` parameters. -- Start the plugin as described on "Getting Started" section. - -### Update the API with Tyk middleware settings - -Configure Tyk to use the sendgrid API by adding the `x-tyk-api-gateway` extension: - -```json -{ - "x-tyk-api-gateway": { - "info": { - "id": "tyk-sendgrid-id", - "name": "Sendgrid Mail API", - "state": { - "active": true - } - }, - "upstream": { - "url": "https://api.sendgrid.com" - }, - "server": { - "listenPath": { - "value": "/sendgrid/", - "strip": true - } - }, - "middleware": { - "global": { - "pluginConfig": { - "data": { - "enabled": true, - "value": { - } - }, - "driver": "goplugin" - }, - "postPlugins": [ - { - "enabled": true, - "functionName": "SelectAndRewrite", - "path": "middleware/agent-bridge-plugin.so" - }, - { - "enabled": true, - "functionName": "RewriteQueryToOas", - "path": "middleware/agent-bridge-plugin.so" - } - ], - "responsePlugins": [ - { - "enabled": true, - "functionName": "RewriteResponseToNl", - "path": "middleware/agent-bridge-plugin.so" - } - ] - } - } - } -} -``` - -You have an example of a such configuration in `configs/api.sendgrid.com.oas.json` - -### Configure an Endpoint to Allow Plugin to Retrieve It - -On the same oas file, add a `x-nl-input-examples` element to an endpoint with -sentence that describe how you can use the endpoint with natural language. -For example: - -```json -{ - "...": "...", - "paths": { - "...": "...", - "/v3/mail/send": { - "...": "...", - "post": { - "...": "...", - "x-nl-input-examples": [ - "Send an message to 'test@example.com' including a joke. Please use emojis inside it.", - "Send an email to 'test@example.com' including a joke. Please use emojis inside it.", - "Tell to 'test@example.com' that his new car is available.", - "Write a professional email to reject the candidate 'John Doe . John is french, the message should be a joke using a lot of emojis, something fun about comparing France and Italy' -``` - -As a result, the receiver (j.doe@example.com) must receive a mail like: -``` - -Subject: A Little Joke for You! ๐Ÿ‡ซ๐Ÿ‡ท๐Ÿ‡ฎ๐Ÿ‡น - -Hey John! ๐Ÿ˜„ - -I hope you're having a fantastic day! I just wanted to share a little joke with you: - -Why did the French chef break up with the Italian chef? ๐Ÿฝ๏ธโค๏ธ - -Because he couldn't handle all the pasta-bilities! ๐Ÿ๐Ÿ˜‚ - -But don't worry, they still have a "bready" good friendship! ๐Ÿฅ–๐Ÿ˜œ - -Just remember, whether it's croissants or cannoli, we can all agree that food brings us together! ๐Ÿท๐Ÿฐ - -Take care and keep smiling! ๐Ÿ˜Š - -Best, -agntcy - -``` - -## An example by adding a new API from scratch - -Let's say you want to add a new API to your Tyk API Gateway, and make it available over natural language. Here are the steps: - -- Create or get the OpenAPI specification for your API. -- Add the `x-tyk-api-gateway` section to your OpenAPI specification. -- Add the `x-nl-input-examples` section to your OpenAPI operations. -- Configure Tyk to use this new API. -- Query your new API with natural language questions. - -### Let's choose an API - -Let's choose . -There is no OpenAPI specification available so we create one. - -
-OpenSky Network OpenAPI (Click to expand) - -```json -{ - "openapi": "3.0.0", - "info": { - "title": "OpenSky Network API", - "description": "API for accessing flight tracking data from the OpenSky Network", - "version": "1.0.0" - }, - "servers": [ - { - "url": "https://opensky-network.org/api" - } - ], - "paths": { - "/states/all": { - "get": { - "operationId": "getStatesAll", - "summary": "Get all state vectors", - "description": "Retrieve any state vector of the OpenSky Network", - "parameters": [ - { - "name": "time", - "in": "query", - "schema": { - "type": "integer" - }, - "description": "Unix timestamp to retrieve states for" - }, - { - "name": "icao24", - "in": "query", - "schema": { - "type": "string" - }, - "description": "ICAO24 transponder address in hex" - }, - { - "name": "lamin", - "in": "query", - "schema": { - "type": "number", - "format": "float" - }, - "description": "Lower bound for latitude" - }, - { - "name": "lomin", - "in": "query", - "schema": { - "type": "number", - "format": "float" - }, - "description": "Lower bound for longitude" - }, - { - "name": "lamax", - "in": "query", - "schema": { - "type": "number", - "format": "float" - }, - "description": "Upper bound for latitude" - }, - { - "name": "lomax", - "in": "query", - "schema": { - "type": "number", - "format": "float" - }, - "description": "Upper bound for longitude" - }, - { - "name": "extended", - "in": "query", - "schema": { - "type": "integer" - }, - "description": "Set to 1 to include aircraft category" - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StateVector" - } - } - } - } - } - } - }, - "/states/own": { - "get": { - "operationId": "getStatesOwn", - "summary": "Get own state vectors", - "description": "Retrieve state vectors for authenticated user's sensors", - "security": [ - { - "basicAuth": [] - } - ], - "parameters": [ - { - "name": "time", - "in": "query", - "schema": { - "type": "integer" - } - }, - { - "name": "icao24", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "serials", - "in": "query", - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StateVector" - } - } - } - } - } - } - }, - "/flights/all": { - "get": { - "operationId": "getFlightsAll", - "summary": "Get flights in time interval", - "parameters": [ - { - "name": "begin", - "in": "query", - "required": true, - "schema": { - "type": "integer" - } - }, - { - "name": "end", - "in": "query", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Flight" - } - } - } - } - } - } - } - }, - "/flights/aircraft": { - "get": { - "operationId": "getFlightsAircraft", - "summary": "Get flights by aircraft", - "description": "Retrieve flights for a particular aircraft within a time interval", - "parameters": [ - { - "name": "icao24", - "in": "query", - "required": true, - "schema": { - "type": "string" - }, - "description": "Unique ICAO 24-bit address of the transponder in hex string representation (lower case)" - }, - { - "name": "begin", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "Start of time interval as Unix timestamp (seconds since epoch)" - }, - { - "name": "end", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "End of time interval as Unix timestamp (seconds since epoch)" - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Flight" - } - } - } - } - }, - "404": { - "description": "No flights found for the given time period" - } - } - } - }, - "/flights/arrival": { - "get": { - "operationId": "getFlightsArrival", - "summary": "Get arrivals by airport", - "description": "Retrieve flights that arrived at a specific airport within a given time interval", - "parameters": [ - { - "name": "airport", - "in": "query", - "required": true, - "schema": { - "type": "string" - }, - "description": "ICAO identifier for the airport" - }, - { - "name": "begin", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "Start of time interval as Unix timestamp (seconds since epoch)" - }, - { - "name": "end", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "End of time interval as Unix timestamp (seconds since epoch)" - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Flight" - } - } - } - } - }, - "404": { - "description": "No flights found for the given time period" - } - } - } - }, - "/flights/departure": { - "get": { - "operationId": "getFlightsDeparture", - "summary": "Get departures by airport", - "description": "Retrieve flights that departed from a specific airport within a given time interval", - "parameters": [ - { - "name": "airport", - "in": "query", - "required": true, - "schema": { - "type": "string" - }, - "description": "ICAO identifier for the airport (usually upper case)" - }, - { - "name": "begin", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "Start of time interval as Unix timestamp (seconds since epoch)" - }, - { - "name": "end", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "End of time interval as Unix timestamp (seconds since epoch)" - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Flight" - } - } - } - } - }, - "404": { - "description": "No flights found for the given time period" - } - } - } - }, - "/tracks": { - "get": { - "operationId": "getTracks", - "summary": "Get track by aircraft", - "description": "Retrieve the trajectory for a certain aircraft at a given time. The trajectory is a list of waypoints\ncontaining position, barometric altitude, true track and an on-ground flag.\nNote: This endpoint is experimental.\n", - "parameters": [ - { - "name": "icao24", - "in": "query", - "required": true, - "schema": { - "type": "string" - }, - "description": "Unique ICAO 24-bit address of the transponder in hex string representation (lower case)" - }, - { - "name": "time", - "in": "query", - "required": true, - "schema": { - "type": "integer" - }, - "description": "Unix timestamp. Can be any time between start and end of a known flight. If time = 0, returns live track if there is any ongoing flight." - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Track" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "StateVector": { - "type": "object", - "properties": { - "time": { - "type": "integer" - }, - "states": { - "type": "array", - "items": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "type": "integer" - } - } - ] - } - } - } - } - }, - "Flight": { - "type": "object", - "properties": { - "icao24": { - "type": "string" - }, - "firstSeen": { - "type": "integer" - }, - "lastSeen": { - "type": "integer" - }, - "callsign": { - "type": "string" - } - } - }, - "Track": { - "type": "object", - "properties": { - "icao24": { - "type": "string", - "description": "Unique ICAO 24-bit address of the transponder in lower case hex string" - }, - "startTime": { - "type": "integer", - "description": "Time of the first waypoint in seconds since epoch" - }, - "endTime": { - "type": "integer", - "description": "Time of the last waypoint in seconds since epoch" - }, - "callsign": { - "type": "string", - "nullable": true, - "description": "Callsign (8 characters) that holds for the whole track" - }, - "path": { - "type": "array", - "description": "Waypoints of the trajectory", - "items": { - "type": "array", - "minItems": 6, - "maxItems": 6, - "items": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - } - } - } - }, - "securitySchemes": { - "basicAuth": { - "type": "http", - "scheme": "basic" - } - } - } -} -``` - -
- -### Update the API and configure Tyk - -Let's add the `x-tyk-api-gateway` extension. You can find more information about -it in the [Tyk OAS API Definition](https://tyk.io/docs/api-management/gateway-config-tyk-oas/). - -```json - "x-tyk-api-gateway": { - "info": { - "id": "tyk-opensky-network-id", - "name": "OpenSky Network API", - "state": { - "active": true - } - }, - "upstream": { - "url": "https://opensky-network.org/api" - }, - "server": { - "listenPath": { - "value": "/opensky/", - "strip": true - } - }, - "middleware": { - "global": { - "pluginConfig": { - "data": { - "enabled": true, - "value": {} - }, - "driver": "goplugin" - }, - "postPlugins": [ - { - "enabled": true, - "functionName": "SelectAndRewrite", - "path": "middleware/agent-bridge-plugin.so" - }, - { - "enabled": true, - "functionName": "RewriteQueryToOas", - "path": "middleware/agent-bridge-plugin.so" - } - ], - "responsePlugins": [ - { - "enabled": true, - "functionName": "RewriteResponseToNl", - "path": "middleware/agent-bridge-plugin.so" - } - ] - } - } - }, -``` - -### Configure Tyk with this new API - -```shell -curl http://localhost:8080/tyk/apis/oas \ - --header "x-tyk-authorization: foo" \ - --header 'Content-Type: text/plain' \ - -d@configs/opensky_network.json - -curl http://localhost:8080/tyk/reload/group --header "x-tyk-authorization: foo" -``` - -- Tyk is now listening on the `/opensky/` path. -- You can now query the `/opensky/` path using a natural language query, by - adding the `application/nlq` HTTP Content-Type header, and your query as body. - -```shell -curl http://localhost:8080/opensky/ \ - --header 'Content-Type: application/nlq' \ - -d 'Get flights from 12pm to 1pm on March 11th 2025' - -Here are the flights that were recorded between 12 PM and 1 PM on March 11th, 2025: - -1. **Flight DESET** (ICAO24: 3d34ab) - - Departure Airport: EDEN - - Arrival Airport: EDVK - - First Seen: 12:54 PM - - Last Seen: 1:03 PM - - Horizontal Distance from Departure Airport: 9,544 meters - - Vertical Distance from Departure Airport: 1,181 meters - -2. **Flight THD120** (ICAO24: 88530e) - - Departure Airport: VTBS - - Arrival Airport: Not specified - - First Seen: 12:47 PM - - Last Seen: 12:54 PM - - Horizontal Distance from Departure Airport: 2,237 meters - - Vertical Distance from Departure Airport: 432 meters - -[...] -``` - -Here is what happened behind the scenes: - -1. **Content-Type Detection**: The system recognizes the `application/nlq` content type. -2. **Operation Matching**: - - Compares your query ("Get flights from 12pm to 1pm on March 11th 2025") - against the `x-nl-input-examples` or the operation description. - - Identifies the closest matching operation (`getFlightsAll` in this case, ie `GET /flights/all`). -3. **Parameter Extraction**: - - An LLM extracts relevant parameters from your query. - - Builds a proper API request. -4. **Request Transformation**: - - Converts the natural query to a proper HTTP request. - - Forwards the request to the upstream API (`GET /flights/all?start=1678560000&end=1678563600` for example). -5. **Response Handling**: - - Receives the raw API response. - - Returns the JSON response with the flight data. - -Without any code you were able to query the OpenSky Network API and retrieve flight data. - -### Improving the Operation Matching - -When the `description` field provided in the operation definition are poorly -written or missing, it's possible to improve the selection of the operation by -providing examples of queries that should match the operation. - -This is done by adding the `x-nl-input-examples` field to the operation definition. - -For example, you could provide the following examples which could help improve the operation matching: - -In the OpenAPI specification, add the `x-nl-output-examples` field to the operation definition: - -```json ---- -lineno-start: 123 -emphasize-lines: 7-11 ---- -{ -"paths": { - "/flights/all": { - "get": { - "operationId": "getFlightsAll", - "summary": "Get flights in time interval", - "x-nl-input-examples": [ - "Get flights from 12pm to 1pm on March 11th 2025", - "Donne moi les vols de 12h ร  13h le 11 mars 2025", - "List of flights from 12pm to 1pm on March 11th 2025" - ] - "parameters": [ - { - "name": "begin", - "in": "query", - "required": true, - "schema": { - "type": "integer" - } - }, - { - "name": "end", - "in": "query", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Flight" - } - } - } - } - } - } - } - } -} -``` - -## An Example with a new MCP Server - -In this example, we have already activated MCP support -We want to add a new MCP server (weather) in addition to the existing one (the default one) github. - -1- create a MCP server - -create a folder `mcp-weather`, then inside this folder create a file `weather.py` and copy paste the following content: - -
-weather.py content (Click to expand) - -```python -from typing import Any -import httpx -from mcp.server.fastmcp import FastMCP - -# Initialize FastMCP server -mcp = FastMCP("weather") - -# Constants -NWS_API_BASE = "https://api.weather.gov" -USER_AGENT = "weather-app/1.0" - -async def make_nws_request(url: str) -> dict[str, Any] | None: - """Make a request to the NWS API with proper error handling.""" - headers = { - "User-Agent": USER_AGENT, - "Accept": "application/geo+json" - } - async with httpx.AsyncClient() as client: - try: - response = await client.get(url, headers=headers, timeout=30.0) - response.raise_for_status() - return response.json() - except Exception: - return None - -def format_alert(feature: dict) -> str: - """Format an alert feature into a readable string.""" - props = feature["properties"] - return f""" -Event: {props.get('event', 'Unknown')} -Area: {props.get('areaDesc', 'Unknown')} -Severity: {props.get('severity', 'Unknown')} -Description: {props.get('description', 'No description available')} -Instructions: {props.get('instruction', 'No specific instructions provided')} -""" - -@mcp.tool() -async def get_alerts(state: str) -> str: - """Get weather alerts for a US state. - - Args: - state: Two-letter US state code (e.g. CA, NY) - """ - url = f"{NWS_API_BASE}/alerts/active/area/{state}" - data = await make_nws_request(url) - - if not data or "features" not in data: - return "Unable to fetch alerts or no alerts found." - - if not data["features"]: - return "No active alerts for this state." - - alerts = [format_alert(feature) for feature in data["features"]] - return "\n---\n".join(alerts) - -@mcp.tool() -async def get_forecast(latitude: float, longitude: float) -> str: - """Get weather forecast for a location. - - Args: - latitude: Latitude of the location - longitude: Longitude of the location - """ - # First get the forecast grid endpoint - points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" - points_data = await make_nws_request(points_url) - - if not points_data: - return "Unable to fetch forecast data for this location." - - # Get the forecast URL from the points response - forecast_url = points_data["properties"]["forecast"] - forecast_data = await make_nws_request(forecast_url) - - if not forecast_data: - return "Unable to fetch detailed forecast." - - # Format the periods into a readable forecast - periods = forecast_data["properties"]["periods"] - forecasts = [] - for period in periods[:5]: # Only show next 5 periods - forecast = f""" -{period['name']}: -Temperature: {period['temperature']}ยฐ{period['temperatureUnit']} -Wind: {period['windSpeed']} {period['windDirection']} -Forecast: {period['detailedForecast']} -""" - forecasts.append(forecast) - - return "\n---\n".join(forecasts) - -if __name__ == "__main__": - # Initialize and run the server - mcp.run(transport='stdio') - -``` - -
- -Then, still inside the `mcp-weather` folder, create a file `pyproject.toml` with content: - -``` -[project] -name = "weather" -version = "0.1.0" -description = "A simple MCP weather server" -requires-python = ">=3.10" -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.2.0", -] - -[build-system] -requires = [ "hatchling",] -build-backend = "hatchling.build" - -[project.scripts] -weather = "weather:main" - -``` - -Use poetry to install dependencies. - -```shell -poetry install -``` - -2- update the API Bridge Agent configuration. Add the following "weather" entry on the mcpServers list inside the `configs/mcp.oas.json` file: - -```json - "mcpServers": { - ... - "weather": { - "command": "poetry", - "args": [ - "run", - "python", - "/mcp-weather/weather.py" - ] - } - } -``` - -3- reload the config - -```shell -curl http://localhost:8080/tyk/apis/oas \ - --header 'x-tyk-authorization: foo' \ - --header 'Content-Type: text/plain' \ - -d@configs/mcp.oas.json - -curl http://localhost:8080/tyk/reload/group --header 'x-tyk-authorization: foo' - -curl http://localhost:8080/mcp/init -``` - -4- You can request API Bridge Agent with - -```shell -curl 'http://localhost:8080/mcp/' \ - --header 'Content-Type: application/nlq' \ - -d "give me the weather forecast in california" -``` - -and you will receive a response that looks like - -``` -Here's the weather forecast for California: - -### Today: -- **Temperature:** 66ยฐF -- **Wind:** 5 mph S -- **Forecast:** A slight chance of rain after 5 PM. Partly sunny. High near 66, with temperatures falling to around 64 in the afternoon. Chance of precipitation is 20%. New rainfall amounts less than a tenth of an inch possible. - -### Tonight: -- **Temperature:** 48ยฐF -- **Wind:** 0 to 5 mph ENE -- **Forecast:** A chance of rain. Mostly cloudy. Low around 48, with temperatures rising to around 50 overnight. Chance of precipitation is 50%. New rainfall amounts less than a tenth of an inch possible. - -### Saturday: -- **Temperature:** 60ยฐF -- **Wind:** 0 to 5 mph SSW -- **Forecast:** A chance of rain before 11 AM, then showers and thunderstorms likely between 11 AM and 5 PM, then a chance of rain. Mostly cloudy, with a high near 60. Chance of precipitation is 70%. New rainfall amounts between a tenth and quarter of an inch possible. - -### Saturday Night: -- **Temperature:** 48ยฐF -- **Wind:** 0 to 5 mph NE -- **Forecast:** A chance of rain before 5 AM. Mostly cloudy, with a low around 48. Chance of precipitation is 50%. New rainfall amounts less than a tenth of an inch possible. - -### Sunday: -- **Temperature:** 65ยฐF -- **Wind:** 0 to 5 mph SW -- **Forecast:** Partly sunny, with a high near 65. - -``` diff --git a/docs/how-to-guides/interrupts.md b/docs/how-to-guides/interrupts.md deleted file mode 100644 index 1d7ec4aa..00000000 --- a/docs/how-to-guides/interrupts.md +++ /dev/null @@ -1,957 +0,0 @@ -# Building Applications with ACP Interrupts - -This tutorial guides you through creating an agentic mail composer application with interrupts capability. Interrupts allow your agent to pause execution, prompt the user for additional information, and then resume processing. - -For more information on the Agent Connect Protocol, see [here](../syntactic/connect.md). - -## Overview - -We'll build a mail composer agent that helps users draft marketing emails. The agent follows this workflow: - -1. Engage in conversation with the user to gather email content details -2. Generate a well-structured marketing email -3. Use interrupts to ask for email format preferences -4. Deliver the final email in the requested format - -## Prerequisites - -- Python 3.9 or higher -- Poetry 2.0 or higher -- Workflow Server Manager (wfsm) -- Azure OpenAI API key and endpoint - -## Setting up the Project - -First, let's set up our project using Poetry: - -```bash -# Create a new Poetry project -poetry new --python='>=3.9,<4.0' mailcomposer-agent -cd mailcomposer-agent - -# Add all dependencies -poetry add python-dotenv langgraph langchain-openai langchain pydantic agntcy-acp - -# Install the current project -poetry install -``` - -Poetry automatically creates the project structure with a src/ directory, so we'll use that convention for our files. - -## Step 1: Creating the Basic Mail Composer Agent - -Let's start by creating the basic mail composer agent without interrupt functionality. We'll need two files: - -1. First, create the state models: - - ```python - # filepath: src/mailcomposer_agent/state.py - from enum import Enum - from typing import Optional, Annotated - - from pydantic import BaseModel, Field - import operator - - class Type(Enum): - human = 'human' - assistant = 'assistant' - ai = 'ai' - - - class Message(BaseModel): - type: Type = Field( - ..., - description='indicates the originator of the message, a human or an assistant', - ) - content: str = Field(..., description='the content of the message') - - - class ConfigSchema(BaseModel): - test: bool - - - class AgentState(BaseModel): - messages: Annotated[Optional[list[Message]], operator.add] = [] - is_completed: Optional[bool] = None - - class StatelessAgentState(BaseModel): - messages: Optional[list[Message]] = [] - is_completed: Optional[bool] = None - - - class OutputState(AgentState): - final_email: Optional[str] = Field( - default=None, - description="Final email produced by the mail composer, in html format" - ) - - class StatelessOutputState(StatelessAgentState): - final_email: Optional[str] = Field( - default=None, - description="Final email produced by the mail composer, in html format" - ) - ``` - -2. Now, let's create the logic for our mail composer agent: - - ```python - # filepath: src/mailcomposer_agent/mailcomposer.py - import os - from langchain_core.messages import BaseMessage, HumanMessage, AIMessage - from langgraph.checkpoint.memory import InMemorySaver - from langgraph.graph import StateGraph, START, END - from langchain_openai import AzureChatOpenAI - from pydantic import SecretStr - from langchain.prompts import PromptTemplate - - from .state import ( - OutputState, - AgentState, - StatelessAgentState, - StatelessOutputState, - Message, - Type as MsgType, - ) - - api_key = os.getenv("AZURE_OPENAI_API_KEY") - if not api_key: - raise ValueError("AZURE_OPENAI_API_KEY must be set as an environment variable.") - - azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") - if not azure_endpoint: - raise ValueError("AZURE_OPENAI_ENDPOINT must be set as an environment variable.") - - is_stateless = os.getenv("STATELESS", "true").lower() == "true" - - llm = AzureChatOpenAI( - api_key=SecretStr(api_key), - azure_endpoint=azure_endpoint, - model="gpt-4o", - openai_api_type="azure_openai", - api_version="2024-07-01-preview", - temperature=0, - max_retries=10, - seed=42, - ) - - # Writer and subject role prompts - MARKETING_EMAIL_PROMPT_TEMPLATE = PromptTemplate.from_template( - """ - You are a highly skilled writer and you are working for a marketing company. - Your task is to write formal and professional emails. We are building a publicity campaign and we need to send a massive number of emails to many clients. - The email must be compelling and adhere to our marketing standards. - - If you need more details to complete the email, please ask me. - Once you have all the necessary information, please create the email body. The email must be engaging and persuasive. The subject that cannot exceed 5 words (no bold). - The email should be in the following format - {{separator}} - subject - body - {{separator}} - DO NOT FORGET TO ADD THE SEPARATOR BEFORE THE SUBECT AND AFTER THE EMAIL BODY! - SHOULD NEVER HAPPEN TO HAVE THE SEPARATOR AFTER THE SUBJECT AND BEFORE THE EMAIL BODY! NEVER AFTER THE SUBJECT! - DO NOT ADD EXTRA TEXT IN THE EMAIL, LIMIT YOURSELF IN GENERATING THE EMAIL - """, - template_format="jinja2", - ) - - SEPARATOR = "**************" - - def extract_mail(messages) -> str: - for m in reversed(messages): - splits: list[str] = [] - if isinstance(m, Message): - if m.type == MsgType.human: - continue - splits = m.content.split(SEPARATOR) - if isinstance(m, dict): - if m.get("type", "") == "human": - continue - splits = m.get("content", "").split(SEPARATOR) - if len(splits) >= 3: - return splits[len(splits) - 2].strip() - elif len(splits) == 2: - return splits[1].strip() - elif len(splits) == 1: - return splits[0] - return "" - - def should_format_email(state: AgentState | StatelessAgentState): - # In the basic version, we just return END - return END - - def convert_messages(messages: list) -> list[BaseMessage]: - converted = [] - for m in messages: - if isinstance(m, Message): - mdict = m.model_dump() - else: - mdict = m - if mdict["type"] == "human": - converted.append(HumanMessage(content=mdict["content"])) - else: - converted.append(AIMessage(content=mdict["content"])) - - return converted - - # Define mail_agent function - def email_agent( - state: AgentState | StatelessAgentState, - ) -> OutputState | AgentState | StatelessOutputState | StatelessAgentState: - """This agent is a skilled writer for a marketing company, creating formal and professional emails for publicity campaigns. - It interacts with users to gather the necessary details. - Once the user approves by sending "is_completed": true, the agent outputs the finalized email in "final_email". - """ - # Check subsequent messages and handle completion - return final_output(state) if state.is_completed else generate_email(state) - - def final_output( - state: AgentState | StatelessAgentState, - ) -> OutputState | AgentState | StatelessOutputState | StatelessAgentState: - final_mail = extract_mail(state.messages) - - output_state: OutputState = OutputState( - messages=state.messages, - is_completed=state.is_completed, - final_email=final_mail, - ) - return output_state - - def generate_email( - state: AgentState | StatelessAgentState, - ) -> ( - OutputState | AgentState | StatelessOutputState | StatelessAgentState - ): # Append messages from state to initial prompt - messages = [ - Message( - type=MsgType.human, - content=MARKETING_EMAIL_PROMPT_TEMPLATE.format(separator=SEPARATOR), - ) - ] + state.messages - - # Call the LLM - ai_message = Message( - type=MsgType.ai, content=str(llm.invoke(convert_messages(messages)).content) - ) - - if is_stateless: - return {"messages": state.messages + [ai_message]} - else: - return {"messages": [ai_message]} - - # Build the graph - if is_stateless: - graph_builder = StateGraph(StatelessAgentState, output=StatelessOutputState) - else: - graph_builder = StateGraph(AgentState, output=OutputState) - - graph_builder.add_node("email_agent", email_agent) - - graph_builder.add_edge(START, "email_agent") - graph_builder.add_edge("email_agent", END) - - if is_stateless: - print("mailcomposer - running in stateless mode") - graph = graph_builder.compile() - else: - print("mailcomposer - running in stateful mode") - checkpointer = InMemorySaver() - graph = graph_builder.compile(checkpointer=checkpointer) - ``` - -!!! note - The checkpointer setup in `build_graph()` is critical for interrupts to work properly. - When using interrupts, we must run the agent in stateful mode with a checkpointer configured: - - The checkpointer preserves the graph's execution state when an interrupt occurs, - allowing it to resume from exactly where it left off after receiving user input. - Without this persistence mechanism, the graph would have no memory of what happened - before the interrupt, making it impossible to continue execution correctly. - -## Step 2: Generating the Agent Manifest - -Next, we need to generate a manifest for our agent. This will allow it to be deployed and used by other systems. Let's create a manifest generator: - -```python -# filepath: src/mailcomposer_agent/generate_manifest.py -from pathlib import Path -from pydantic import AnyUrl -from mailcomposer_agent.state import ConfigSchema, StatelessAgentState, StatelessOutputState -from agntcy_acp.manifest import ( - AgentManifest, - AgentDeployment, - DeploymentOptions, - LangGraphConfig, - EnvVar, - AgentMetadata, - AgentACPSpec, - AgentRef, - Capabilities, - SourceCodeDeployment, -) - -manifest = AgentManifest( - metadata=AgentMetadata( - ref=AgentRef(name="org.agntcy.mailcomposer", version="0.0.1"), - description="Offer a chat interface to compose an email for a marketing campaign. Final output is the email that could be used for the campaign" - ), - specs=AgentACPSpec( - input=StatelessAgentState.model_json_schema(), - output=StatelessOutputState.model_json_schema(), - config=ConfigSchema.model_json_schema(), - capabilities=Capabilities( - threads=False, - callbacks=False, - interrupts=False, # No interrupts yet - streaming=None - ), - custom_streaming_update=None, - thread_state=None, - interrupts=None # No interrupts defined yet - ), - deployment=AgentDeployment( - deployment_options=[ - DeploymentOptions( - root=SourceCodeDeployment( - type="source_code", - name="source_code_local", - url=AnyUrl("file://../"), - framework_config=LangGraphConfig( - framework_type="langgraph", - graph="mailcomposer_agent.mailcomposer:graph" - ) - ) - ) - ], - env_vars=[ - EnvVar(name="AZURE_OPENAI_API_KEY", desc="Azure key for the OpenAI service"), - EnvVar(name="AZURE_OPENAI_ENDPOINT", desc="Azure endpoint for the OpenAI service") - ], - dependencies=[] - ) -) - -output_dir = Path(__file__).parent.parent.parent / "deploy" -output_dir.mkdir(exist_ok=True) - -with open(output_dir / "mailcomposer.json", "w") as f: - f.write(manifest.model_dump_json( - exclude_unset=True, - exclude_none=True, - indent=2 - )) - -print(f"Manifest successfully generated at {output_dir / 'mailcomposer.json'}") -``` - -Now let's run the manifest generator: - -```bash -# Run the manifest generator -poetry run python -m mailcomposer_agent.generate_manifest - -# Expected output: -# Manifest successfully generated at /path/to/mailcomposer-agent/deploy/mailcomposer.json -``` - -With our agent code implemented and the manifest generated, our basic mail composer agent without interrupts is now ready to work. You can already deploy and run it to generate marketing emails through conversation. In the next step, we'll enhance it with interrupt capabilities for a more interactive experience. - -## Step 3: Adding Interrupts to the Agent - -Now, let's enhance our agent with interrupt capability. Interrupts allow our agent to pause execution, ask the user for additional information, and then resume processing. - -> ### Understanding Interrupts in LangGraph -> -> In LangGraph, implementing the `interrupt()` function **requires a stateful application** to preserve state across pauses and resumptions: -> -> - **How interrupts work**: The `interrupt()` function pauses execution at a specific point, often to await human input or external data. When invoked, LangGraph throws a `GraphInterrupt` exception, halting execution and surfacing the interrupt information to the client. -> -> - **Resuming execution**: To resume, the application must provide a `Command` object with the resume key set to the value returned by the `interrupt()` function. -> -> - **State preservation**: When an `interrupt()` occurs, the current state of the graphโ€”including variables, execution progress, and other dataโ€”is saved, allowing accurate resumption from the interruption point. -> -> - **Stateless limitations**: Stateless applications cannot retain information about previous interactions, making interrupts impractical as they would lack necessary context to resume correctly. -> -> - **Implementation considerations**: When using `interrupt()`, implement a persistence mechanism (database, in-memory store, etc.) to maintain state during interruptions and ensure seamless resumption. - - - -### Adding Interrupt Support to Our Agent - -We need to make the following additions to `mailcomposer.py`: - -1. First, add the import for interrupts at the top of the file: - - ```python - from langgraph.graph import StateGraph, START, END - from langgraph.types import interrupt # Add this import for interrupts - ``` - -2. Add the `format_email` function after the `SEPARATOR` constant: - - ```python - def format_email(state): - answer = interrupt( - Message( - type=MsgType.assistant, - content="In what format would like your email to be?", - ) - ) - answer_content = Message(**answer) - email = extract_mail(state.messages) - answer_content.content += " This is the email: " + email - state.messages = (state.messages or []) + [answer_content] - state_after_formating = generate_email(state) - - interrupt( - Message( - type=MsgType.assistant, content="The email is formatted, please confirm" - ) - ) - - state_after_formating = StatelessAgentState( - **state_after_formating, is_completed=True - ) - return final_output(state_after_formating) - ``` - -3. Update the `should_format_email` function to check for format requests: - - ```python - def should_format_email(state: AgentState | StatelessAgentState): - if state.is_completed and not is_stateless: - return "format_email" - return END - ``` - -4. Finally, update the `graph` structure to add the `format_email` node and conditional edges: - - ```python - if is_stateless: - graph_builder = StateGraph(StatelessAgentState, output=StatelessOutputState) - else: - graph_builder = StateGraph(AgentState, output=OutputState) - - graph_builder.add_node("email_agent", email_agent) - graph_builder.add_node("format_email", format_email) # Add this node - - graph_builder.add_edge(START, "email_agent") - # This node will only be added in stateful mode since langgraph requires checkpointer if any node should interrupt - graph_builder.add_conditional_edges("email_agent", should_format_email) - graph_builder.add_edge("format_email", END) - graph_builder.add_edge("email_agent", END) - - if is_stateless: - print("mailcomposer - running in stateless mode") - graph = graph_builder.compile() - else: - print("mailcomposer - running in stateful mode") - checkpointer = InMemorySaver() - graph = graph_builder.compile(checkpointer=checkpointer) - ``` - -The additions we've made implement a formatting feature that: - -1. Interrupts the normal flow to ask for formatting preferences -2. Processes the user's response to format the email accordingly -3. Interrupts again to confirm the formatting -4. Completes the email generation process with the requested formatting - -## Step 4: Updating the Manifest for Interrupts - -Now we need to **update our generated manifest JSON file to include interrupt support**. Open the generated `mailcomposer.json` and make the following key changes: - -1. In the capabilities section, change `interrupts` from `false` to `true`: - - ```json - "capabilities": { - "threads": false, - "interrupts": true, // Change from false to true - "callbacks": false - } - ``` - -2. Add the interrupts array in the `specs` section: - - ```json - "interrupts": [ - { - "interrupt_type": "format_email", - "interrupt_payload": { - "$defs": { - "Message": { - "properties": { - "type": { - "$ref": "#/$defs/Type", - "description": "indicates the originator of the message, a human or an assistant" - }, - "content": { - "description": "the content of the message", - "title": "Content", - "type": "string" - } - }, - "required": ["type", "content"], - "title": "Message", - "type": "object" - }, - "Type": { - "enum": ["human", "assistant", "ai"], - "title": "Type", - "type": "string" - } - } - }, - "resume_payload": { - "$defs": { - "Message": { - "properties": { - "type": { - "$ref": "#/$defs/Type", - "description": "indicates the originator of the message, a human or an assistant" - }, - "content": { - "description": "the content of the message", - "title": "Content", - "type": "string" - } - }, - "required": ["type", "content"], - "title": "Message", - "type": "object" - }, - "Type": { - "enum": ["human", "assistant", "ai"], - "title": "Type", - "type": "string" - } - } - } - } - ] - ``` - -These changes inform the ACP infrastructure that our agent uses interrupts and specify the format and structure of the interrupt payloads. - -## Step 5: Creating the Test Client - -To test our Mail Composer Agent with interrupts, we'll create a simple client script that can interact with the agent directly: - -```python -# filepath: src/mailcomposer_agent/main.py -import os -from dotenv import load_dotenv, find_dotenv - -from mailcomposer_agent.mailcomposer import graph -from mailcomposer_agent.state import Message, OutputState, Type as MsgType -from langgraph.types import Command - -def main(): - load_dotenv(dotenv_path=find_dotenv(usecwd=True)) - - is_stateless = os.getenv("STATELESS", "true").lower() == "true" - print(f"Running with STATELESS={is_stateless}") - - output = OutputState(messages=[], final_email=None) - is_completed = False - - thread = {"configurable": {"thread_id": "foo"}} - - while True: - if output.messages and len(output.messages) > 0: - m = output.messages[-1] - print(f"[Assistant] \t\t>>> {m.content}") - if output.final_email: - break - message = input("YOU [Type OK when you are happy with the email proposed] >>> ") - - if is_stateless: - nextinput = output.messages + [Message(content=message, type=MsgType.human)] - else: - nextinput = [Message(content=message, type=MsgType.human)] - - if message == "OK": - is_completed = True - - out = graph.invoke( - {"messages": nextinput, "is_completed": is_completed}, - thread, - ) - - try: - curr_state = graph.get_state(thread) - print(f"Current state has {len(curr_state.tasks)} tasks") - - # Check if graph is interrupted by mailcomposer - while len(curr_state.tasks) and len(curr_state.tasks[0].interrupts) > 0: - print(f"Interrupt detected with {len(curr_state.tasks[0].interrupts)} interrupts") - message = input("YOU [INTERRUPT: Type format preference] >>> ") - - command = Command( - resume=Message( - content=message, type=MsgType.human - ).model_dump() - ) - - # Send a signal to the graph to resume execution - graph.invoke(command, config=thread) - curr_state = graph.get_state(thread) - - except ValueError as e: - print(f"Error getting state: {e}") - # If we get No checkpointer set error - if "No checkpointer set" in str(e): - print("Make sure STATELESS=false in your .env file and rebuild the graph") - break - - output: OutputState = OutputState.model_validate(out) - - print("Final email is:") - print(output.final_email) - -if __name__ == "__main__": - main() -``` - -### Running the Agent Locally - -You can run the agent locally using the main.py script: - -```bash -# Create a .env file with your Azure OpenAI credentials -echo "AZURE_OPENAI_API_KEY=your_api_key" > .env -echo "AZURE_OPENAI_ENDPOINT=your_endpoint" >> .env -echo "STATELESS=false" >> .env - -# Run the agent -poetry run python -m mailcomposer_agent.main -``` - -## Step 6: Running with Workflow Server Manager - -Now that we've created our mail composer agent with interrupt support, we can deploy and run it using the [Workflow Server Manager](../agws/workflow-server-manager.md). - -### Setting up the Environment - -First, download the Workflow Server Manager for your platform: - -```bash -# For macOS with Apple Silicon -curl -L https://github.com/agntcy/workflow-srv-mgr/releases/download/v0.2.2/wfsm0.2.2_darwin_arm64.tar.gz -o wfsm.tar.gz -tar -xzf wfsm.tar.gz -chmod +x wfsm -``` - -!!! note - For other platforms, download the appropriate binary from the [releases page](https://github.com/agntcy/workflow-srv-mgr/releases) - -Next, create a configuration file for our mail composer agent: - -```yaml -# filepath: mailcomposer-agent/mailcomposer_config.yaml -config: - org.agntcy.mailcomposer: - port: 12345 - apiKey: a1b2c3d4-e5f6-a7b8-c9d0-e1f2a3b4c5d6 - id: a1a1a1a1-b2b2-c3c3-d4d4-e5e5e5e5e5e5 - envVars: - STATELESS: "false" - AZURE_OPENAI_API_KEY: "your_azure_openai_api_key" - AZURE_OPENAI_ENDPOINT: "your_azure_openai_endpoint" -``` - -Replace the placeholders with your actual Azure OpenAI credentials. - -### Deploying the Agent - -Now, deploy the mail composer agent using wfsm: - -```bash -./wfsm deploy -m ./deploy/mailcomposer.json -c ./mailcomposer_config.yaml -b ghcr.io/agntcy/acp/wfsrv:v0.2.7 --dryRun=false -``` - -## Step 7: Testing Interrupts via Workflow Server - -After deploying your agent, you can test the interrupt functionality by directly calling the workflow server API. This allows you to verify that both interrupts work correctly. - -For this test, we'll use curl commands to simulate a conversation that includes interrupts. - -```bash -curl -X 'POST' \ - 'http://127.0.0.1:52384/runs/wait' \ - -H 'accept: application/json' \ - -H 'x-api-key: YOUR_API_KEY' \ - -H 'Content-Type: application/json' \ - -d '{ - "agent_id": "YOUR_AGENT_ID", - "input": { - "is_completed": true, - "messages": [ - { - "type": "human", - "content": "Email about wooden spoon be inventive on regarding email body" - }, - { - "type": "ai", - "content": "**************\nDiscover Our Wooden Spoons\n**************\nDear [Client'\''s Name],\n\nI hope this message finds you well. We are excited to introduce our latest collection of handcrafted wooden spoons, designed to bring elegance and functionality to your kitchen. Each spoon is meticulously crafted from sustainably sourced wood, ensuring durability and a unique touch to your culinary experience.\n\nOur wooden spoons are not only a practical tool but also a beautiful addition to your kitchen decor. Whether you'\''re stirring, serving, or tasting, these spoons offer a comfortable grip and a smooth finish that enhances your cooking process.\n\nWe invite you to explore our collection and discover the perfect wooden spoon that suits your style and needs. As a valued client, you can enjoy an exclusive discount on your first purchase. Simply use the code WOODEN10 at checkout.\n\nThank you for considering our products. We look forward to serving you with quality and craftsmanship.\n\nWarm regards,\n\n[Your Name]\n[Your Position]\n[Company Name]\n[Contact Information]\n**************" - }, - { - "type": "human", - "content": "OK" - } - ] - }, - "metadata": {}, - "config": { - "tags": [ - "string" - ], - "recursion_limit": 10, - "configurable": { - "test": true, - "thread_id": "1" - } - }, - "stream_mode": null, - "on_disconnect": "cancel", - "multitask_strategy": "reject", - "after_seconds": 0, - "on_completion": "delete" -}' -``` - -The agent should respond with "interrupted" status and ask what format the email should be: - -```bash -{ - "run": { - "run_id": "YOUR_RUN_ID", - "thread_id": "YOUR_THREAD_ID", - "agent_id": "YOUR_AGENT_ID", - "created_at": "2025-05-23T12:36:24.093877", - "updated_at": "2025-05-23T12:36:24.106818", - "status": "interrupted", - "creation": { - "agent_id": "YOUR_AGENT_ID", - "input": { - "is_completed": true, - "messages": [ - { - "type": "human", - "content": "Email about wooden spoon be inventive on regarding email body" - }, - { - "type": "ai", - "content": "**************\nDiscover Our Wooden Spoons\n**************\nDear [Client's Name],\n\nI hope this message finds you well. We are excited to introduce our latest collection of handcrafted wooden spoons, designed to bring elegance and functionality to your kitchen. Each spoon is meticulously crafted from sustainably sourced wood, ensuring durability and a unique touch to your culinary experience.\n\nOur wooden spoons are not only a practical tool but also a beautiful addition to your kitchen decor. Whether you're stirring, serving, or tasting, these spoons offer a comfortable grip and a smooth finish that enhances your cooking process.\n\nWe invite you to explore our collection and discover the perfect wooden spoon that suits your style and needs. As a valued client, you can enjoy an exclusive discount on your first purchase. Simply use the code WOODEN10 at checkout.\n\nThank you for considering our products. We look forward to serving you with quality and craftsmanship.\n\nWarm regards,\n\n[Your Name]\n[Your Position]\n[Company Name]\n[Contact Information]\n**************" - }, - { - "type": "human", - "content": "OK" - } - ] - }, - "metadata": {}, - "config": { - "tags": [ - "string" - ], - "recursion_limit": 10, - "configurable": { - "test": true, - "thread_id": "1" - } - }, - "webhook": null, - "stream_mode": null, - "on_disconnect": "cancel", - "multitask_strategy": "reject", - "after_seconds": null, - "on_completion": "delete" - } - }, - "output": { - "type": "interrupt", - "interrupt": { - "format_email": { - "type": "assistant", - "content": "In what format would like your email to be?" - } - } - } -} -``` - -Next, respond to the first interrupt with a formatting preference: - -```bash -curl -X 'POST' \ - 'http://127.0.0.1:52384/runs/YOUR_RUN_ID' \ - -H 'accept: application/json' \ - -H 'x-api-key: YOUR_API_KEY' \ - -H 'Content-Type: application/json' \ - -d ' { - "type": "human", - "content": "Format my email in html" - }' -``` - -The response shows that the request is being processed: - -```bash -{ - "run_id": "YOUR_RUN_ID", - "thread_id": "YOUR_THREAD_ID", - "agent_id": "YOUR_AGENT_ID", - "created_at": "2025-05-23T12:36:24.093877", - "updated_at": "2025-05-23T12:43:09.484371", - "status": "pending", - "creation": { - "agent_id": "YOUR_AGENT_ID", - "input": { - "is_completed": true, - "messages": [ - { - "type": "human", - "content": "Email about wooden spoon be inventive on regarding email body" - }, - { - "type": "ai", - "content": "**************\nDiscover Our Wooden Spoons\n**************\nDear [Client's Name],\n\nI hope this message finds you well. We are excited to introduce our latest collection of handcrafted wooden spoons, designed to bring elegance and functionality to your kitchen. Each spoon is meticulously crafted from sustainably sourced wood, ensuring durability and a unique touch to your culinary experience.\n\nOur wooden spoons are not only a practical tool but also a beautiful addition to your kitchen decor. Whether you're stirring, serving, or tasting, these spoons offer a comfortable grip and a smooth finish that enhances your cooking process.\n\nWe invite you to explore our collection and discover the perfect wooden spoon that suits your style and needs. As a valued client, you can enjoy an exclusive discount on your first purchase. Simply use the code WOODEN10 at checkout.\n\nThank you for considering our products. We look forward to serving you with quality and craftsmanship.\n\nWarm regards,\n\n[Your Name]\n[Your Position]\n[Company Name]\n[Contact Information]\n**************" - }, - { - "type": "human", - "content": "OK" - } - ] - }, - "metadata": {}, - "config": { - "tags": [ - "string" - ], - "recursion_limit": 10, - "configurable": { - "test": true, - "thread_id": "1" - } - }, - "webhook": null, - "stream_mode": null, - "on_disconnect": "cancel", - "multitask_strategy": "reject", - "after_seconds": null, - "on_completion": "delete" - } -} -``` - -Then respond to the second interrupt that confirms the formatting: - -```bash -curl -X 'POST' \ - 'http://127.0.0.1:52384/runs/YOUR_RUN_ID' \ - -H 'accept: application/json' \ - -H 'x-api-key: YOUR_API_KEY' \ - -H 'Content-Type: application/json' \ - -d ' { - "type": "human", - "content": "OK" - }' -``` - -Finally, retrieve the formatted email: - -```bash -curl -X 'GET' \ - 'http://127.0.0.1:52384/runs/YOUR_RUN_ID/wait' \ - -H 'accept: application/json' \ - -H 'x-api-key: YOUR_API_KEY' -``` - -The response will include the HTML-formatted email: - -```bash -{ - "run": { - "run_id": "YOUR_RUN_ID", - "thread_id": "YOUR_THREAD_ID", - "agent_id": "YOUR_AGENT_ID", - "created_at": "2025-05-23T12:36:24.093877", - "updated_at": "2025-05-23T12:49:58.635417", - "status": "success", - "creation": { - "agent_id": "YOUR_AGENT_ID", - "input": { - "is_completed": true, - "messages": [ - { - "type": "human", - "content": "Email about wooden spoon be inventive on regarding email body" - }, - { - "type": "ai", - "content": "**************\nDiscover Our Wooden Spoons\n**************\nDear [Client's Name],\n\nI hope this message finds you well. We are excited to introduce our latest collection of handcrafted wooden spoons, designed to bring elegance and functionality to your kitchen. Each spoon is meticulously crafted from sustainably sourced wood, ensuring durability and a unique touch to your culinary experience.\n\nOur wooden spoons are not only a practical tool but also a beautiful addition to your kitchen decor. Whether you're stirring, serving, or tasting, these spoons offer a comfortable grip and a smooth finish that enhances your cooking process.\n\nWe invite you to explore our collection and discover the perfect wooden spoon that suits your style and needs. As a valued client, you can enjoy an exclusive discount on your first purchase. Simply use the code WOODEN10 at checkout.\n\nThank you for considering our products. We look forward to serving you with quality and craftsmanship.\n\nWarm regards,\n\n[Your Name]\n[Your Position]\n[Company Name]\n[Contact Information]\n**************" - }, - { - "type": "human", - "content": "OK" - } - ] - }, - "metadata": {}, - "config": { - "tags": [ - "string" - ], - "recursion_limit": 10, - "configurable": { - "test": true, - "thread_id": "1" - } - }, - "webhook": null, - "stream_mode": null, - "on_disconnect": "cancel", - "multitask_strategy": "reject", - "after_seconds": null, - "on_completion": "delete" - } - }, - "output": { - "type": "result", - "values": { - "messages": [ - { - "type": "ai", - "content": "Sure, I can help you format an email in HTML. Here's an example based on the previous wooden spoon email:\n\n```html\n\n\n\n \n \n Discover Our Wooden Spoons\n\n\n \n \n \n \n
\n \n \n \n \n
\n

Discover Our Wooden Spoons

\n

Dear [Client's Name],

\n

I hope this message finds you well. We are excited to introduce our latest collection of handcrafted wooden spoons, designed to bring elegance and functionality to your kitchen. Each spoon is meticulously crafted from sustainably sourced wood, ensuring durability and a unique touch to your culinary experience.

\n

Our wooden spoons are not only a practical tool but also a beautiful addition to your kitchen decor. Whether you're stirring, serving, or tasting, these spoons offer a comfortable grip and a smooth finish that enhances your cooking process.

\n

We invite you to explore our collection and discover the perfect wooden spoon that suits your style and needs. As a valued client, you can enjoy an exclusive discount on your first purchase. Simply use the code WOODEN10 at checkout.

\n

Thank you for considering our products. We look forward to serving you with quality and craftsmanship.

\n

Warm regards,

\n

[Your Name]
\n [Your Position]
\n [Company Name]
\n [Contact Information]

\n
\n
\n\n\n```\n\nThis HTML email template includes basic styling and structure to ensure it looks professional and is easy to read. You can customize the content and styles as needed to fit your specific requirements." - } - ], - "is_completed": true, - "final_email": "Sure, I can help you format an email in HTML. Here's an example based on the previous wooden spoon email:\n\n```html\n\n\n\n \n \n Discover Our Wooden Spoons\n\n\n \n \n \n \n
\n \n \n \n \n
\n

Discover Our Wooden Spoons

\n

Dear [Client's Name],

\n

I hope this message finds you well. We are excited to introduce our latest collection of handcrafted wooden spoons, designed to bring elegance and functionality to your kitchen. Each spoon is meticulously crafted from sustainably sourced wood, ensuring durability and a unique touch to your culinary experience.

\n

Our wooden spoons are not only a practical tool but also a beautiful addition to your kitchen decor. Whether you're stirring, serving, or tasting, these spoons offer a comfortable grip and a smooth finish that enhances your cooking process.

\n

We invite you to explore our collection and discover the perfect wooden spoon that suits your style and needs. As a valued client, you can enjoy an exclusive discount on your first purchase. Simply use the code WOODEN10 at checkout.

\n

Thank you for considering our products. We look forward to serving you with quality and craftsmanship.

\n

Warm regards,

\n

[Your Name]
\n [Your Position]
\n [Company Name]
\n [Contact Information]

\n
\n
\n\n\n```\n\nThis HTML email template includes basic styling and structure to ensure it looks professional and is easy to read. You can customize the content and styles as needed to fit your specific requirements." - } - } -} -``` - -## Conclusion - -In this tutorial, you've built a Mail Composer Agent with interrupt capabilities using LangGraph, running it on the Workflow Server and interacting with it via ACP. - -The interrupt feature provided a more interactive experience thanks to the introduction of the human in the loop, allowing the agent to gather additional information during execution and respond dynamically to user needs. - -The full working agent code is available in the [agntcy/agentic-apps repository](https://github.com/agntcy/agentic-apps/tree/main/mailcomposer), and you can run it through the Workflow Server exactly as described in this tutorial. diff --git a/docs/how-to-guides/mas-creation-tutorial/_static/marketing_campaign_final.png b/docs/how-to-guides/mas-creation-tutorial/_static/marketing_campaign_final.png deleted file mode 100644 index e48d6bbe..00000000 Binary files a/docs/how-to-guides/mas-creation-tutorial/_static/marketing_campaign_final.png and /dev/null differ diff --git a/docs/how-to-guides/mas-creation-tutorial/_static/marketing_campaign_skeleton.png b/docs/how-to-guides/mas-creation-tutorial/_static/marketing_campaign_skeleton.png deleted file mode 100644 index eda1e433..00000000 Binary files a/docs/how-to-guides/mas-creation-tutorial/_static/marketing_campaign_skeleton.png and /dev/null differ diff --git a/docs/how-to-guides/mas-creation-tutorial/mas-tutorial.md b/docs/how-to-guides/mas-creation-tutorial/mas-tutorial.md deleted file mode 100644 index b250cf21..00000000 --- a/docs/how-to-guides/mas-creation-tutorial/mas-tutorial.md +++ /dev/null @@ -1,1038 +0,0 @@ -# Getting Started: Build Your First Multi-Agent Software - -This tutorial guides you through the process of building a distributed multi-agent application using [LangGraph](https://www.langchain.com/langgraph) and leveraging [Agent Connect Protocol (ACP)](../../syntactic/agntcy_acp_sdk.md) and other **AGNTCY** components and tools. - -The sample app used for this tutorial is a **Marketing Campaign Manager** agent. A "pre-cooked" version of this application is available [here](https://github.com/agntcy/agentic-apps/tree/main/marketing-campaign). - -For this tutorial we are using LangGraph, but other frameworks can also be used. - -## Overview - -The **Marketing Campaign Manager** we are building implements a LangGraph graph which: - -* Interacts with a user to gather the description of the email marketing campaign to launch. -* Uses an already existing [Mail Composer Agent](https://github.com/agntcy/agentic-apps/tree/main/mailcomposer), capable of composing emails for the marketing campaign. This agent is written using LangGraph, it provides an Agent Manifest which allows to deploy it through the Agent Workflow Server and be consumed through ACP. -* Uses an already existing [Email Reviewer Agent](https://github.com/agntcy/agentic-apps/tree/main/email_reviewer) capable of reviewing an email and adjust it for a specific target audience. This agent is written using [LlamaIndex](https://www.llamaindex.ai/framework) and similarly to the previous agent, it provides a Agent Manifest which allows to deploy it through the [Agent Workflow Server](../../agws/workflow-server.md) and be consumed through ACP. -* Uses [Twilio Sendgrid](https://sendgrid.com/) API to deliver the marketing campaign email to the intended recipient. We will consume this API leveraging the capabilities of the [API Bridge Agent](../../syntactic/api_bridge_agent.md). - -This tutorial is structured in the following steps: - -1. [Create a Basic LangGraph Skeleton Application](#step-1-create-a-basic-langgraph-skeleton-application): Set up a LangGraph application structure to serve as the base of your multi-agent software. - -2. [Generate Models from Agent Manifests](#step-2-generate-models-from-agent-manifests): Use agent manifests to generate models defining data structures and interfaces. - -3. [State Definition](#step-3-state-definition): Create states to manage the flow of your multi-agent software (MAS). - -4. [Multi-Agent Application Development](#step-4-multi-agent-application-development): Use ACP SDK to integrate ACP nodes into your LangGraph application. - -5. [API Bridge Integration](#step-5-api-bridge-integration): Connect natural language outputs to structured API requests. - -6. [I/O Mapper Integration](#step-6-io-mapper-integration): Adapt inputs and outputs between different agents such that they match in format and meaning. - -7. [Generate Application Manifest](#step-7-generate-application-manifest): Create a manifest file to define how your application can be deployed and consumed via ACP. - -8. [Review Resulting Application](#step-8-review-resulting-application): Analyze the complete workflow and how all components interact with one another. - -9. [Execute Application through Workflow Server Manager](#step-9-execute-application-through-workflow-server-manager): Deploy and test the multi-agent system using Workflow Server Manager. - -## Prerequisites - -* A working installation of [Python](https://www.python.org/) 3.9 or higher -* [poetry](https://pypi.org/project/poetry/) v2 or greater -* [curl](https://curl.se/) - -## Step 1: Create a Basic LangGraph Skeleton Application - -Let's start by setting up our project environment. You can either use pip to install the required packages or Poetry for dependency management. - -### Setting up the project - -First, create a new project using Poetry: - -```bash -# Create a new Poetry project -poetry new --python='>=3.9,<4.0' marketing-campaign -cd marketing-campaign -``` - -At this point, it's recommended to open the project folder in your favorite IDE for a better development experience. - -Install the dependencies: - -```bash -# Add all dependencies -poetry add python-dotenv langgraph langchain-openai langchain "agntcy-acp[iomapper-langgraph]" - -# Install the current project (marketing-campaign) and dependencies -poetry install -``` - -### Building a Simple LangGraph Skeleton - -Now let's begin by setting up a **simple LangGraph** skeleton application with the following nodes: - -*Start, Mail Composer, Email Reviewer, Send Mail, and End* - -![Skeleton LangGraph Application](./_static/marketing_campaign_skeleton.png) - -This setup is a basic framework with **placeholders for each task** in the workflow. It sets the stage for **transforming** these nodes into remote **ACP nodes**, allowing the interaction with **real remote agents**. - -The ultimate goal of this application is to compose and review emails that will be sent to a mail recipient. Each node represents a task to be performed in this process, from composing the email to reviewing it, and finally sending it. - -### Skeleton Code Example - -To create the initial structure of our application (skeleton), we need to create a Python file that defines our LangGraph application with placeholder nodes. This serves as the foundation that we'll enhance later with ACP integration. - -Let's create a file named `app.py` in the `src/marketing_campaign/` directory with the following content: - -```python -# app.py -from langgraph.graph import StateGraph, START, END -from langgraph.graph.state import CompiledStateGraph -from pydantic import BaseModel, Field -from typing import Optional, List - -# Define the overall state with placeholders for future use -class OverallState(BaseModel): - messages: List[str] = Field([], description="Chat messages") - has_composer_completed: Optional[bool] = Field(None, description="Flag indicating if the mail composer has successfully completed its task") - mailcomposer_output: Optional[str] = Field(None, description="Output from Mail Composer") - email_reviewer_output: Optional[str] = Field(None, description="Output from Email Reviewer") - sendgrid_result: Optional[str] = Field(None, description="Result from SendGrid API call") - -def mail_composer(state: OverallState) -> OverallState: - # Placeholder logic for composing mail - print("Composing mail...") - state.mailcomposer_output = "Draft email content" - state.has_composer_completed = True - return state - -def email_reviewer(state: OverallState) -> OverallState: - # Placeholder logic for reviewing email - print("Reviewing email...") - state.email_reviewer_output = "Reviewed email content" - return state - -def send_mail(state: OverallState) -> OverallState: - # Placeholder logic for sending email - print("Sending email...") - state.sendgrid_result = "Email sent successfully" - return state - -# Build the state graph with placeholder nodes -def build_app_graph() -> CompiledStateGraph: - sg = StateGraph(OverallState) - - # Add placeholder nodes - sg.add_node(mail_composer) - sg.add_node(email_reviewer) - sg.add_node(send_mail) - - # Define the flow of the graph - sg.add_edge(START, mail_composer.__name__) - sg.add_edge(mail_composer.__name__, email_reviewer.__name__) - sg.add_edge(email_reviewer.__name__, send_mail.__name__) - sg.add_edge(send_mail.__name__, END) - - graph = sg.compile() - print("Graph compiled successfully.") - return graph - -# Compile and skeleton graph -graph = build_app_graph() - -``` - -Let's run our code to make sure everything works as expected: - -```bash -poetry run python src/marketing_campaign/app.py -``` - -You should see the output: -``` -"Graph compiled successfully." -``` - -## Step 2: Generate Models from Agent Manifests - -In this step, you will **generate** models based on the agent manifests to define the **input, output and config schemas** for each agent involved in MAS. The models are created using the `acp generate-agent-models` cli command, which reads the agent manifest files and produces Python files that encapsulate the agent's data structures and interfaces necessary for integration. - -> **What is an Agent Manifest?**\ -> An Agent Manifest is a detailed document outlining an agent's capabilities, deployment methods, data structure specifications and dependencies on other agents. It provides **essential information** for ensuring agents can communicate and work together within the **Agent Connect Protocol** and **Workflow Server ecosystem**. [Learn more](../../manifest/manifest.md) - -### Schema and Type Generation - -We will use two agents whose manifests - -* [Mail Composer Manifest](https://github.com/agntcy/agentic-apps/tree/main/mailcomposer/deploy/mailcomposer.json) -* [Email Reviewer Manifest](https://github.com/agntcy/agentic-apps/tree/main/email_reviewer/deploy/email_reviewer.json) - -are provided within the [Agentic Apps](https://github.com/agntcy/agentic-apps) repository. To proceed, let's download the manifest files: - -```bash -# Create a manifests directory to store the agent manifests -mkdir -p manifests - -# Download Mail Composer Manifest -curl -o manifests/mailcomposer.json https://raw.githubusercontent.com/agntcy/agentic-apps/refs/heads/main/mailcomposer/deploy/mailcomposer.json - -# Download Email Reviewer Manifest -curl -o manifests/email_reviewer.json https://raw.githubusercontent.com/agntcy/agentic-apps/refs/heads/main/email_reviewer/deploy/email_reviewer.json -``` - -Now, we can generate the models using the `acp` command-line tool that was installed as part of our dependencies: - -```bash -# Generate models for Mail Composer -poetry run acp generate-agent-models manifests/mailcomposer.json --output-dir ./src/marketing_campaign --model-file-name mailcomposer.py - -# Generate models for Email Reviewer -poetry run acp generate-agent-models manifests/email_reviewer.json --output-dir ./src/marketing_campaign --model-file-name email_reviewer.py -``` - -These commands create the necessary Python files containing the Pydantic models for interacting with these agents. - -**Model File Structure:** - -* **Pydantic Models**: Each file includes Pydantic models that represent the **configuration**, **input**, and **output** schemas, enforcing type validation. -* **Input, Output and Config Schemas**: These schemas handle incoming and outgoing data and the configuration of the agent. - -## Step 3: State Definition - -State management is fundamental to **track progress and outcomes** so that each agent can interact effectively with others following the right workflow. In this step, we will define the states necessary to manage the flow of your multi-agent software. - -### Understanding State in Multi-Agent Systems - -State in multi-agent systems refers to the structured data that represents the **current status of the application**. It includes information about the inputs, outputs, and **intermediate results** of each agent's operations. Effective state management allows for the **coordination and synchronization of agent activities**. - -### State Definition in the Marketing Campaign Example - -Create a file named `state.py` in the `src/marketing_campaign/` directory that will hold state definitions for the MAS: - -```python -# src/marketing_campaign/state.py -from pydantic import BaseModel, Field -from typing import List, Optional -from marketing_campaign import mailcomposer -from marketing_campaign import email_reviewer - -class ConfigModel(BaseModel): - recipient_email_address: str = Field(..., description="Email address of the email recipient") - sender_email_address: str = Field(..., description="Email address of the email sender") - target_audience: email_reviewer.TargetAudience = Field(..., description="Target audience for the marketing campaign") - -class MailComposerState(BaseModel): - input: Optional[mailcomposer.InputSchema] = None - output: Optional[mailcomposer.OutputSchema] = None - -class MailReviewerState(BaseModel): - input: Optional[email_reviewer.InputSchema] = None - output: Optional[email_reviewer.OutputSchema] = None - -class OverallState(BaseModel): - messages: List[mailcomposer.Message] = Field([], description="Chat messages") - operation_logs: List[str] = Field([], - description="An array containing all the operations performed and their result. Each operation is appended to this array with a timestamp.", - examples=[["Mar 15 18:10:39 Operation performed: email sent Result: OK", - "Mar 19 18:13:39 Operation X failed"]]) - - has_composer_completed: Optional[bool] = Field(None, description="Flag indicating if the mail composer has successfully completed its task") - has_reviewer_completed: Optional[bool] = None - has_sender_completed: Optional[bool] = None - mailcomposer_state: Optional[MailComposerState] = None - email_reviewer_state: Optional[MailReviewerState] = None - target_audience: Optional[email_reviewer.TargetAudience] = None -``` - -The `ConfigModel` defines the configuration parameters for the Marketing Campaign application: -* `recipient_email_address`: Specifies who will receive the email (target recipient) -* `sender_email_address`: Defines the email address that will appear in the "From" field -* `target_audience`: Provides details about the audience the email is intended for, allowing the Email Reviewer to optimize content appropriately - -After creating the state file, replace the imports in your `app.py` file with: - -```python -# Replace all imports at the top of src/marketing_campaign/app.py with: -from langgraph.graph import StateGraph, START, END -from langgraph.graph.state import CompiledStateGraph -from marketing_campaign.state import OverallState - -# IMPORTANT: Remove the -# class OverallState(BaseModel): -# definition. Rest of your code remains the same -# ... -``` - -> **Important**: When updating your application, make sure to remove the placeholder `OverallState` class defined in [Step 1](#step-1-create-a-basic-langgraph-skeleton-application) and import the state classes from your new `state.py` file. This ensures your application uses the proper state definitions that incorporate the agent models generated from the manifests in [Step 2](#step-2-generate-models-from-agent-manifests). - -With this state definition in place, your application now has a structured approach for managing the flow of data between agents: - -* The `OverallState` class captures the complete state of the application -* The individual component states (`MailComposerState`, `MailReviewerState`) handle the specific data requirements for each agent. - -The next step involves transforming our placeholder nodes into actual ACP nodes for remote agent integration. - -## Step 4: Multi-Agent Application Development - -Now, let's enhance the skeleton setup by **transforming** LangGraph nodes **into ACP nodes** using `agntcy_acp` **sdk**. ACP nodes allow network communication between agents by using the **Agent Connect Protocol (ACP)**. -This enables remote invocation, configuration, and output retrieval with the goal of allowing heterogeneous and distributed agents to interoperate. - -> **Why Use ACP?** -> 1. **Remote Execution**: ACP nodes run on a Agent Workflow Server, making it possible to execute tasks remotely. -> 2. **Technology Independence**: ACP allows agents to be implemented in various technologies, such as LangGraph, LlamaIndex, etc., without compatibility issues. -> 3. **Interoperability**: ACP ensures that agents can communicate and work together, regardless of the underlying technology, by adhering to a standardized protocol. -> [Learn more about ACP](../../syntactic/agntcy_acp_sdk.md) - -### Add Mail Composer and Email Reviewer ACP Nodes - -To integrate the Mail Composer and Email Reviewer as ACP nodes, update the `src/marketing_campaign/app.py` file by adding the following imports at the top of the file: - -```python -# Add these imports at the top of src/marketing_campaign/app.py -import os -from marketing_campaign import mailcomposer -from marketing_campaign import email_reviewer -from agntcy_acp import ApiClientConfiguration -from agntcy_acp.langgraph.acp_node import ACPNode -``` - -Then, add the client configuration for the remote agents below your imports: - -```python -# Below your imports, fill in client configuration for the remote agents -MAILCOMPOSER_AGENT_ID = os.environ.get("MAILCOMPOSER_ID", "") -EMAIL_REVIEWER_AGENT_ID = os.environ.get("EMAIL_REVIEWER_ID", "") -mailcomposer_client_config = ApiClientConfiguration.fromEnvPrefix("MAILCOMPOSER_") -email_reviewer_client_config = ApiClientConfiguration.fromEnvPrefix("EMAIL_REVIEWER_") -``` - -Next, define the ACP nodes to **replace** our placeholder functions: - -```python -# Mail Composer ACP Node -acp_mailcomposer = ACPNode( - name="mailcomposer", - agent_id=MAILCOMPOSER_AGENT_ID, - client_config=mailcomposer_client_config, - input_path="mailcomposer_state.input", - input_type=mailcomposer.InputSchema, - output_path="mailcomposer_state.output", - output_type=mailcomposer.OutputSchema, -) - -# Email Reviewer ACP Node -acp_email_reviewer = ACPNode( - name="email_reviewer", - agent_id=EMAIL_REVIEWER_AGENT_ID, - client_config=email_reviewer_client_config, - input_path="email_reviewer_state.input", - input_type=email_reviewer.InputSchema, - output_path="email_reviewer_state.output", - output_type=email_reviewer.OutputSchema, -) -``` - -> **Note**: The `_path` fields indicate where to find the input and output in the `OverallState`, while the `_type` fields specify the type of the input and output. - -Finally, update the `build_app_graph` function to use these ACP nodes instead of the placeholder functions: - -```python -# Update your build_app_graph function -def build_app_graph() -> CompiledStateGraph: - sg = StateGraph(OverallState) - - # Replace placeholder nodes with ACP nodes - sg.add_node(acp_mailcomposer) - sg.add_node(acp_email_reviewer) - sg.add_node(send_mail) # We'll replace this with an API Bridge Agent node in Step 5 - - # Define the flow of the graph - sg.add_edge(START, acp_mailcomposer.get_name()) - sg.add_edge(acp_mailcomposer.get_name(), acp_email_reviewer.get_name()) - sg.add_edge(acp_email_reviewer.get_name(), send_mail.__name__) - sg.add_edge(send_mail.__name__, END) - - graph = sg.compile() - print("Graph compiled successfully.") - return graph -``` - -## Step 5: API Bridge Integration - -The API Bridge **converts natural language outputs into structured API requests**. The input to the API Bridge is in natural language, but APIs like [SendGrid APIs](https://github.com/twilio/sendgrid-oai/blob/main/spec/json/tsg_mail_v3.json) require specifically structured formats. The API Bridge ensures that the **correct endpoint and request format** are used. - -For more detailed information about the API Bridge Agent implementation and configuration, please refer to the [API Bridge documentation](../../syntactic/api_bridge_agent.md). - -### Add SendGrid API Bridge Node - -To integrate the SendGrid API Bridge into our `src/marketing_campaign/app.py` file, add the following imports at the top of your file (along with the previous imports): - -```python -# Add these imports at the top of src/marketing_campaign/app.py -from agntcy_acp.langgraph.api_bridge import APIBridgeAgentNode -``` - -Then add the SendGrid configuration below the existing agent configurations, replacing the placeholder `send_mail` function: - -```python -# Remove the placeholder send_mail function in src/marketing_campaign/app.py -# and replace it with this APIBridgeAgentNode: - -# Instantiate APIBridge Agent Node -SENDGRID_HOST = os.environ.get("SENDGRID_HOST", "http://localhost:8080") -sendgrid_api_key = os.environ.get("SENDGRID_API_KEY", None) -if sendgrid_api_key is None: - raise ValueError("SENDGRID_API_KEY environment variable is not set") - -send_email = APIBridgeAgentNode( - name="sendgrid", - input_path="sendgrid_state.input", - output_path="sendgrid_state.output", - service_api_key=sendgrid_api_key, - hostname=SENDGRID_HOST, - service_name="sendgrid/v3/mail/send" -) -``` - -**Explanation**: -* The `_path` fields indicate where to find the input and output in the `OverallState`, as explained in [Step 4](#step-4-multi-agent-application-development). -* The `service_name` field specifies the endpoint manually (`sendgrid/v3/mail/send`). However, the API Bridge can **automatically determine** the correct endpoint based on the natural language request if this field is not provided. [Learn more](../../syntactic/api_bridge_agent.md) - -Finally, update your `build_app_graph` function to **replace** the placeholder `send_mail` function defined in [Step 1](#step-1-create-a-basic-langgraph-skeleton-application) with the new `send_email` API Bridge node: - -```python -# Update your build_app_graph function -def build_app_graph() -> CompiledStateGraph: - sg = StateGraph(OverallState) - - # Replace placeholder nodes with ACP nodes - sg.add_node(acp_mailcomposer) - sg.add_node(acp_email_reviewer) - sg.add_node(send_email) # Replace the placeholder send_mail with the API Bridge - - # Define the flow of the graph - sg.add_edge(START, acp_mailcomposer.get_name()) - sg.add_edge(acp_mailcomposer.get_name(), acp_email_reviewer.get_name()) - sg.add_edge(acp_email_reviewer.get_name(), send_email.get_name()) - sg.add_edge(send_email.get_name(), END) - - graph = sg.compile() - print("Graph compiled successfully.") - return graph -``` - -At this point, if you want to run your application to see if it compiles [(as at the end of Step 1 above)](#skeleton-code-example), you will -need to set the `SENDGRID_API_KEY` in your environment. This depends on your operating system and -shell, but an example for most shells in most Unix-like OSes: - -```shell -export SENDGRID_API_KEY="enter API key here" -``` - -For a complete setup guide including Tyk gateway configuration and SendGrid API details, see the [SendGrid API Bridge example in the ACP SDK documentation](../bridge-howto.md#an-example-with-sendgrid-api). - -## Step 6: I/O Mapper Integration - -In this section, we will explore how to handle inputs and outputs effectively within the workflow. Managing the flow of data between agents allows to maintain the integrity of the process. - -To achieve this, we not only add the **I/O Mapper**, a powerful tool that automatically transforms outputs from one node to match the input requirements of the next using an LLM, but also **introduce additional nodes** to demonstrate how to perform **manual mapping**. This combination showcases both automated and manual approaches to handle the state within the application. - -**What is I/O Mapper?** - -I/O Mapper is a component that ensures compatibility between agents by **transforming outputs to meet the input requirements** of subsequent agents. It addresses both **format-level** and **semantic-level** compatibility by leveraging an LLM to perform tasks such as: - -* **JSON Structure Transcoding**: Remapping JSON dictionaries. -* **Text Summarization**: Reducing or refining text content. -* **Text Translation**: Translating text between languages. -* **Text Manipulation**: Reformulating or extracting specific information. - -For more details on I/O Mapper functionality and implementation, see the [official I/O Mapper documentation](../../semantic/io_mapper.md). - -### I/O Processing Overview - -Among the three nodes added so far, some additional nodes are required to handle input and output transformations effectively. Specifically, as shown in [Marketing Campaign MAS](https://github.com/agntcy/agentic-apps/tree/main/marketing-campaign/src/marketing_campaign/app.py), the following nodes were added: - -* **`process_inputs`**: Processes the user's input, updates the `OverallState`, and initializes the `mailcomposer_state` with messages to ensure they are correctly interpreted by the `mailcomposer`. It also checks if the user has completed their interaction (e.g., input is "OK"), which means the user is satisfied about the composed email. - -* **`prepare_sendgrid_input`**: This node prepares the input for the SendGrid API. It constructs a query in natural language to send an email, using the corrected email content from the `email_reviewer` and configuration details like the recipient and sender email addresses. - -* **`prepare_output`**: This node consolidates the outputs of the application. It updates the `OverallState` with the final email content and logs the result of the email send operation. - -To make this tutorial code fully functional, we need to add implementations for the processing nodes mentioned above: - -1. First, update the `src/marketing_campaign/state.py` file to include a SendGridState definition: - ```python - # src/marketing_campaign/state.py - # Add these imports - from agntcy_acp.langgraph.api_bridge import APIBridgeOutput, APIBridgeInput - - # Add SendGridState class along with existing state classes - class SendGridState(BaseModel): - input: Optional[APIBridgeInput] = None - output: Optional[APIBridgeOutput]= None - ``` -2. Then, update `OverallState` class to include `sendgrid_state` in `src/marketing_campaign/state.py`: - ```python - class OverallState(BaseModel): - # Add sendgrid_state to OverallState - messages: List[mailcomposer.Message] = Field([], description="Chat messages") - operation_logs: List[str] = Field([], - description="An array containing all the operations performed and their result. Each operation is appended to this array with a timestamp.", - examples=[["Mar 15 18:10:39 Operation performed: email sent Result: OK", - "Mar 19 18:13:39 Operation X failed"]]) - - has_composer_completed: Optional[bool] = Field(None, description="Flag indicating if the mail composer has successfully completed its task") - has_reviewer_completed: Optional[bool] = None - has_sender_completed: Optional[bool] = None - mailcomposer_state: Optional[MailComposerState] = None - email_reviewer_state: Optional[MailReviewerState] = None - target_audience: Optional[email_reviewer.TargetAudience] = None - sendgrid_state: Optional[SendGridState] = None # Add this line - ``` - -3. Subsequently, update imports at the top of your `src/marketing_campaign/app.py` file: - ```python - # src/marketing_campaign/app.py - # Update import to include SendGridState - from marketing_campaign.state import OverallState, MailComposerState, SendGridState - - # Add these imports - import copy - from agntcy_acp.langgraph.api_bridge import APIBridgeAgentNode, APIBridgeInput - from langchain_core.runnables import RunnableConfig - ``` - -4. Finally, implement the processing nodes in `src/marketing_campaign/app.py`: - ```python - # Add these processing nodes to src/marketing_campaign/app.py - def process_inputs(state: OverallState, config: RunnableConfig) -> OverallState: - cfg = config.get('configurable', {}) - - user_message = state.messages[-1].content - - if user_message.upper() == "OK": - state.has_composer_completed = True - else: - state.has_composer_completed = False - - state.target_audience = email_reviewer.TargetAudience(cfg["target_audience"]) - - state.mailcomposer_state = MailComposerState( - input=mailcomposer.InputSchema( - messages=copy.deepcopy(state.messages), - is_completed=state.has_composer_completed - ) - ) - return state - - def prepare_sendgrid_input(state: OverallState, config: RunnableConfig) -> OverallState: - cfg = config.get('configurable', {}) - state.sendgrid_state = SendGridState( - input=APIBridgeInput( - query=f"" - f"Please send an email to {cfg['recipient_email_address']} from {cfg['sender_email_address']}.\n" - f"Content of the email should be the following:\n" - f"{state.email_reviewer_state.output.corrected_email if (state.email_reviewer_state - and state.email_reviewer_state.output - and hasattr(state.email_reviewer_state.output, 'corrected_email') - ) else ''}" - ) - ) - return state - - def prepare_output(state: OverallState, config: RunnableConfig) -> OverallState: - state.messages = copy.deepcopy( - state.mailcomposer_state.output.messages if (state.mailcomposer_state - and state.mailcomposer_state.output - and state.mailcomposer_state.output.messages - ) else [] - ) - if state.sendgrid_state and state.sendgrid_state.output and state.sendgrid_state.output.result: - state.operation_logs.append(f"Email Send Operation: {state.sendgrid_state.output.result}") - - return state - ``` - -### Conditional Edge with I/O Mapper - -The edge between the `mailcomposer` and subsequent nodes is a **conditional edge**. This edge uses the `check_final_email` **function to determine the next step** to be executed. The condition works as follows: - -* If the user input is **not "OK"**, the graph transitions to the `prepare_output` node, allowing the user to interact with the `mailcomposer` again. -* If the user input is **"OK"**, the graph transitions to the `email_reviewer` node and continues through the workflow. - -The conditional edge is implemented with the I/O Mapper, which ensures that the outputs of one node are transformed to match the input requirements of the next node. Here's how to implement the conditional edge in `src/marketing_campaign/app.py`: - -1. Add LLM client for the I/O Mapper. In this example we're using `AzureChatOpenAI`, but you can use any LLM client supported by LangChain: - ```python - # Add LLM client to src/marketing_campaign/app.py - from langchain_openai.chat_models.azure import AzureChatOpenAI - - # Initialize LLM for the I/O Mapper - llm = AzureChatOpenAI( - model="gpt-4o-mini", - api_version="2024-07-01-preview", - seed=42, - temperature=0, - ) - ``` - -2. Define the conditional edge function in `src/marketing_campaign/app.py`: - ```python - # Add conditional edge function to src/marketing_campaign/app.py - def check_final_email(state: OverallState): - """Determine whether to proceed to email review or continue user interaction. - - Returns: - "done": If the mailcomposer has produced a final email - "user": If we need to continue interacting with the user - """ - return "done" if (state.mailcomposer_state - and state.mailcomposer_state.output - and state.mailcomposer_state.output.final_email - ) else "user" - ``` - -3. Next, update the `build_app_graph` function in `src/marketing_campaign/app.py` to include our new nodes and the `add_io_mapped_conditional_edge` edge: - ```python - # Add import for I/O Mapper in src/marketing_campaign/app.py - from agntcy_acp.langgraph.io_mapper import add_io_mapped_conditional_edge - - # Update build_app_graph - def build_app_graph() -> CompiledStateGraph: - sg = StateGraph(OverallState) - - # Add all nodes to the graph - sg.add_node(process_inputs) - sg.add_node(acp_mailcomposer) - sg.add_node(acp_email_reviewer) - sg.add_node(send_email) - sg.add_node(prepare_sendgrid_input) - sg.add_node(prepare_output) - - # Define the initial flow - sg.add_edge(START, "process_inputs") - sg.add_edge("process_inputs", acp_mailcomposer.get_name()) - - # Add conditional edge between mailcomposer and either email_reviewer or END, adding io_mappers between them - add_io_mapped_conditional_edge( - sg, - start=acp_mailcomposer, - path=check_final_email, - iomapper_config_map={ - "done": { - "end": acp_email_reviewer, - "metadata": { - "input_fields": ["mailcomposer_state.output.final_email", "target_audience"] - } - }, - "user": { - "end": "prepare_output", - "metadata": None - } - }, - llm=llm - ) - - # Define the remaining flow for the "done" path - sg.add_edge(acp_email_reviewer.get_name(), "prepare_sendgrid_input") - sg.add_edge("prepare_sendgrid_input", send_email.get_name()) - sg.add_edge(send_email.get_name(), "prepare_output") - sg.add_edge("prepare_output", END) - - graph = sg.compile() - print("Graph compiled successfully.") - return graph - ``` - -#### Explanation of Parameters and Workflow Behavior: - -* **`start=acp_mailcomposer`**: Specifies the starting node for the conditional edge, which is the `mailcomposer`. -* **`path=check_final_email`**: This is the function that determines the condition for the edge. It returns either `"done"` or `"user"`. - * `"done"` indicates that the user is satisfied with the composed email, so to go to the `email_reviewer`. - * `"user"` indicates that the user is not satisfied, and move towards `prepare_output` to log the results and loops back to the user. - -* **`"input_fields": ["mailcomposer_state.output.final_email", "target_audience"]`**: Specifies what to map: - * `"mailcomposer_state.output.final_email"`: Automatically takes the `final_email` output from the `mailcomposer` and maps it to the input defined in the manifest of the `email_reviewer` - * `"target_audience"`: is populated during `process_inputs` from the configuration, required by `email_reviewer` - -!!! Note - All paths specified in the `input_fields` are rooted in the `OverallState` - -With these additions, our application now has a complete workflow that can: -1. Process user inputs and initialize states -2. Compose emails with the Mail Composer agent -3. Route based on user feedback using conditional edges -4. Review emails with the Email Reviewer agent when needed -5. Prepare and send emails using the SendGrid API Bridge -6. Provide meaningful output back to the user - -At this point, if you want to run your application to see if it compiles [(as at the end of Step 1 above)](#skeleton-code-example), you will -need to set some variables for Azure in your environment. This depends on your operating system -and shell, but an example for most shells in most Unix-like OSes: - -```shell -export AZURE_OPENAI_API_KEY="enter API key here" -export AZURE_OPENAI_ENDPOINT="https://resource.azure.openai.com/openai/" -``` - -## Step 7: Generate Application Manifest - -In this step, we will **generate the Agent Manifest** for our Marketing Campaign application. The manifest generation enables our application to be used by other applications and to be deployed through an [Agent Workflow Server](https://github.com/agntcy/workflow-srv). - -> **Why Generate an Agent Manifest?** -> 1. **Reusability**: The manifest allows your MAS to be used as a dependency in other applications, so as to allow modular and composable agent architectures. -> 2. **Deployment**: It provides the necessary information for the Workflow Server Manager to deploy and run your application along with its dependencies. -> 3. **Documentation**: It serves as a self-documenting artifact that describes your agent's capabilities, configuration options, and dependencies. - -### Creating the Manifest Generator - -Let's create a new file called `generate_manifest.py` in the `src/marketing_campaign/` directory: - -```python -# src/marketing_campaign/generate_manifest.py -from datetime import datetime, timezone -from pathlib import Path - -from agntcy_acp.manifest import ( - AgentACPSpec, - AgentDependency, - AgentDeployment, - AgentManifest, - AgentRef, - Capabilities, - DeploymentManifest, - DeploymentOptions, - EnvVar, - LangGraphConfig, - Locator, - Manifest, - Skill, - SourceCodeDeployment, - OASF_EXTENSION_NAME_MANIFEST, -) -from pydantic import AnyUrl - -from marketing_campaign.state import ConfigModel, OverallState - -# Deps are relative to the main manifest file. -mailcomposer_dependency_manifest = "mailcomposer.json" -email_reviewer_dependency_manifest = "email_reviewer.json" - -manifest = AgentManifest( - name="org.agntcy.marketing-campaign", - authors=["AGNTCY Internet of Agents Collective"], - annotations={"type": "langgraph"}, - version="0.3.1", - locators=[ - Locator( - url="https://github.com/agntcy/agentic-apps/tree/main/marketing-campaign", - type="source-code", - ), - ], - description="Offer a chat interface to compose an email for a marketing campaign. Final output is the email that could be used for the campaign", - created_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), - schema_version="0.1.3", - skills=[ - Skill( - category_name="Natural Language Processing", - category_uid=1, - class_name="Dialogue Generation", - class_uid=10204, - ), - Skill( - category_name="Natural Language Processing", - category_uid=1, - class_name="Text Completion", - class_uid=10201, - ), - Skill( - category_name="Natural Language Processing", - category_uid=1, - class_name="Text Paraphrasing", - class_uid=10203, - ), - Skill( - category_name="Natural Language Processing", - category_uid=1, - class_name="Knowledge Synthesis", - class_uid=10303, - ), - Skill( - category_name="Natural Language Processing", - category_uid=1, - class_name="Text Style Transfer", - class_uid=10206, - ), - Skill( - category_name="Natural Language Processing", - category_uid=1, - class_name="Tone and Style Adjustment", - class_uid=10602, - ), - ], - extensions=[ - Manifest( - name=OASF_EXTENSION_NAME_MANIFEST, - version="v0.2.2", - data=DeploymentManifest( - acp=AgentACPSpec( - input=OverallState.model_json_schema(), - output=OverallState.model_json_schema(), - config=ConfigModel.model_json_schema(), - capabilities=Capabilities( - threads=False, callbacks=False, interrupts=False, streaming=None - ), - custom_streaming_update=None, - thread_state=None, - interrupts=None, - ), - deployment=AgentDeployment( - deployment_options=[ - DeploymentOptions( - root=SourceCodeDeployment( - type="source_code", - name="source_code_local", - url=AnyUrl("file://../"), - framework_config=LangGraphConfig( - framework_type="langgraph", - graph="marketing_campaign.app:graph", - ), - ) - ) - ], - env_vars=[ - EnvVar( - name="AZURE_OPENAI_API_KEY", - desc="Azure key for the OpenAI service", - ), - EnvVar( - name="AZURE_OPENAI_ENDPOINT", - desc="Azure endpoint for the OpenAI service", - ), - EnvVar(name="SENDGRID_API_KEY", desc="Sendgrid API key"), - ], - agent_deps=[ - AgentDependency( - name="mailcomposer", - ref=AgentRef( - name="org.agntcy.mailcomposer", - version="0.0.1", - url=AnyUrl(f"file://{mailcomposer_dependency_manifest}"), - ), - deployment_option=None, - env_var_values=None, - ), - AgentDependency( - name="email_reviewer", - ref=AgentRef( - name="org.agntcy.email_reviewer", - version="0.0.1", - url=AnyUrl(f"file://{email_reviewer_dependency_manifest}"), - ), - deployment_option=None, - env_var_values=None, - ), - ], - ), - ), - ), - ], -) - -with open( - f"{Path(__file__).parent.parent.parent}/manifests/marketing-campaign.json", "w" -) as f: - json_content = manifest.model_dump_json( - exclude_unset=True, exclude_none=True, indent=2 - ) - f.write(json_content) - f.write("\n") -``` - -### Understanding the Manifest Generator Structure - -Let's break down the components of our manifest generator: - -1. **Metadata**: Defines basic information as unique name of the agent (`org.agntcy.marketing-campaign`), its version number, and a human-readable description explaining its purpose or functionality. - -2. **Specs**: Establishes how the agent communicates by defining expected input/output formats using `OverallState` JSON schemas, configuration options through `ConfigModel`, and supported capabilities. - -3. **Deployment**: This section contains deployment-related information: - * `deployment_options`: Defines how the agent can be deployed - * `url=AnyUrl("file://.")`: Specifies that the source code is located in the current directory (relative to where the manifest is being used) - * `framework_config`: Specifies that this is a LangGraph application with the graph defined in `marketing_campaign.app:graph` - - * `env_vars`: Lists the environment variables required by the marketing campaign. - * `dependencies`: Lists the agents that our application depends on. Each dependency specifies: - * The local name used to refer to the dependency - * The reference to the agent manifest file (`./manifests/mailcomposer.json`) - -### Generating the Manifest - -Now, let's run our script to generate the manifest: - -```bash -# Install the current project -poetry install - -# Run the manifest generator -poetry run python src/marketing_campaign/generate_manifest.py -``` - -This will create a file called `marketing-campaign.json` in the `manifests` directory, which contains all the information needed for: - -* Using our Marketing Campaign application as a dependency in other applications -* Deploying and running our application through the Workflow Server Manager - -We'll focus on the second point and see how to execute our application using the Workflow Server Manager. - -## Step 8: Review Resulting Application - -Below is the final graph that represents the **complete process** of composing, reviewing, and sending an email. This graph shows how agents are connected, how inputs and outputs are processed, and how the application adapts dynamically based on user interactions. - -![Final LangGraph Application](./_static/marketing_campaign_final.png) - -Our completed application now implements a robust workflow where: - -The MAS begins with the `process_inputs` node and transitions to the `mailcomposer` node, where the email draft is created. A **conditional edge** allows the user to interact with the `mailcomposer` until they are satisfied with the composed email. Once confirmed, the workflow proceeds through the following nodes in sequence: - -1. **`email_reviewer`**: Reviews and refines the email content. -2. **`prepare_sendgrid_input`**: Prepares the input for the SendGrid API. -3. **`sendgrid`**: Sends the email using the SendGrid API. -4. **`prepare_output`**: Consolidates the final output and logs the result. - -This application review highlights the **importance of input and output transformations**, the role of the **I/O Mapper** in ensuring compatibility between agents, and the flexibility provided by conditional edges to adapt the workflow dynamically. With this architecture, the system achieves a robust and user-friendly process for managing email campaigns. - -## Step 9: Execute Application through Workflow Server Manager - -After generating the manifest, we can deploy and run our application using the Workflow Server Manager. This allows us to execute the entire multi-agent system as a distributed application with all dependencies properly managed. - -### Installing the Workflow Server Manager - -First, download the Workflow Server Manager CLI appropriate for your operating system from -the [releases page](https://github.com/agntcy/workflow-srv-mgr/releases). Note that the -latest version may be later (semantically) than the version in the example below. Make sure -to execute these commands from the root directory of your project: - -```bash -# For macOS with Apple Silicon (run from project root) -curl -L https://github.com/agntcy/workflow-srv-mgr/releases/download/v0.3.2/wfsm0.3.2_darwin_arm64.tar.gz -o wfsm.tar.gz -tar -xzf wfsm.tar.gz # Keep the extracted wfsm binary it in the project root -chmod +x wfsm -# For other platforms, download the appropriate binary from the releases page -``` - -Follow these [instructions](../../agws/workflow-server-manager.md#installation) to install the Agent Workflow Server Manager. - -### Configuring the Application Environment - -Before starting the workflow server, create a configuration file that provides the necessary environment variables for the marketing-campaign application and its dependencies. Create a file named `marketing_campaign_config.yaml` in your project root directory: - -```yaml -# marketing_campaign_config.yaml -config: - email_reviewer: - port: 0 - apiKey: 799cccc7-49e4-420a-b0a8-e4de949ae673 - id: 45fb3f84-c0d7-41fb-bae3-363ca8f8092a - envVars: - AZURE_OPENAI_API_KEY: "[YOUR AZURE OPEN API KEY]" - AZURE_OPENAI_ENDPOINT: "https://[YOUR ENDPOINT].openai.azure.com" - AZURE_OPENAI_API_VERSION: "2024-08-01-preview" - mailcomposer: - port: 0 - apiKey: a9ee3d6a-6950-4252-b2f0-ad70ce57d603 - id: 76363e34-d684-4cab-b2b7-2721c772e42f - envVars: - AZURE_OPENAI_API_KEY: "[YOUR AZURE OPEN API KEY]" - AZURE_OPENAI_ENDPOINT: "https://[YOUR ENDPOINT].openai.azure.com" - org.agntcy.marketing-campaign: - port: 65222 - apiKey: 12737451-d333-41c2-b3dd-12f15fa59b38 - id: d6306461-ea6c-432f-b6a6-c4feaa81c19b - envVars: - AZURE_OPENAI_API_KEY: "[YOUR AZURE OPEN API KEY]" - AZURE_OPENAI_ENDPOINT: "https://[YOUR ENDPOINT].openai.azure.com" - SENDGRID_HOST: "http://host.docker.internal:8080" - SENDGRID_API_KEY: "[YOUR SENDGRID_API_KEY]" - LOG_LEVEL: debug -``` - -> **Note**: Replace placeholder values with your actual API keys and endpoints. The `SENDGRID_HOST` is set to `http://host.docker.internal:8080` to allow communication with a API Bridge service that will locally run in Docker. - -### Setting Up the SendGrid API Bridge - -Before testing the full application workflow, you need to set up the SendGrid API Bridge locally. Follow the detailed guide in the [API Bridge documentation](../bridge-howto.md#an-example-with-sendgrid-api) for complete instructions. - -### Deploying the Application - -Now, deploy the Marketing Campaign workflow server using the manifest we generated. Run this command from the root directory of your project: - -```bash -./wfsm deploy -m ./manifests/marketing-campaign.json -c ./marketing_campaign_config.yaml --dryRun=false -``` - -If the deployment is successful, you'll see output similar to: -```plaintext -2025-06-16T15:53:19+02:00 INF --------------------------------------------------------------------- -2025-06-16T15:53:19+02:00 INF ACP agent deployment name: org.agntcy.marketing-campaign -2025-06-16T15:53:19+02:00 INF ACP agent running in container: org.agntcy.marketing-campaign, listening for ACP requests on: http://127.0.0.1:65222 -2025-06-16T15:53:19+02:00 INF Agent ID: d6306461-ea6c-432f-b6a6-c4feaa81c19b -2025-06-16T15:53:19+02:00 INF API Key: 12737451-d333-41c2-b3dd-12f15fa59b38 -2025-06-16T15:53:19+02:00 INF API Docs: http://127.0.0.1:65222/agents/d6306461-ea6c-432f-b6a6-c4feaa81c19b/docs -2025-06-16T15:53:19+02:00 INF --------------------------------------------------------------------- -``` - -Take note of the **Agent ID**, **API Key**, and **Host** information, as you'll need them to interact with the deployed application. - -### Testing the Application with ACP Client - -To test our application, we'll use an ACP client that allows us to communicate with the deployed workflow server: - -1. Download the client script by running the following command from the root of the project: - ```bash - # Download the ACP client example from the agntcy/agentic-apps repository - curl https://raw.githubusercontent.com/agntcy/agentic-apps/refs/heads/main/marketing-campaign/src/marketing_campaign/main_acp_client.py -o src/marketing_campaign/main_acp_client.py - ``` - -2. Set the environment variables with the information from your configuration: - ```bash - export MARKETING_CAMPAIGN_HOST="http://localhost:65222" # Use the host from your logs - export MARKETING_CAMPAIGN_ID="d6306461-ea6c-432f-b6a6-c4feaa81c19b" # Use your actual Agent ID - export MARKETING_CAMPAIGN_API_KEY='{"x-api-key": "12737451-d333-41c2-b3dd-12f15fa59b38"}' # Use your actual API Key - - # Configuration of the application - export RECIPIENT_EMAIL_ADDRESS="recipient@example.com" - export SENDER_EMAIL_ADDRESS="sender@example.com" # Sender email address as configured in SendGrid - ``` - -3. Run the ACP client: - ```bash - poetry run python src/marketing_campaign/main_acp_client.py - ``` - -4. Interact with the application: - * Describe the marketing campaign email you want to compose - * Refine the email content through conversation with the Mail Composer agent - * Type "OK" when you're satisfied with the draft - * The Email Reviewer agent will review and improve the email for your target audience - * The email will be sent to the specified recipient via SendGrid - -Through this client interaction, you can experience the complete workflow of our multi-agent system, from email composition to delivery, with all the intermediate processing steps handled automatically. - -The Workflow Server Manager makes it easy to deploy and run complex multi-agent applications, handling the dependencies, environment configuration, and communication between components. - -### Conclusion - -In this tutorial, we demonstrated how to build a complete Multi-Agent System (MAS) using the ACP SDK. Starting from a basic LangGraph skeleton application, we progressively: - -1. Integrated remote agents using ACP nodes -2. Defined states to manage data flow between components -3. Implemented advanced features such as the I/O Mapper and API Bridge integration -4. Generated a deployable manifest for our application -5. Executed our application through the Workflow Server Manager - -These components allowed us to create a dynamic and flexible workflow that ensures compatibility between agents, adapts to user interactions, and can be deployed as a complete distributed system. - -By following this approach, you can design and implement your own MAS tailored to specific use cases, leveraging the power of ACP to enable communication and collaboration between distributed agents. diff --git a/docs/how-to-guides/thread.md b/docs/how-to-guides/thread.md deleted file mode 100644 index 3c34acf0..00000000 --- a/docs/how-to-guides/thread.md +++ /dev/null @@ -1,295 +0,0 @@ -# Building Applications with ACP Threads - -ACP Node supports threads, where a thread contains the accumulated state of a sequence of runs. - -In this tutorial, we will explore how to create a LangGraph agent with threads, wrap it in an ACP node, and leverage the various functionalities that come with using threads. - -For more information on the Agent Connect Protocol, see [here](../syntactic/connect.md). - -## Learning Objectives - -In this short tutorial you will learn: - -* How to create a manifest for a LangGraph agent from code -* Deploy the agent with Workflow Server -* Use thread endpoints effectively - -## Prerequisites - -* Poetry -* Python 3.9 or higher -* [Workflow server manager](../agws/workflow-server-manager.md#installation) -* An Editor of your choice - -## Implementation Walkthrough - -Together we will, create a LangGraph agent, deploy it on a Workflow Server, and utilize its threading capabilities. You can find the agent's source code here: [Mail Composer Agent Source Code](https://github.com/agntcy/agentic-apps/blob/main/mailcomposer/mailcomposer/mailcomposer.py). -The agent we will work with is called **Mail Composer**, which specializes in composing emails for marketing campaigns. - -### Setup - -1. Create a new poetry project - - ```console - - poetry new agent_with_thread - ``` - - !!! note - As of this writing, `angtcy_acp` only supports Python versions specified as `requires-python = "<4.0,>=3.10.0"`. Therefore, before proceeding to the next step, ensure you edit the `pyproject.toml` file and set the `requires-python` variable to: - - ```console - - requires-python = "<4.0,>=3.10.0" - ``` - -1. Install dependencies - - ```console - poetry add langgraph langchain langchain-openai pydantic agntcy_acp - ``` - -1. Within the `src/agent_with_thread` directory, create two new files: the first named `agent.py` and the second named `state.py`. - - ```console - - cd src/agent_with_thread && touch agent.py && touch state.py - ``` - -1. Copy and Paste [this code](https://github.com/agntcy/agentic-apps/blob/main/mailcomposer/mailcomposer/mailcomposer.py) in the agent.py file - -1. Change the following lines: - - From - - ```python - if is_stateless: - print("mailcomposer - running in stateless mode") - graph = graph_builder.compile() - else: - print("mailcomposer - running in stateful mode") - checkpointer = InMemorySaver() - graph = graph_builder.compile(checkpointer=checkpointer) - ``` - - To - - ```python - checkpointer = InMemorySaver() - graph = graph_builder.compile(checkpointer=checkpointer) - ``` - -1. Copy and Paste [this code](https://github.com/agntcy/agentic-apps/blob/main/mailcomposer/mailcomposer/state.py) in the `state.py` file - - !!! note - The creation of a LangGraph agent is outside the scope of this guide. If you're unfamiliar with how to create one, refer to this tutorial provided by the LangGraph team: `LangGraph Agent Tutorial `_. - -### Define agent manifest - -1. At the same level as the `src` file, create a new directory named `deploy` and inside src/agent_with_thread create a new Python file called `generate_manifest.py`. - - ```console - mkdir ../../deploy && touch generate_manifest.py - ``` - -2. In the `generate_manifest.py` file, import all the necessary libraries. - - ```python - from pathlib import Path - from pydantic import AnyUrl - from state import AgentState, OutputState, ConfigSchema - from agntcy_acp.manifest import ( - AgentManifest, - AgentDeployment, - DeploymentOptions, - LangGraphConfig, - EnvVar, - AgentMetadata, - AgentACPSpec, - AgentRef, - Capabilities, - SourceCodeDeployment, - ) - ``` - -3. Define the agent manifest, in code. - - ```python - :emphasize-lines: 10,16 - - manifest = AgentManifest( - metadata=AgentMetadata( - ref=AgentRef(name="org.agntcy.agent_with_thread", version="0.0.1", url=None), - description="Offer a chat interface to compose an email for a marketing campaign. Final output is the email that could be used for the campaign"), - specs=AgentACPSpec( - input=AgentState.model_json_schema(), - output=OutputState.model_json_schema(), - config=ConfigSchema.model_json_schema(), - capabilities=Capabilities( - threads=True, - callbacks=False, - interrupts=False, - streaming=None - ), - custom_streaming_update=None, - thread_state=AgentState.model_json_schema(), - interrupts=None - ), - deployment=AgentDeployment( - deployment_options=[ - DeploymentOptions( - root = SourceCodeDeployment( - type="source_code", - name="source_code_local", - url=AnyUrl("file://../"), - framework_config=LangGraphConfig( - framework_type="langgraph", # or "llamaindex" if yout agent is written with that particular framework, - graph="agent_with_thread.agent:graph" # if a llamaindex agent than the key for the entrypoint is path - ) - ) - ) - ], - env_vars=[ - EnvVar(name="AZURE_OPENAI_API_KEY", desc="Azure key for the OpenAI service"), - EnvVar(name="AZURE_OPENAI_ENDPOINT", desc="Azure endpoint for the OpenAI service") - ], - dependencies=[] - ) - ) - - #Write the result in a json file - - with open(f"{Path(__file__).parent}/../../deploy/manifest.json", "w") as f: - f.write(manifest.model_dump_json( - exclude_unset=True, - exclude_none=True, - indent=2 - )) - ``` - -!!! note - You might have some indentation problems if you copy and paste the above code, make sure to fix them before you proceed. - -With the above code we've defined the manifest for our agent and in it we set threads with as one of it capabilities, and for that reason we also had to define the thread_state, so that the workflow server knows the model for the threads. For more detail about the manifest [here](../manifest/manifest.md). - -Now you should be able to generate the agent manifest by running - -```console - poetry run python generate_manifest.py -``` - -Confirm that there is file called manifest.json inside deploy folder. - -### Run and test the Agent - -1. Create the agent configuration file - - First you need to create a configuration file that will hold the environment variables needed by the agent. To know more about the structure of this file go `here `_. - -1. Go to deploy folder previously created and create a file called config.yaml. - - ```console - cd ../../deploy && touch config.yaml - ``` - -1. Paste the code below, inside config.yaml and replace the environment variables accordingly. - - ```yaml - config: - org.agntcy.agent_with_thread: - port: 52393 - apiKey: 799cccc7-49e4-420a-b0a8-e4de949ae673 - id: 45fb3f84-c0d7-41fb-bae3-363ca8f8092a - envVars: - AZURE_OPENAI_API_KEY: [YOUR AZURE OPEN API KEY] - AZURE_OPENAI_ENDPOINT: https://[YOUR ENDPOINT].openai.azure.com - ``` - -1. Deploy the agent using the Workflow Server ([Workflow Server Repository](https://github.com/agntcy/workflow-srv)) and the Workflow Server Manager ([Workflow Server Manager Repository](https://github.com/agntcy/workflow-srv-mgr)) - - From the root of this project run: - - ```console - wfsm deploy -m deploy/manifest.json -c deploy/config.yaml --dryRun=false - ``` - -1. Test your Agent - -### Create a new thread - -```console - curl -X 'POST' \ - 'http://127.0.0.1:52393/threads' \ - -H 'accept: application/json' \ - -H 'x-api-key: 799cccc7-49e4-420a-b0a8-e4de949ae673' \ - -H 'Content-Type: application/json' \ - -d '{ - "thread_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "metadata": {}, - "if_exists": "raise" - }' -``` - -### Run the thread - -```console - - curl -X 'POST' \ - 'http://127.0.0.1:52393/threads/3fa85f64-5717-4562-b3fc-2c963f66afa6/runs/wait' \ - -H 'accept: application/json' \ - -H 'x-api-key: 799cccc7-49e4-420a-b0a8-e4de949ae673' \ - -H 'Content-Type: application/json' \ - -d '{ - "agent_id": "45fb3f84-c0d7-41fb-bae3-363ca8f8092a", - "input": { - "is_completed": null, - "messages": [{"type": "human", "content": "Email about wooden spoon be inventive on regarding email body"}] - }, - "metadata": {}, - "config": { - "tags": [ - "string" - ], - "recursion_limit": 10, - "configurable": { - "test": true, - "thread_id":"3fa85f64-5717-4562-b3fc-2c963f66afa6" - } - }, - "stream_mode": null, - "on_disconnect": "cancel", - "multitask_strategy": "reject", - "after_seconds": 0, - "stream_subgraphs": false, - "if_not_exists": "reject" - }' -``` - -### Get the state - -```console - curl -X 'GET' \ - 'http://127.0.0.1:53032/threads/3fa85f64-5717-4562-b3fc-2c963f66afa6' \ - -H 'accept: application/json' \ - -H 'x-api-key: 8280bb5a-ced8-44d6-bb38-71a69ba2cb31' -``` - -This will return a the current state of the thread in the format specified in the manifest. - -### Get the state history - -```console - - curl -X 'GET' \ - 'http://127.0.0.1:52393/threads/3fa85f64-5717-4562-b3fc-2c963f66afa6' \ - -H 'accept: application/json' \ - -H 'x-api-key: 799cccc7-49e4-420a-b0a8-e4de949ae673' -``` - -This will return a the entire state for every run of the given thread_id. - -## Final Words - -Do not stop here check our open api documentation and try out the more [endpoints](https://spec.acp.agntcy.org/#tag/threads). - -Thank you for reading! diff --git a/docs/identity/.index b/docs/identity/.index index cf7e3e27..e0ce205a 100644 --- a/docs/identity/.index +++ b/docs/identity/.index @@ -20,4 +20,5 @@ nav: - MCP Server Badge Examples: vc_mcp.md - Architecture Diagrams: arch_diagrams.md - Sequence Flows: flow.md - - OpenAPI Reference: openapi.md \ No newline at end of file + - OpenAPI Reference: openapi.md + - Identity Quickstart Guide: identity-quickstart.md \ No newline at end of file diff --git a/docs/identity/creating_identities.md b/docs/identity/creating_identities.md index 780810a4..f45edcb0 100644 --- a/docs/identity/creating_identities.md +++ b/docs/identity/creating_identities.md @@ -51,7 +51,7 @@ For services that are not directly accessible from the public internet (e.g., se **Steps:** 1. **Install the Python SDK:** - - Ensure you have the Python SDK installed. Refer to [the SDK section](/docs/sdk) of the documentation for detailed installation instructions. + - Ensure you have the Python SDK installed. Refer to [the SDK section](./identity_service_sdk.md) of the documentation for detailed installation instructions. 2. **Perform the Badge Creation Command:** - Open your terminal or command prompt. - Execute the following command: diff --git a/docs/how-to-guides/identity-quickstart.md b/docs/identity/identity-quickstart.md similarity index 100% rename from docs/how-to-guides/identity-quickstart.md rename to docs/identity/identity-quickstart.md diff --git a/docs/identity/identity_service.md b/docs/identity/identity_service.md index 4532f69f..c55bb4ef 100644 --- a/docs/identity/identity_service.md +++ b/docs/identity/identity_service.md @@ -73,7 +73,7 @@ To run these steps successfully, you need to have the following installed: ``` !!! note - You can also install the `Backend` and the `Frontend` using our [Helm charts](charts). + You can also install the `Backend` and the `Frontend` using our [Helm charts](https://github.com/agntcy/identity-service/tree/main/charts). 3. Access the Frontend UI and the Backend APIs: diff --git a/docs/identity/identity_service_contributing.md b/docs/identity/identity_service_contributing.md index cbbec23e..6d0a739a 100644 --- a/docs/identity/identity_service_contributing.md +++ b/docs/identity/identity_service_contributing.md @@ -228,7 +228,7 @@ The [`internal/pkg/errutil`](https://github.com/agntcy/identity-service/blob/mai The `id` provides a way to identify the error without relying on the message, and also enables custom error messages to be displayed on the frontend. Throughout the codebase, the `id` is constructed as `.`, where the `` corresponds to either the Application Service name or the directory name of the domain. -> For more information about the Application layer and its packages, see the [architecture](architecture.md) document. +> For more information about the Application layer and its packages, see the [architecture](./identity_service_contributing.md#agent-identity-service-backend-architecture) section. In case a technical error must be returned, it should be wrapped to provide additional context and traceability: diff --git a/docs/identity/identity_service_development.md b/docs/identity/identity_service_development.md index 8bbb9e48..d6c4ac17 100644 --- a/docs/identity/identity_service_development.md +++ b/docs/identity/identity_service_development.md @@ -59,7 +59,7 @@ except Exception as e: print("Error verifying badge: ", e) ``` -_Replace `{YOUR_ORGANIZATION_API_KEY}` with your actual [Organization API Key](/docs/api#organization-api-key) and `{JOSE_ENVELOPED_BADGE}` with the JOSE enveloped badge you want to verify._ +Replace `{YOUR_ORGANIZATION_API_KEY}` with your actual [Organization API Key](./identity_service_api_access.md#organization-api-key) and `{JOSE_ENVELOPED_BADGE}` with the JOSE enveloped badge you want to verify. Here is the same operation using the REST API: diff --git a/docs/manifest/.index b/docs/manifest/.index deleted file mode 100644 index 94524d28..00000000 --- a/docs/manifest/.index +++ /dev/null @@ -1,3 +0,0 @@ -nav: - - Agent Manifest: manifest.md - - Structure: manifest-structure.md \ No newline at end of file diff --git a/docs/manifest/manifest-structure.md b/docs/manifest/manifest-structure.md deleted file mode 100644 index 6ed701ee..00000000 --- a/docs/manifest/manifest-structure.md +++ /dev/null @@ -1,270 +0,0 @@ -# Agent Manifest Structure - -An Agent Manifest includes the following sections: - -* [Agent Identification and Metadata](#agent-identification-and-metadata) -* [Agent Interface Data Structure Specification](#agent-interface-data-structure-specification) -* [Agent Deployment and Consumption](#agent-deployment-and-consumption) -* [Agent Dependencies](#agent-dependencies) - -## Agent Identification and Metadata - -Agent Manifest must uniquely identify an agent within the namespace it is part of. This is done through a unique name and a version. - -Agent Manifest must include a natural language description that describes what the agent is capable of doing. This allows user and potentially other agents to select the agent that best fits a given task. - -Agent Manifest can include metadata that provides additional information about the agent, such as ownership, timestamps, tags, and so on. - -
-Sample descriptor metadata section for the mailcomposer agent - -```json -{ - "metadata": { - "ref": { - "name": "org.agntcy.mailcomposer", - "version": "0.0.1", - "url": "https://github.com/agntcy/acp-spec/blob/main/docs/sample_acp_descriptors/mailcomposer.json" - }, - "description": "This agent is able to collect user intent through a chat interface and compose wonderful emails based on that." - } - ... -} -``` - -Metadata for a mail composer agent named `org.agntcy.mailcomposer` version `0.0.1`. - -
- - -### Agent Interface Data Structure Specification -Agents willing to interoperate with other agents expose an interface that allow for invocation and configuration. - -Agent Connect Protocol specifies a standard for this interface. However, it specifies methods to configure and invoke agents, but it does not specify the format of the data structures that an agent expects and produces for such configurations and invocations. - -The specification of these data structures is included in what we call the Agent ACP descriptor, which can be provided by ACP itself, but it is also defined as part of the Agent Manifest. - -Agent `specs` section includes ACP invocation capabilities, e.g. `streaming`, `callbacks`, `interrupts` etc., and the JSON schema definitions for ACP interactions: - -* Agent Configuration. -* Run Input. -* Run Output. -* Interrupt and Resume Payloads. -* Thread State. - -
-Sample specs section for the mailcomposer agent - -```json -{ - ... - "specs": { - "capabilities": { - "threads": true, - "interrupts": true, - "callbacks": true - }, - "input": { - "type": "object", - "description": "Agent Input", - "properties": { - "message": { - "type": "string", - "description": "Last message of the chat from the user" - } - } - }, - "thread_state": { - "type": "object", - "description": "The state of the agent", - "properties": { - "messages": { - "type": "array", - "description": "Full chat history", - "items": { - "type": "string", - "description": "A message in the chat" - } - } - } - }, - "output": { - "type": "object", - "description": "Agent Input", - "properties": { - "message": { - "type": "string", - "description": "Last message of the chat from the user" - } - } - }, - "config": { - "type": "object", - "description": "The configuration of the agent", - "properties": { - "style": { - "type": "string", - "enum": ["formal", "friendly"] - } - } - }, - "interrupts": [ - { - "interrupt_type": "mail_send_approval", - "interrupt_payload": { - "type": "object", - "title": "Mail Approval Payload", - "description": "Description of the email", - "properties": { - "subject": { - "title": "Mail Subject", - "description": "Subject of the email that is about to be sent", - "type": "string" - }, - "body": { - "title": "Mail Body", - "description": "Body of the email that is about to be sent", - "type": "string" - }, - "recipients": { - "title": "Mail recipients", - "description": "List of recipients of the email", - "type": "array", - "items": { - "type": "string", - "format": "email" - } - } - }, - "required": [ - "subject", - "body", - "recipients" - ] - }, - "resume_payload": { - "type": "object", - "title": "Email Approval Input", - "description": "User Approval for this email", - "properties": { - "reason": { - "title": "Approval Reason", - "description": "Reason to approve or decline", - "type": "string" - }, - "approved": { - "title": "Approval Decision", - "description": "True if approved, False if declined", - "type": "boolean" - } - }, - "required": [ - "approved" - ] - } - } - ] - } - ... -} -``` -The agent supports threads, interrupts, and callback. - -It declares schemas for input, output, and config: - -* As input, it expects the next message of the chat from the user. -* As output, it produces the next message of the chat from the agent. -* As config it expects the style of the email to be written. - -It supports one kind of interrupt, which is used to ask user for approval before sending the email. It provides subject, body, and recipients of the email as interrupt payload and expects approval as input to resume. - -It supports a thread state which holds the chat history. - -
- - -### Agent Deployment and Consumption - -Agents can be provided in two different forms, which we call deployment options: - -* **As a service**: a network endpoint that exposes an interface to the agent (for example, Agent Connect Protocol). -* **As a deployable artifact**, for example: - - * A docker image, which once deployed exposes an interface to the agent (for example, Agent Connect Protocol). - * A source code bundle, which can be executed within the specific runtime and framework it is built on. - -The same agent can support one or more deployment options. - -Agent Manifest currently supports three deployment options: - -* Source Code Deployment: In this case the agent can be deployed starting from its code. For this deployment mode, the manifest provides: - * The location where the code is available - * The framework used for this agent - * The framework specific configuration needed to run the agent. -* Remote Service Deployment: In this case, the agent does not come as a deployable artefact, but it's already deployed and available as a service. For this deployment mode, the manifest provides: - * The network endpoint where the agent is available through the ACP - * The authentication used by ACP for this agent -* Docker Deployment: In this case the agent can be deployed starting from a docker image. It is assumed that once running the docker container expose the agent through ACP. For this deployment mode, the manifest provides: - * The agent container image - * The authentication used by ACP for this agent - -
-Sample manifest dependency section for the mailcomposer agent - -```json -{ - ... - "deployments": [ - { - "type": "source_code", - "name": "src", - "url": "git@github.com:agntcy/mailcomposer.git", - "framework_config": { - "framework_type": "langgraph", - "graph": "mailcomposer" - } - } - ] - ... -} -``` - -Mailcomposer agent in the example above comes as code written for LangGraph and available on Github. - - -
- -### Agent Dependencies - -An agent may depend on other agents, which means that at some point of its execution it needs to invoke them to accomplish its tasks. We refer to these other agents as **sub-agents**. A user who wants to use the agent, needs to know this information and check that the dependencies are satisfied, that is, make sure that the sub-agents are available. -This may imply simply checking that sub-agents are reachable or deploying them, according to the deployment modes they support. - -The Agent Manifest must include a list of all sub-agents in the form of a list of references to their manifests. - -Note the recursive nature of Agent Manifests that can point in turn to other Agent Manifests as dependencies. - -
-Sample manifest dependency section for the mailcomposer agent - -```json -{ - ... - "dependencies": [ - { - "name": "org.agntcy.sample-agent-2", - "version": "0.0.1" - }, - { - "name": "org.agntcy.sample-agent-3", - "version": "0.0.1" - } - ] - ... -} -``` - -Mailcomposer agent in the example above depends on `sample-agent-2` and `sample-agent-3`. - -
- - diff --git a/docs/manifest/manifest.md b/docs/manifest/manifest.md deleted file mode 100644 index edd49d75..00000000 --- a/docs/manifest/manifest.md +++ /dev/null @@ -1,17 +0,0 @@ -# Agent Manifest - -!!! info - This component is not currently under active development. While it remains available, updates and new features are not planned at this time. - -An Agent Manifest is a document that describes in detail the following: - -* What the agent is capable of. -* How the agent can be consumed if provided as-a-service. -* How the agent can be deployed if provided as a deployable artifact. -* What are the dependencies of the agent, that is, which other agents it relies on. - -The manifest is designed to be used by [Agent Connect Protocol](../syntactic/agntcy_acp_reference.md) and the [Agent Workflow Server](../agws/workflow-server.md) and stored in the [Agent Directory](../dir/overview.md) with the corresponding OASF extensions. - -This document describes the principles of the Agent Manifest definition. Manifest definition can be found [here](https://github.com/agntcy/workflow-srv-mgr/blob/main/wfsm/spec/manifest.json) - -Sample manifests can be found [here](https://github.com/agntcy/workflow-srv-mgr/tree/main/wfsm/spec/examples). diff --git a/docs/oasf/.index b/docs/oasf/.index index 8adae101..8d8197ef 100644 --- a/docs/oasf/.index +++ b/docs/oasf/.index @@ -6,4 +6,5 @@ nav: - Decoding Service: decoding.md - Translation Service: translation.md - Validation Service: validation.md + - OASF Record Guide: agent-record-guide.md - OASF Contribution Guide: contributing.md diff --git a/docs/how-to-guides/agent-record-guide.md b/docs/oasf/agent-record-guide.md similarity index 100% rename from docs/how-to-guides/agent-record-guide.md rename to docs/oasf/agent-record-guide.md diff --git a/docs/oasf/open-agentic-schema-framework.md b/docs/oasf/open-agentic-schema-framework.md index c1ea94f0..fe3b2ade 100644 --- a/docs/oasf/open-agentic-schema-framework.md +++ b/docs/oasf/open-agentic-schema-framework.md @@ -77,7 +77,7 @@ by Outshift by Cisco. To deploy the server either locally or as a hosted service, see the [server'sย guide](oasf-server.md) for more information. -See [Creatingย anย Agentย Record](../how-to-guides/agent-record-guide.md) for more +See [Creatingย anย Agentย Record](./agent-record-guide.md) for more information on the Agent Record. The current skill set taxonomy is described in diff --git a/docs/semantic/.index b/docs/semantic/.index deleted file mode 100644 index 68fd4e4d..00000000 --- a/docs/semantic/.index +++ /dev/null @@ -1,3 +0,0 @@ -nav: - - IO Mapper: io_mapper.md - - Semantic Router Agent: semantic_router.md \ No newline at end of file diff --git a/docs/semantic/io_mapper.md b/docs/semantic/io_mapper.md deleted file mode 100644 index adfbbb98..00000000 --- a/docs/semantic/io_mapper.md +++ /dev/null @@ -1,405 +0,0 @@ -# IO Mapper Agent - -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-fbab2c.svg)](https://github.com/agntcy/acp-sdk/blob/main/CODE_OF_CONDUCT.md) - -## About the IO Mapper Agent - -When connecting agents in an application, the output of an agent needs to be compatible with the input of the agent that is connected to it. This compatibility needs to be guaranteed at three different levels: - -1. Transport level: the two agents need to use the same transport protocol. -2. Format level: the two agents need to carry information using the same format (for example, the same JSON data structures). -3. Semantic level: the two agents need to โ€œtalk about the same thingโ€. - -Communication between agents is not possible if there are discrepancies between the agents at any of these levels. - -Ensuring that agents are semantically compatible, that is, the output of the one agent contains the information needed -by later agents, is an problem of composition or planning in the application. The IO Mapper Agent -addresses level 2 and 3 compatibility. It is a component, implemented as an agent, that can make use of an LLM -to transform the output of one agent to become compatible to the input of another agent. This can mean -many different things: - -- JSON structure transcoding: A JSON dictionary needs to be remapped into another JSON dictionary. -- Text summarisation: A text needs to be summarised or some information needs to be removed. -- Text translation: A text needs to be translated from one language to another. -- Text manipulation: Part of the information of one text needs to be reformulated into another text. -- A combination of the above. - -The IO mapper Agent can be fed the schema definitions of inputs and outputs as defined by the `Agent Connect Protocol `_. - -## Getting Started - -### Prerequisites - -- [Poetry](https://python-poetry.org/) -- [cmake](https://cmake.org/) - -### Use in your project - -To install the IO Mapper Agent, run the following command: - -```sh -pip install agntcy-iomapper -``` - -To get a local copy up and running, follow the steps below. - -#### Clone the repository - -``` sh -git clone https://github.com/agntcy/iomapper-agnt.git -``` - -#### Install dependencies - -``` sh -poetry install -``` - -## Usage - -There are several different ways to leverage the IO Mapper functions in -Python. There is an [How to use the Agent IO mapping](#how-to-use-the-agent-io-mapping) using -models that can be invoked on different AI platforms and an imperative -interface that does deterministic JSON remapping without using any AI -models. - -## Key Features - -The IO Mapper Agent uses an LLM to transform the inputs (typically the -output of an agent) to match the desired output (typically the input of -another agent). As such, it additionally supports specifying the model -prompts for the translation. The configuration object provides a -specification for the system and default user prompts: - -This project supports specifying model interactions using -[LangGraph](https://langchain-ai.github.io/langgraph/). - -## How to use the Agent IO mapping - -!!! note - For each example, the detailed process of creating agents and - configuring the respective multi-agent software is omitted. Instead, - only the essential steps for configuring and integrating the IO mapper - agent are presented. - -## LangGraph - -We support usages with both LangGraph state defined with TypedDict or as -a Pydantic object - -## Entities - -### IOMappingAgentMetadata - -::: agntcy_iomapper.base.IOMappingAgentMetadata - -### IOMappingAgent - -::: agntcy_iomapper.agent.IOMappingAgent - -## LangGraph Example 1 - -This example involves a multi-agent software system designed to process a create -engagement campaign and share within an organization. It interacts with an agent -specialized in creating campaigns, another agent specialized in identifying suitable -users. The information is then relayed to an IO mapper, which converts the list -of users and the campaign details to present statistics about the campaign. - -### Define an agent io mapper metadata - -```python -metadata = IOMappingAgentMetadata( - input_fields=["selected_users", "campaign_details.name"], - output_fields=["stats.status"], -) -``` - -The above instruction directs the IO mapper agent to utilize the `selected_users` -and `name` from the `campaign_details` field and map them to the `stats.status`. -No further information is needed since the type information can be derived from -the input data which is a pydantic model. - -!!! note "Tip" - Both input_fields and output_fields can also be sourced with a list composed - of str and/or instances of FieldMetadata as the below example shows - -```python -metadata = IOMappingAgentMetadata( - input_fields=[ - FieldMetadata( - json_path="selected_users", description="A list of users to be targeted" - ), - FieldMetadata( - json_path="campaign_details.name", - description="The name that can be used by the campaign", - examples=["Campaign A"] - ), - ], - output_fields=["stats"], -) -``` - -### Define an Instance of the Agent - -``` -mapping_agent = IOMappingAgent(metadata=metadata, llm=llm) -``` - -### Add the node to the LangGraph graph - -```python -workflow.add_node( - "io_mapping", - mapping_agent.langgraph_node, -) -``` - -### Add the Edge - -With the edge added, you can run the your LangGraph graph. - -```python -workflow.add_edge("create_communication", "io_mapping") -workflow.add_edge("io_mapping", "send_communication") -``` - -## LangGraph Example 2 - -This example involves a multi-agent software system designed to process a list -of ingredients. It interacts with an agent specialized in recipe books to identify -feasible recipes based on the provided ingredients. The information is then relayed -to an IO mapper, which converts it into a format suitable for display to the user. - -### Define an Agent IO Mapper Metadata - -```python -metadata = IOMappingAgentMetadata( - input_fields=["documents.0.page_content"], - output_fields=["recipe"], - input_schema=TypeAdapter(GraphState).json_schema(), - output_schema={ - "type": "object", - "properties": { - "title": {"type": "string"}, - "ingredients": {"type": "array", "items": {"type": "string"}}, - "instructions": {"type": "string"}, - }, - "required": ["title", "ingredients, instructions"], - }, -) -``` - -### Define an Instance of the Agent - -```python -mapping_agent = IOMappingAgent(metadata=metadata, llm=llm) -``` - -### Add the node to the LangGraph graph - -```python -graph.add_node( - "recipe_io_mapper", - mapping_agent.langgraph_node, -) -``` - -### Add the Edge - -With the edge added, you can run the your LangGraph graph. - -```python -graph.add_edge("recipe_expert", "recipe_io_mapper") -``` - -## LlamaIndex - -We support both LlamaIndex Workflow and the new AgentWorkflow multi agent software - -### Entities - -#### IOMappingInputEvent - -... agntcy_iomapper.IOMappingInputEvent - -#### IOMappingOutputEvent - -... agntcy_iomapper.IOMappingOutputEvent - -## Example of usage in a LlamaIndex workflow - -In this example we recreate the campaign workflow using `LlamaIndex workflow `_ - -### Begin by importing the necessary object - -```python -from agntcy_iomapper import IOMappingAgent, IOMappingAgentMetadata -``` - -### Define the workflow - -```python hl_lines="35-49" -class CampaignWorkflow(Workflow): - @step - async def prompt_step(self, ctx: Context, ev: StartEvent) -> PickUsersEvent: - await ctx.set("llm", ev.get("llm")) - return PickUsersEvent(prompt=ev.get("prompt")) - - @step - async def pick_users_step( - self, ctx: Context, ev: PickUsersEvent - ) -> CreateCampaignEvent: - return CreateCampaignEvent(list_users=users) - - # The step that will trigger IO mapping - @step - async def create_campaign( - self, ctx: Context, ev: CreateCampaignEvent - ) -> IOMappingInputEvent: - prompt = f""" - You are a campaign builder for company XYZ. Given a list of selected users and a user prompt, create an engaging campaign. - Return the campaign details as a JSON object with the following structure: - {{ - "name": "Campaign Name", - "content": "Campaign Content", - "is_urgent": yes/no - }} - Selected Users: {ev.list_users} - User Prompt: Create a campaign for all users - """ - parser = PydanticOutputParser(output_cls=Campaign) - llm = await ctx.get("llm", default=None) - - llm_response = llm.complete(prompt) - try: - campaign_details = parser.parse(str(llm_response)) - metadata = IOMappingAgentMetadata( - input_fields=["selected_users", "campaign_details.name"], - output_fields=["stats"], - ) - config = LLamaIndexIOMapperConfig(llm=llm) - - io_mapping_input_event = IOMappingInputEvent( - metadata=metadata, - config=config, - data=OverallState( - campaign_details=campaign_details, - selected_users=ev.list_users, - ), - ) - return io_mapping_input_event - except Exception as e: - print(f"Error parsing campaign details: {e}") - return StopEvent(result=f"{e}") - - @step - async def after_translation(self, evt: IOMappingOutputEvent) -> StopEvent: - return StopEvent(result="Done") -``` - -!!!note "Tip" - The highlighted lines shows how the io mapper can be triggered - -### Add The IO mapper step - -```python -w = CampaignWorkflow() -IOMappingAgent.as_worfklow_step(workflow=w) -``` - -## Example of usage in a LlamaIndex AgentWorkflow - -In this example we recreate the recipe workflow using `LlamaIndex AgentWorkflow `_ - -### Import the necessary objects - -``` -from agntcy_iomapper import FieldMetadata, IOMappingAgent, IOMappingAgentMetadata -``` - -### Define an instance of the IOMappingAgentMetadata - -```python -mapping_metadata = IOMappingAgentMetadata( - input_fields=["documents.0.text"], - output_fields=[ - FieldMetadata( - json_path="recipe", - description="this is a recipe for the ingredients you've provided", - ) - ], - input_schema=TypeAdapter(GraphState).json_schema(), - output_schema={ - "type": "object", - "properties": { - "title": {"type": "string"}, - "ingredients": {"type": "array", "items": {"type": "string"}}, - "instructions": {"type": "string"}, - }, - "required": ["title", "ingredients, instructions"], - }, - ) -``` - -### Finally define the IOMappingAgent and add it to the AgentWorkflow. - -Important to note that a tool is passed, to instruct the io mapper where to go next in the flow. - -```python -io_mapping_agent = IOMappingAgent.as_workflow_agent( - mapping_metadata=mapping_metadata, - llm=llm, - name="IOMapperAgent", - description="Useful for mapping a recipe document into recipe object", - can_handoff_to=["Formatter_Agent"], - tools=[got_to_format], -) - - -io_mapping_agent = IOMappingAgent.as_workflow_agent( - mapping_metadata=mapping_metadata, - llm=llm, - name="IOMapperAgent", - description="Useful for mapping a recipe document into recipe object", - can_handoff_to=["Formatter_Agent"], - tools=[got_to_format], -) -``` - -## Use Examples - -1. Install - - - `cmake `_ - - `pip `_ - -1. From the `examples` folder run the desired make command, for example: - - ```bash - make make run_lg_eg_py - ``` - -## Contributing - -Contributions are what make the open source community such an amazing place to -learn, inspire, and create. Any contributions you make are **greatly -appreciated**. For detailed contributing guidelines, please see -`CONTRIBUTING.md `_ - -## Copyright Notice and License - -`Copyright Notice and License `_ - -Copyright (c) 2025 Cisco and/or its affiliates. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/docs/semantic/semantic_router.md b/docs/semantic/semantic_router.md deleted file mode 100644 index 8086a252..00000000 --- a/docs/semantic/semantic_router.md +++ /dev/null @@ -1,18 +0,0 @@ -# Semantic Router Agent (Coming soon) - -When defining a multi-agent application workflow, it is often necessary to make decisions based on the output of invoked agents. - -In a graph-based agentic applications (for example, LangGraph), this corresponds to deciding which node in the graph to execute based on the current state of the graph. - -There is a large set of common decisions that are based on semantic similarity, even if some of these decisions can be trivially implemented by a simple `if` condition and others can be so complex that they require a dedicated agent to be processed. - -This is where the Semantic Router Agent comes in. The Semantic Router Agent is a component, modelled as a node in the graph, that takes an input in the form of natural language and decides where to go next. Here next means following an edge in the graph that is associated to the semantically closest reference natural language text. In other words: the Semantic Router Agent chooses the next node based on a semantic routing table. - -**Example**: - -> An assistant agent receives a prompt from a user during a conversation and based on the content, it needs to perform different actions: -> - If the user is posting a new request, start the request handling flow. -> - If the user is satisfied with the conversation, terminate it and direct the flow to the auditing agent. -> - If the user is not satisfied, involve a human. -> -> The above can be implemented with a semantic router agent with three possible routes, with each route associated with a text describing what is the expected content of the user prompt. diff --git a/docs/syntactic/.index b/docs/syntactic/.index deleted file mode 100644 index e63174ca..00000000 --- a/docs/syntactic/.index +++ /dev/null @@ -1,5 +0,0 @@ -nav: - - Agent Connect Protocol: connect.md - - Agntcy ACP Client SDK: agntcy_acp_sdk.md - - API Bridge Agent: api_bridge_agent.md - - Human In the Loop: hil.md \ No newline at end of file diff --git a/docs/syntactic/agntcy_acp_models.md b/docs/syntactic/agntcy_acp_models.md deleted file mode 100644 index 5bbc06ee..00000000 --- a/docs/syntactic/agntcy_acp_models.md +++ /dev/null @@ -1,251 +0,0 @@ -# API Models - -## Agent - -::: agntcy_acp.models.Agent - -## AgentACPDescriptor - -::: agntcy_acp.models.AgentACPDescriptor - options: - docstring_style: sphinx - -## AgentACPSpec - -::: agntcy_acp.models.AgentACPSpec - options: - docstring_style: sphinx - -## AgentACPSpecInterruptsInner - -::: agntcy_acp.models.AgentACPSpecInterruptsInner - options: - docstring_style: sphinx - -## AgentCapabilities - -::: agntcy_acp.models.AgentCapabilities - options: - docstring_style: sphinx - -## AgentMetadata - -::: agntcy_acp.models.AgentMetadata - options: - docstring_style: sphinx - -## AgentRef - -::: agntcy_acp.models.AgentRef - options: - docstring_style: sphinx - -## AgentSearchRequest - -::: agntcy_acp.models.AgentSearchRequest - options: - docstring_style: sphinx - -## Config - -::: agntcy_acp.models.Config - options: - docstring_style: sphinx - -## Content - -::: agntcy_acp.models.Content - options: - docstring_style: sphinx - -## ContentOneOfInner - -::: agntcy_acp.models.ContentOneOfInner - options: - docstring_style: sphinx - -## CustomRunResultUpdate - -::: agntcy_acp.models.CustomRunResultUpdate - options: - docstring_style: sphinx - -## Message - -::: agntcy_acp.models.Message - options: - docstring_style: sphinx - -## MessageAnyBlock - -::: agntcy_acp.models.MessageAnyBlock - options: - docstring_style: sphinx - -## MessageTextBlock - -::: agntcy_acp.models.MessageTextBlock - options: - docstring_style: sphinx - -## Run - -::: agntcy_acp.models.Run - options: - docstring_style: sphinx - -## RunCreate - -::: agntcy_acp.models.RunCreate - options: - docstring_style: sphinx - -## RunCreateStateful - -::: agntcy_acp.models.RunCreateStateful - options: - docstring_style: sphinx - -## RunCreateStateless - -::: agntcy_acp.models.RunCreateStateless - options: - docstring_style: sphinx - -## RunError - -::: agntcy_acp.models.RunError - options: - docstring_style: sphinx - -## RunInterrupt - -::: agntcy_acp.models.RunInterrupt - options: - docstring_style: sphinx - -## RunOutput - -::: agntcy_acp.models.RunOutput - options: - docstring_style: sphinx - -## RunOutputStream - -::: agntcy_acp.models.RunOutputStream - options: - docstring_style: sphinx - -## RunResult - -::: agntcy_acp.models.RunResult - options: - docstring_style: sphinx - -## RunSearchRequest - -::: agntcy_acp.models.RunSearchRequest - options: - docstring_style: sphinx - -## RunStateful - -::: agntcy_acp.models.RunStateful - options: - docstring_style: sphinx - -## RunStateless - -::: agntcy_acp.models.RunStateless - options: - docstring_style: sphinx - -## RunStatus - -::: agntcy_acp.models.RunStatus - options: - docstring_style: sphinx - -## RunWaitResponseStateful - -::: agntcy_acp.models.RunWaitResponseStateful - options: - docstring_style: sphinx - -## RunWaitResponseStateless - -::: agntcy_acp.models.RunWaitResponseStateless - options: - docstring_style: sphinx - -## StreamEventPayload - -::: agntcy_acp.models.StreamEventPayload - options: - docstring_style: sphinx - -## StreamMode - -::: agntcy_acp.models.StreamMode - options: - docstring_style: sphinx - -## StreamingMode - -::: agntcy_acp.models.StreamingMode - options: - docstring_style: sphinx - -## StreamingModes - -::: agntcy_acp.models.StreamingModes - options: - docstring_style: sphinx - -## Thread - -::: agntcy_acp.models.Thread - options: - docstring_style: sphinx - -## ThreadCheckpoint - -::: agntcy_acp.models.ThreadCheckpoint - options: - docstring_style: sphinx - -## ThreadCreate - -::: agntcy_acp.models.ThreadCreate - options: - docstring_style: sphinx - -## ThreadPatch - -::: agntcy_acp.models.ThreadPatch - options: - docstring_style: sphinx - -## ThreadSearchRequest - -::: agntcy_acp.models.ThreadSearchRequest - options: - docstring_style: sphinx - -## ThreadState - -::: agntcy_acp.models.ThreadState - options: - docstring_style: sphinx - -## ThreadStatus - -::: agntcy_acp.models.ThreadStatus - options: - docstring_style: sphinx - -## ValueRunResultUpdate - -::: agntcy_acp.models.ValueRunResultUpdate - options: - docstring_style: sphinx diff --git a/docs/syntactic/agntcy_acp_reference.md b/docs/syntactic/agntcy_acp_reference.md deleted file mode 100644 index 60dbb600..00000000 --- a/docs/syntactic/agntcy_acp_reference.md +++ /dev/null @@ -1,167 +0,0 @@ -# Client Reference API - -## ACP Objects - -### ApiClientConfiguration - -::: agntcy_acp.ApiClientConfiguration - -### ACPClient - -::: agntcy_acp.ACPClient - options: - docstring_style: sphinx - -### AsyncACPClient - -::: agntcy_acp.AsyncACPClient - options: - docstring_style: sphinx - -### ApiClient - -::: agntcy_acp.ApiClient - options: - docstring_style: sphinx - -### AsyncApiClient - -::: agntcy_acp.AsyncApiClient - options: - docstring_style: sphinx - -### ApiResponse - -::: agntcy_acp.ApiResponse - options: - docstring_style: sphinx - -## LangGraph Bindings - -### ACPNode - -::: agntcy_acp.langgraph.ACPNode - options: - docstring_style: sphinx - -### APIBridgeAgentNode - -::: agntcy_acp.langgraph.APIBridgeAgentNode - options: - docstring_style: sphinx - -### APIBridgeInput - -::: agntcy_acp.langgraph.APIBridgeInput - options: - docstring_style: sphinx - -### APIBridgeOutput - -::: agntcy_acp.langgraph.APIBridgeOutput - options: - docstring_style: sphinx - -### add_io_mapped_edge - -::: agntcy_acp.langgraph.add_io_mapped_edge - options: - docstring_style: sphinx - -### add_io_mapped_conditional_edge - -::: agntcy_acp.langgraph.add_io_mapped_conditional_edge - options: - docstring_style: sphinx - -## Exceptions - -### ACPDescriptorValidationException - -::: agntcy_acp.ACPDescriptorValidationException - options: - docstring_style: sphinx - -### ACPRunException - -::: agntcy_acp.ACPRunException - options: - docstring_style: sphinx - -### ApiTypeError - -::: agntcy_acp.ApiTypeError - options: - docstring_style: sphinx - -### ApiValueError - -::: agntcy_acp.ApiValueError - options: - docstring_style: sphinx - -### ApiKeyError - -::: agntcy_acp.ApiKeyError - options: - docstring_style: sphinx - -### ApiAttributeError - -::: agntcy_acp.ApiAttributeError - options: - docstring_style: sphinx - -### ApiException - -::: agntcy_acp.ApiException - options: - docstring_style: sphinx - -### BadRequestException - -::: agntcy_acp.BadRequestException - options: - docstring_style: sphinx - -### NotFoundException - -::: agntcy_acp.NotFoundException - options: - docstring_style: sphinx - -### UnauthorizedException - -::: agntcy_acp.UnauthorizedException - options: - docstring_style: sphinx - -### ForbiddenException - -::: agntcy_acp.ForbiddenException - options: - docstring_style: sphinx - -### ServiceException - -::: agntcy_acp.ServiceException - options: - docstring_style: sphinx - -### ConflictException - -::: agntcy_acp.ConflictException - options: - docstring_style: sphinx - -### UnprocessableEntityException - -::: agntcy_acp.UnprocessableEntityException - options: - docstring_style: sphinx - -### OpenApiException - -::: agntcy_acp.OpenApiException - options: - docstring_style: sphinx diff --git a/docs/syntactic/agntcy_acp_sdk.md b/docs/syntactic/agntcy_acp_sdk.md deleted file mode 100644 index 1b90fd81..00000000 --- a/docs/syntactic/agntcy_acp_sdk.md +++ /dev/null @@ -1,204 +0,0 @@ -# Agntcy ACP Client - -!!! info - This component is not currently under active development. While it remains available, updates and new features are not planned at this time. - -## Introduction - -The Agent Connect Protocol SDK is an open-source library designed to -facilitate the adoption of the Agent Connect Protocol. It offers tools -for client implementations, enabling seamless integration, and communication -between multi-agent systems. - -The SDK is current available in [Python](https://pypi.org/project/agntcy-acp/) [![PyPI version](https://img.shields.io/pypi/v/agntcy-acp.svg)](https://pypi.org/project/agntcy-acp/). - -## Getting Started with the client - -To use the package, follow the steps below. - -### Requirements - -Python 3.9+ - -### Installation - -Install the latest version from PyPi: -```shell -pip install agntcy-acp -``` - -### Usage - -``` python -from agntcy_acp import AsyncACPClient, AsyncApiClient, ApiException -from agntcy_acp.models import RunCreate - -# Defining the host is optional and defaults to http://localhost -config = ApiClientConfiguration( - host="https://localhost:8081/", - api_key={"x-api-key": os.environ["API_KEY"]}, - retries=3 -) - -# Enter a context with an instance of the API client -async with AsyncApiClient(config) as api_client: - agent_id = 'agent_id_example' # str | The ID of the agent. - client = AsyncACPClient(api_client) - - try: - api_response = client.create_run(RunCreate(agent_id="my-agent-id")) - print(f"Run {api_response.run_id} is currently {api_response.status}") - except ApiException as e: - print("Exception when calling create_run: %s\n" % e) -``` - -### Documentation for API Endpoints - -The complete documentation for all of the API Endpoints are -available in the reference documentation for the API clients: - -* [ACPClient](https://agntcy.github.io/acp-sdk/html/agntcy_acp.html#agntcy_acp.ACPClient) -* [AsyncACPClient](https://agntcy.github.io/acp-sdk/html/agntcy_acp.html#agntcy_acp.AsyncACPClient) - -## Using ACP with LangGraph - -The SDK provides integration with LangGraph with the {py:obj}`agntcy_acp.langgraph.ACPNode` class -that can be used as a graph node: - -```python -from enum import Enum -from typing import List, Optional - -from langgraph.graph import END, START, StateGraph -from pydantic import BaseModel, Field - -from agntcy_acp import ApiClientConfiguration -from agntcy_acp.langgraph.acp_node import ACPNode - - -class Type(Enum): - human = 'human' - assistant = 'assistant' - ai = 'ai' - -class Message(BaseModel): - type: Type = Field( - ..., - description='indicates the originator of the message, a human or an assistant', - ) - content: str = Field(..., description='the content of the message', title='Content') - -class InputSchema(BaseModel): - messages: Optional[List[Message]] = Field(None, title='Messages') - is_completed: Optional[bool] = Field(None, title='Is Completed') - -class OutputSchema(BaseModel): - messages: Optional[List[Message]] = Field(None, title='Messages') - is_completed: Optional[bool] = Field(None, title='Is Completed') - final_email: Optional[str] = Field( - None, - description='Final email produced by the mail composer', - title='Final Email', - ) - -class StateMeasures(BaseModel): - input: InputSchema - output: OutputSchema - -def main(): - # Instantiate the local ACP node for the remote agent - acp_node = ACPNode( - name="mailcomposer", - agent_id='50272dfd-4c77-4529-abbb-419bb1724230', - client_config=ApiClientConfiguration.fromEnvPrefix("COMPOSER_"), - input_path="input", - input_type=InputSchema, - output_path="output", - output_type=OutputSchema, - ) - - # Create the state graph - sg = StateGraph(StateMeasures) - - # Add edges - sg.add_edge(START, acp_node.get_name()) - sg.add_edge(acp_node.get_name(), END) - - graph = sg.compile() - output_state = graph.invoke({ - "input": InputSchema(content=input), - "output": OutputSchema(content="bad-output"), - }) -``` - -## Using the CLI to generate Agent-specific bindings - -The Client SDK includes a CLI tool to generate models or OpenAPI specs -specific to an agent using the manifest descriptor. With these models -the agent-specific data sent to ACP can be validated. By default, -only the ACP parameters are validated by the SDK client. - -The CLI also provides validators for the ACP descriptor and manifest -files. - -You can use the CLI easily: - -* using [poetry](https://python-poetry.org/): `poetry run acp --help` -* with the package installed: `python3 -m agntcy_acp --help` - -Usage: `acp [OPTIONS] COMMAND [ARGS]...` - - Options: - -* `--help` Show this message and exit. - -Commands: - -* `generate-agent-models [OPTIONS] AGENT_DESCRIPTOR_PATH` - - Generate pydantic models from agent manifest or descriptor. - - Options: - - * `--output-dir TEXT` - - Pydantic models for specific agent based on provided - agent descriptor or agent manifest [required] - - * `--model-file-name TEXT` - - Filename containing the pydantic model of the agent - schemas - -* `generate-agent-oapi [OPTIONS] AGENT_DESCRIPTOR_PATH` - - Generate OpenAPI Spec from agent manifest or descriptor - - Options: - - * `--output TEXT` - - OpenAPI output file - -* `validate-acp-descriptor [OPTIONS] AGENT_DESCRIPTOR_PATH` - - Validate the Agent Descriptor contained in the file AGENT_DESCRIPTOR_PATH - against the ACP specification - -* `validate-acp-manifest [OPTIONS] AGENT_MANIFEST_PATH` - - Validate the Agent Manifest contained in the file AGENT_MANIFEST_PATH - against the Manifest specification - -## Testing - -To run the various unit tests in the package, run `make test`. - -## Roadmap - -See the [open issues](https://github.com/agntcy/acp-sdk/issues) for a list of proposed features and known issues. - -## Client Reference API - -For a detailed description of the classes and functions in the SDK, please see the -[agntcy-acp Package Documentation](https://agntcy.github.io/acp-sdk/index.html). diff --git a/docs/syntactic/api_bridge_agent.md b/docs/syntactic/api_bridge_agent.md deleted file mode 100644 index ba77278f..00000000 --- a/docs/syntactic/api_bridge_agent.md +++ /dev/null @@ -1,104 +0,0 @@ -# API Bridge Agent - -## About The Project - -The [API Bridge Agent](https://github.com/agntcy/api-bridge-agnt) project provides a [Tyk](https://tyk.io/) middleware plugin -that allows users to interact with external services, using natural language. These external services can either offers -traditional REST APIs interface, or MCP interfaces. It acts as a translator between human language and structured API and -MCP Servers, for requests and responses. - -Key features: - -- Select best services, based on the intent on the query. -- Converts natural language queries into valid API requests based on OpenAPI specifications for service with API interfaces, -or into valid MCP tool calls for MCP Servers. -- Transforms service responses back into natural language explanations. -- Integrates with Tyk API Gateway as a plugin. -- Uses Azure OpenAI's GPT models for language processing. -- Preserves API schema validation and security while enabling conversational interfaces. - -This enables developers to build more accessible and user-friendly API interfaces without modifying -the underlying API implementations, or to access to MCP Servers and tools without implementing a MCP Client. - -## API Bridge Agent Interfaces - -API Agent Bridge support several level of interface: - -![Agent Bridge Interfaces](../assets/ABA.drawio.png)``` - -### The API Interfaces - -API Bridge Agent provides one endpoint per API (service) supported - -i.e. if you add the Github support to API Bridge Agent, with a /github/ configured listen path, then you can address natural language requests directly to this endpoint to access to Github service. - -#### Direct Mode - -You can request directly the wanted endpoint in the API specification. - -For ex: -```shell -curl 'http://localhost:8080/gmail/gmail/v1/users/me/messages/send' \ - --header "Authorization: Bearer YOUR_GOOGLE_TOKEN" \ - --header 'Content-Type: text/plain' \ - --header 'X-Nl-Query-Enabled: yes' \ - --header 'X-Nl-Response-Type: nl' \ - --data 'Send an email to "john.doe@example.com". Explain that we are accepting his offer for Agntcy' -``` - -In this example -- /gmail/ is the listen path defined on the x-tyk-api-gateway part of the spec -- gmail/v1/users/me/messages/send is the endpoint in the specification - -API Bridge Agent will : -- use LLM to translate Natural Language Query (NLQ) to api call for the wanted endpoint -- Tyk will automatically connect to the upstream endpoint and get the response -- use LLM to translate result of api call to NLQ - -#### Indirect Mode - -In this case, you target a service, but you let API Bridge Agent to choose inside the service the best endpoint to solve the -request. - -For ex: -```shell -curl 'http://localhost:8080/gmail/' \ - --header "Authorization: Bearer YOUR_GOOGLE_TOKEN" \ - --header 'Content-Type: text/plain' \ - --header 'X-Nl-Query-Enabled: yes' \ - --header 'X-Nl-Response-Type: nl' \ - --data 'Send an email to "john.doe@example.com". Explain that we are accepting his offer for Agntcy' -``` - -API Bridge Agent will : -- use a semantic search to select the best endpoint that correspond to the query -- use LLM to translate NLQ to api call for the wanted endpoint -- Tyk will automatically connect to the upstream endpoint and get the response -- use LLM to translate result of api call to NLQ - -### The Cross-API Interface - -API Bridge Agent provide a specific endpoint /aba/. If you address a natural language request to this endpoint, -API Bridge Agent will search for the best service to solve the request, then it will forward the request to the proper API -interface. - -API Bridge Agent will : -- use a semantic search for best service selection. -- forward the request to the selected service (indirect mode: we let the service choose the best endpoint. We donโ€™t select it -at the cross-api interface level) - -### The MCP Interface (new) - -MCP is an open protocol that standardizes how applications provide context to LLMs. It provides a standardized way to connect -AI models to different data sources and tools. - -API Bridge Agent support MCP across a specific endpoint /mcp/. One MCP Client is instantiated per MCP servers connected. - -API Bridge Agent will : -- invoke the LLM with the list of available tools that come from all the connected MCP Servers. -- if the LLM needs information coming from the tools, API bridge Agent will request all the needed tools using corresponding -MCP client. -- API Bridge Agent invoke again the LLM with the NLQ, the list of tools, the first response and the list of result of call tools. -- If LLM still need some information, API Bridge Agent loop again and call tools. - -See the [Getting Started with API Bridge Agent](../how-to-guides/bridge-howto.md) section for step-by-step instructions. diff --git a/docs/syntactic/connect.md b/docs/syntactic/connect.md deleted file mode 100644 index 1e28d2a2..00000000 --- a/docs/syntactic/connect.md +++ /dev/null @@ -1,708 +0,0 @@ -# Agent Connect Protocol - -!!! info - This component is not currently under active development. While it remains available, updates and new features are not planned at this time. - -## Introduction - -Existing Multi-Agent Systems (MAS) provide convenient ways to build Multi-Agent Applications (MAAs) that combine various agents and enable them to communicate with each other. Such communication occurs within the MAS using internal mechanisms and APIs. - -Building the Internet of Agents (IoA) requires agents built by different parties, potentially for different MAS and potentially running in different locations to interact. - -While interaction between co-located agents implemented through the same MAS is trivial, it is harder in case the agents are not natively compatible or in case they run in different locations. - -We propose a solution where all agents are able to communicate over the network using a standard protocol to interoperate. We call it the **Agent Connect Protocol** (**ACP**). - -This document describes the main requirements and design principles of the ACP. - -The current specification of the ACP can be found at [https://spec.acp.agntcy.org/](https://spec.acp.agntcy.org/). - -## Getting Started - -See current ACP specification in [JSON Format](https://github.com/agntcy/acp-spec/blob/main/openapi.json) or browse its [OpenAPI visualization](https://spec.acp.agntcy.org/). - -Learn how to use the API by looking at [API Usage Flows](#api-usage-flows) - -Learn about Agent ACP Descriptor and its usage [here](#agent-acp-descriptor) - -Explore tools for ACP and Agent ACP Descriptors in the [Agent Connect SDK Documentation](../syntactic/agntcy_acp_sdk.md) - -## ACP Requirements - -Agent Connect Protocol needs to formally specify the network interactions needed to address the following: - -* **Authentication**: Define how caller authenticates with an agent and what its permissions are. -* **Configuration**: Define how to configure a remote agent. -* **Invocation**: Define how to invoke a remote agent providing input for its execution. -* **Output retrieval and interrupt Handling**: Define how to retrieve the result of an agent invocation. Different interaction modes should be supported: - - * Synchronous - * Asynchronous - * Streaming - - This should include interrupt handling. That is, how agents notify the caller about execution suspension to ask for additional input. - -* **Capabilities and Schema definitions**: Retrieve details about the agent supported capabilities and the data structures definitions for configuration, input, and output. -* **Error definitions**: Receive error notifications with meaningful error codes and explanations. - -### Authentication and Authorization -Agents invoked remotely need to authenticate the caller and make sure they have the proper authorization to perform the invocation. - -Authorization mechanisms are outside the scope of the ACP specification. - -ACP does not enforce a single authentication scheme to be used by all agents. Instead, it defines a list of allowed authentication schemes and lets agents declare which one is adopted. - -For the reason above, ACP must define an endpoint that does not require authentication and returns the specific authentication scheme that is supported by the agent. - - -### Configuration - -Agents may support configuration. - -Configuration is meant to provide parameters needed by the agent to function and to flavor their behavior. - -Configurations are typically valid for multiple invocations. - -ACP needs to define an endpoint to provide agent configuration. - -This endpoint must be distinct by the invocation endpoint, in this case it must return an identifier of the configured instance of the agent that can be used in multiple subsequent invocations. - -Invocation endpoint should also provide an option to specify the configuration. In this case the configuration is valid only for the specific invocation. - -Format of the configuration data structure is specified through a schema. For more information, see [Schema Definitions](#schemas). - -Configuration endpoint may return an error. For more information, see [Error Definitions](#errors). - - -### Invocation - -ACP must define an invocation endpoint that triggers the execution of an agent or resume a previously interrupted execution of an agent. - -The invocation endpoint must accept the following parameters: - -* **Input** - - An input provides specific information and contexts for the agent to operate. Format of the input data structure is specified through a schema. For more information, see [Schema Definitions](#schemas). - -* **Optional configuration** - - When provided, this configuration is valid only for this invocation. Alternatively, the invocation endpoint must accept the identifier of a previously configured instance of an agent. For more information, see [Configuration](#configuration). - -* **Optional callback** - - When provided, the output of the invocation is provided asynchronously through the provided callback. For more information, see [Output Retrieval and Interrupt Handling](#output). - -* **Optional execution identifier** - - In this case, the agent is requested to resume a previously interrupted execution, identified by the execution identifier. - -The invocation endpoint must return the following: - -* The **output** of the execution, in case it is provided synchronously. -* An **execution identifier**, which is then used to receive asynchronous output and to resume an interrupted execution. - -Invocation endpoint may return an error. For more information, see [Error Definitions](#errors). - - -### Output Retrieval - -Once an agent is invoked, it can provide output as a result of its operations. - -Output can be provided to the caller synchronously (as a response of the invocation endpoint) or asynchronously (through a callback provided as input of the invocation endpoint). - -Output can be provided when the following conditions occur: - -* The agent has terminated its execution and provides the final result of the execution. -* The agent has interrupted its execution because it needs additional input. For example approval or chat interaction. -* The agent is still running but it provides partial results, that is, streaming. - -Output must carry information about which condition occurred. - -Format of the output data structure is specified through a schema. For more information, see [Schema Definitions](#schemas). - - -### Capabilities and Schema Definitions - -The ACP does not mandate the format of the data structures used to carry information to and from an agent but it allows agents to provide definitions of those formats through the ACP. -The ACP must define an endpoint that provides schema definitions for configuration, input, and output. - -Different agents may implement different parts of the protocol. For example: an agent may support streaming, while another may only support full responses. An agent may support threads while another may not. - -The ACP must define an endpoint that provides details about the specific capabilities that the agent supports. - -Schemas, agent capabilities, and other essential information that describe an agent are also needed in what we call the [Agent Manifest](../manifest/manifest.md). - - -### Error Definitions - -Each of the operations offered by the ACP can produce an error. - -Errors can be provided synchronously by each of the invoked endpoints or asynchronously when they occur during an execution that supports asynchronous output. - -The ACP must define errors for the most common error conditions. - -Each definition must include the following details: - -* Error code. -* Description of the error condition. -* A flag that says if the error is transient or permanent. -* An optional schema definition of additional information that the error can be associated with. - -The ACP also allows agents to provide definitions of errors specific for that agent. For this purpose, the ACP must define an endpoint that provides schema definitions for all agent specific errors that are not included in the ACP specification. - -## API Usage Flows - -### Agents Retrieval APIs - -ACP offers an API to search for the agents served by the ACP server. -Once a client has an agent identifier `AgentID`, it can use it to either retrieve the agent descriptor or to control agent runs. - -#### Retrieve all agents supported by the server -In this case, the client is doing a search of all agents in the server without specifying any search filter. Result is the list of all agents. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /agents/search {} - S->>-C: AgentList=[{id, metadata}, {id, metadata}, ...] -``` - -#### Retrieve an agent from its name and version -In this case, the client knows name and version of an agent (e.g. learnt from the record in the Agent Directory) and wants to retrieve its `id` to interact with the agent. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /agents/search
{"name":"smart-agent", "version": "0.1.3"} - S->>-C: AgentList = [{id, metadata}] -``` - -#### Retrieve agent descriptor from its identifier -In this case, the client knows the agent id and wants to retrieve its descriptor to learn about the capabilities supported and the data schemas to use. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: GET /agents/agent/{agent_id}/descriptor - S->>-C: AgentACPDescriptor={...} -``` - -### Runs -A run is a single execution of an agent. - -#### Start a Run of an Agent and poll for completion -In this case, the client starts a background run of an agent, keeps polling the server until the run is complete, finally it retrieves the run output. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /runs {agent_id, input, config, metadata} - S->>-C: Run={run_id, status="pending"} - loop Until run["status"] == "pending" - C->>+S: GET /runs/{run_id} - S->>-C: Run={run_id, status} - end - C->>+S: GET /runs/{run_id}/wait - S->>-C: RunOutput={type="result", result} -``` - -In the sequence above: - -1. The client requests to start a run on a specific agent, providing its `agent_id`, and specifying: - - * Configuration: a run configuration is flavoring the behavior of this agent for this run. - * Input: run input provides the data the agent will operate on. - * Metadata: metadata is a free format object that can be used by the client to tag the run with arbitrary information. - -1. The server returns a run object which includes the run identifier and a status, the status at the beginning will be `pending`. -1. The client retrieves the status of the run until completion. -1. The server returns the run object with the updated status. -1. The client request the output of the run with the `wait` endpoint, which returns immediately, since the run is done. -1. The server returns the final result of the run. - -!!! note - Note that the format of the input and the configuration are not specified by ACP, but they are defined in the agent descriptor. - -#### Start a Run of an Agent and block until completion -In this case, the client starts a background run of an agent and immediately tries to retrieve the run output blocking on this call until completion or timeout. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /runs {agent_id, input, config, metadata} - S->>-C: Run={run_id, status="pending"} - C->>+S: GET /runs/{run_id}/wait - S->>-C: RunOutput={type="result", result} -``` - -In the sequence above: -1. The client requests to start a run on a specific agent. -1. The server returns a run object. -1. The client request the output of the run with the `wait` endpoint. In this case the request and blocks until run status changes. -1. The server returns the final result of the run. Note that in case the timeout had expired before, the server would have returned no content. - -#### Start a Run of an Agent with a callback -Agents can support callbacks, i.e. asynchronously call back the client upon run status change. The support for interrupts is signaled in the agent descriptor. - -In this case, the client starts a background run of an agent and provide a callback to be called upon completion. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /runs {agent_id, input, config, metadata, callback={POST /callme}} - S->>-C: Run={run_id, status="pending"} - S->>C: POST /callme Run={run_id, status="success"} - C->>+S: GET /runs/{run_id}/wait - S->>-C: RunOutput={type="result", result} -``` -In the sequence above: - -1. The client requests to start a run on a specific agent, providing an additional `callback`. -1. The server returns a run object. -1. Upon status change, the server calls the provided call back with the run object. -1. The client request the output of the run with the `wait` endpoint, which returns immediately, since the run is done. -1. The server return the final result of the run. - -### Run Interrupt and Resume -Agent can support interrupts, i.e. the run execution can interrupt to request additional input to the client. The support for interrupts is signaled in the agent ACP descriptor. - -When an interrupt occurs, the server provides the client with an interrupt payload, which specifies the interrupt type that has occurred and all the information associated with that interrupt, i.e. a request for additional input. - -The client can collect the needed input for the specific interrupt and resume the run by providing the resume payload, i.e. the additional input requested by the interrupt. - -!!! note - Note that the type of interrupts and the correspondent interrupt and resume payload are not specified by ACP, because they are agent dependent. They are instead specified in the agent ACP descriptor. - -The interrupt is provided by the server when the client requests the output. - -#### Start a run and resume it upon interruption - -In this case, the client asks for the agent output and receives an interrupt instead of the final output. The client then resumes the run providing the needed input and finally when the run is completed, gets the result. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /runs {agent_id, input, config, metadata} - S->>-C: Run={run_id, status="pending"} - C->>+S: GET /runs/{run_id}/wait - S->>-C: RunOutput={type="interrupt", interrupt_type, interrupt_payload} - note over C: collect needed input - C->>+S: POST /runs/{run_id} {interrupt_type, resume_payload} - S->>-C: Run={run_id, status="pending"} - C->>+S: GET /runs/{run_id}/wait - S->>-C: RunOutput={type="result", result} -``` -In the sequence above: - -1. The client start the run. -1. The server returns the run object. -1. The client requests the output. -1. The server returns an interrupt, specifying interrupt type and the associated payload. -1. The client resumes the run providing the needed input in the resume payload. -1. the client requests the output. -1. The server returns the final result. - -### Thread Runs -Agents can support thread run. Support for thread run is signaled in the agent ACP descriptor. - -When an agent supports thread run, each run is associated to a thread, and at the end of the run a thread state is kept in the server. - -Subsequent runs on the same thread use the previously created state, together with the run input provided. - -The server offers ways to retrieve the current thread state, the history of the runs on a thread, and the evolution of the thread states over execution of runs. - -Runs over the same thread can be executed on different agents, as long as the agents support the same thread state format. - -!!! note - Note that the format of the thread state is not specified by ACP, but it is (optionally) defined in the agent ACP descriptor. If specified, it can be retrieved by the client, if not it's not accessible to the client. - -#### Start of multiple runs over the same thread -In this case the client starts a sequence of runs on the same thread accumulating a state in the server. In this specific example the input is a chat message, while the state kept in the server is the chat history. - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - rect rgb(240,240,240) - C->>+S: POST /threads - S->>-C: Thread={thread_id, status="idle"} - C->>+S: POST /threads/{thread_id}/runs {agent_id, message="Hello, my name is John?", config, metadata} - S->>-C: Run={run_id, status="pending"} - C->>+S: GET /threads/{thread_id}/runs/{run_id}/wait - S->>-C: RunOutput={type="result", result={"message"="Hello John, how can I help?"}} - end - note right of S: state=[
"Hello, my name is John?",
"Hello John, how can I help?"
] - rect rgb(240,240,240) - C->>+S: POST /threads/{thread_id}/runs {agent_id, message="Can you remind my name?", config, metadata} - S->>-C: Run={run_id, status="pending"} - C->>+S: GET /threads/{thread_id}/runs/{run_id}/wait - S->>-C: RunOutput={type="result", result={"message"="Yes, your name is John"}} - end - note right of S: state=[
"Hello, my name is John?",
"Hello John, how can I help?"
"Can you remind my name?",
"Yes, your name is John"
] - C->>+S: GET /threads/{thread_id} - S->>-C: Thread{thread_id, status="idle", values=[
"Hello, my name is John?",
"Hello John, how can I help?"
"Can you remind my name?",
"Yes, your name is John"
]} -``` -In the sequence above: - -1. The client requests to create a thread on the server -1. The server returns a thread object that contains a thread ID -1. The client starts the first run on the created thread and provides the first message of the chat. -1. The server returns the run object. -1. The client requests the run output. -1. The server returns the run output which is the next chat message from the agent and leaves a state with the current chat history. -1. The client starts a new run on the same thread providing the input for the run, i.e. the next message in the chat (assuming the existence of the chat history on the server). -1. The server starts the runs using the existing chat history and returns the run object. -1. The client requests the run output. -1. The server updates the thread state and returns the run output. -1. Finally, the client requests the thread state (this is an optional operation). -1. The server returns the current thread object which collects the whole chat history. - -### Output Streaming -ACP supports output streaming. Agent can stream partial results of a Run to provide better response time and user experience. - -ACP implements streaming using Server Sent Events specified [here](https://html.spec.whatwg.org/multipage/server-sent-events.html). - -In a nutshell, the client keeps the HTTP connection open and receives a stream of events from the server, where each event carries an update of the run result. - -ACP supports 2 streaming modes: - -1. **values** where each event contains a full instance of the agent output, which fully replace the previous update. -2. **custom** where the schema of the event is left unspecified by ACP, which it can be specified in the specific agent ACP descriptor under `spec.custom_streaming_update` - -#### Start a Run and stream output until completion - -```mermaid -sequenceDiagram - participant C as ACP Client - participant S as ACP Server - C->>+S: POST /runs/stream {agent_id, input, config, metadata, stream_mode='values'} - S->>-C: Run={run_id, status="pending"} - rect rgb(240,240,240) - S->>C: StreamEvent={id="1", event="agent_event", data={run_id, type="values", result={"message": "Hello"}}} - S->>C: StreamEvent={id="2", event="agent_event", data={run_id, type="values", result={"message": "Hello, how"}}} - S->>C: StreamEvent={id="2", event="agent_event", data={run_id, type="values", result={"message": "Hello, how can"}}} - S->>C: StreamEvent={id="3", event="agent_event", data={run_id, type="values", result={"message": "Hello, how can I help"}}} - S->>C: StreamEvent={id="4", event="agent_event", data={run_id, type="values", result={"message": "Hello, how can I help you"}}} - S->>C: StreamEvent={id="5", event="agent_event", data={run_id, type="values", result={"message": "Hello, how can I help you today"}}} - S->>C: Close Connection - end -``` - -In the sequence above: - -1. The client requests to start a run on a specific agent specifying stream_mode = 'values' and waits immediately for the streaming. -1. The client requests the output streaming and keeps the connection open. -1. The server returns an event with message="Hello". -1. The server returns an event with updated message "Hello, how". -1. The server returns an event with updated message "Hello, how can". -1. The server returns an event with updated message "Hello, how can I help". -1. The server returns an event with updated message "Hello, how can I help you". -1. The server returns an event with updated message "Hello, how can I help you today". -1. The server closes the connection because the output is complete. - -## Agent ACP descriptor - -Agent ACP Descriptor is a descriptor that contains all the needed information to know how: - -* [Identify an agent.](#agent-metadata) -* [Know its capabilities.](#agent-specs) -* Consume its capabilities. - -The Agent ACP Descriptor can be obtained from the Agent Directory or can be obtained through an [ACP call](#retrieve-agent-descriptor-from-its-identifier). - -### Agent descriptor sections and examples - -We present the details of a sample agent ACP descriptor through the various descriptor sections. - -??? "Full sample descriptor" - ``` - { - "metadata": { - "ref": { - "name": "org.agntcy.mailcomposer", - "version": "0.0.1", - "url": "https://github.com/agntcy/acp-spec/blob/main/docs/sample_acp_descriptors/mailcomposer.json" - }, - "description": "This agent is able to collect user intent through a chat interface and compose wonderful emails based on that." - }, - "specs": { - "capabilities": { - "threads": true, - "interrupts": true, - "callbacks": true - }, - "input": { - "type": "object", - "description": "Agent Input", - "properties": { - "message": { - "type": "string", - "description": "Last message of the chat from the user" - } - } - }, - "thread_state": { - "type": "object", - "description": "The state of the agent", - "properties": { - "messages": { - "type": "array", - "description": "Full chat history", - "items": { - "type": "string", - "description": "A message in the chat" - } - } - } - }, - "output": { - "type": "object", - "description": "Agent Input", - "properties": { - "message": { - "type": "string", - "description": "Last message of the chat from the user" - } - } - }, - "config": { - "type": "object", - "description": "The configuration of the agent", - "properties": { - "style": { - "type": "string", - "enum": ["formal", "friendly"] - } - } - }, - "interrupts": [ - { - "interrupt_type": "mail_send_approval", - "interrupt_payload": { - "type": "object", - "title": "Mail Approval Payload", - "description": "Description of the email", - "properties": { - "subject": { - "title": "Mail Subject", - "description": "Subject of the email that is about to be sent", - "type": "string" - }, - "body": { - "title": "Mail Body", - "description": "Body of the email that is about to be sent", - "type": "string" - }, - "recipients": { - "title": "Mail recipients", - "description": "List of recipients of the email", - "type": "array", - "items": { - "type": "string", - "format": "email" - } - } - }, - "required": [ - "subject", - "body", - "recipients" - ] - }, - "resume_payload": { - "type": "object", - "title": "Email Approval Input", - "description": "User Approval for this email", - "properties": { - "reason": { - "title": "Approval Reason", - "description": "Reason to approve or decline", - "type": "string" - }, - "approved": { - "title": "Approval Decision", - "description": "True if approved, False if declined", - "type": "boolean" - } - }, - "required": [ - "approved" - ] - } - } - ] - } - } - ``` - -#### Agent Metadata - -Agent Metadata section contains all the information about agent identification and a description of what the agent does. -It contains unique name which together with a version constitutes the unique identifier of the agent. The uniqueness must be guaranteed within the server it is part of and more generally in the Agent Directory domain it belongs to. - -???+ "Sample descriptor metadata section for the mailcomposer agent" - ``` - { - "metadata": { - "ref": { - "name": "org.agntcy.mailcomposer", - "version": "0.0.1", - "url": "https://github.com/agntcy/docs/blob/main/docs/pages/syntactic_sdk/sample_acp_descriptors/mailcomposer.json" - }, - "description": "This agent is able to collect user intent through a chat interface and compose wonderful emails based on that." - } - ... - } - ``` - -#### Agent Specs - -Agent Specs section includes ACP invocation capabilities and the schema definitions for ACP interactions. - -The ACP capabilities that the agent support, e.g. `streaming`, `callbacks`, `interrupts` etc. - -The schemas of all the objects that this agent supports for: - -* Agent Configuration. -* Run Input. -* Run Output. -* Interrupt and Resume Payloads. -* Thread State. - -!!! note - These schemas are needed in the agent ACP descriptor, since they are agent specific and are not defined by ACP, i.e. ACP defines a generic JSON object for the data structures listed above. - -??? "Sample metadata specs section for the mailcomposer agent" - ``` - { - ... - "specs": { - "capabilities": { - "threads": true, - "interrupts": true, - "callbacks": true - }, - "input": { - "type": "object", - "description": "Agent Input", - "properties": { - "message": { - "type": "string", - "description": "Last message of the chat from the user" - } - } - }, - "thread_state": { - "type": "object", - "description": "The state of the agent", - "properties": { - "messages": { - "type": "array", - "description": "Full chat history", - "items": { - "type": "string", - "description": "A message in the chat" - } - } - } - }, - "output": { - "type": "object", - "description": "Agent Input", - "properties": { - "message": { - "type": "string", - "description": "Last message of the chat from the user" - } - } - }, - "config": { - "type": "object", - "description": "The configuration of the agent", - "properties": { - "style": { - "type": "string", - "enum": ["formal", "friendly"] - } - } - }, - "interrupts": [ - { - "interrupt_type": "mail_send_approval", - "interrupt_payload": { - "type": "object", - "title": "Mail Approval Payload", - "description": "Description of the email", - "properties": { - "subject": { - "title": "Mail Subject", - "description": "Subject of the email that is about to be sent", - "type": "string" - }, - "body": { - "title": "Mail Body", - "description": "Body of the email that is about to be sent", - "type": "string" - }, - "recipients": { - "title": "Mail recipients", - "description": "List of recipients of the email", - "type": "array", - "items": { - "type": "string", - "format": "email" - } - } - }, - "required": [ - "subject", - "body", - "recipients" - ] - }, - "resume_payload": { - "type": "object", - "title": "Email Approval Input", - "description": "User Approval for this email", - "properties": { - "reason": { - "title": "Approval Reason", - "description": "Reason to approve or decline", - "type": "string" - }, - "approved": { - "title": "Approval Decision", - "description": "True if approved, False if declined", - "type": "boolean" - } - }, - "required": [ - "approved" - ] - } - } - ] - } - ... - } - ``` - - The agent supports threads, interrupts, and callback. - - It declares schemas for input, output, and config: - - * As input, it expects the next message of the chat from the user. - * As output, it produces the next message of the chat from the agent. - * As config it expects the style of the email to be written. - - It supports one kind of interrupt, which is used to ask user for approval before sending the email. It provides subject, body, and recipients of the email as interrupt payload and expects approval as input to resume. - - It supports a thread state which holds the chat history. - \ No newline at end of file diff --git a/docs/syntactic/hil.md b/docs/syntactic/hil.md deleted file mode 100644 index efb928d9..00000000 --- a/docs/syntactic/hil.md +++ /dev/null @@ -1,16 +0,0 @@ -# Human in the Loop Agent (Coming soon) - -In many cases, agentic applications require human input. - -Involving the human requires two things: - -* Interrupting a multi-application flow to wait for human input. This is provided by the different frameworks in different ways, but corresponds to pausing the application and resuming it when input is available. -* Engaging with the human to collect the input. This can happen in many ways and each application will have its own preferences. - -The Human in the Loop (HIL) Agent is an agent that implements most common methods to engage with humans. - -Few examples below: - -* Webhook: the agent calls a provided webhook to request for input and receive it through OpenAPI or REST. -* Email engagement: the agent sends an email and offers a web interface to provide input. -* Webex, Slack, or other engagement: the agent uses a messaging platform to request input. diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index 2f1e04fa..c46c62cc 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -78,7 +78,7 @@ plugins: - "../dir-*-v1-api/#*" # Build format raise_error: false raise_error_after_finish: false - validate_external_urls: false + validate_external_urls: true include-markdown: theme: