FastAPI#
The FastAPIAdapter
allows you to expose your FastAgency workflows as a REST API using the FastAPI framework.
Use Cases#
When to Use the FastAPIAdapter
:
-
Custom Client Applications: If you want to build your own client applications in a different language, (e.g., HTML/JavaScript, Go, Java, etc.), that interacts with your FastAgency workflows using REST API and WebSockets.
-
Moderate User Demand: This adapter is a good fit for scenarios where workflows need to be executed by multiple workers to efficiently handle higher machine load.
-
Simplified Production Setup: This adapter is a good choice when you need a simple and easy-to-manage setup for deploying FastAgency workflows as an ASGI server in production.
Architecture Overview#
This section provides high-level architecture diagrams for the two available setups of using FastAPI with:
The system consists of two main components:
Mesop Client App#
The client App serves as the frontend interface for the system. It includes:
MesopUI
: A friendly web interface for users to interact with the workflows. It facilitates the communication with the user and theFastAPIProvider
.FastAPIProvider
: A component that facilitates communication between the Mesop client and theFastAPIAdapter
.
This app handles all client interactions and presents the results back to the user.
FastAPI App#
The FastAPI App forms the backend of our system and consists of:
- AutoGen Workflows: These define the core logic and behavior of our application, utilizing agents to perform various tasks and achieve specific goals.
- The
FastAPIAdapter
: This component communicates with AutoGen, and implements routes and websocket for FastAPI. - FastAPI: Provides the infrastructure for building and exposing AutoGen workflows via REST API.
The system consists of two main components:
Custom Client App#
This application serves as the frontend interface for the system. It includes:
- Custom Client: A client application built in a different language, (e.g., HTML/JavaScript, Go, Java, etc.) facilitates communication between the user and the
FastAPIAdapter
.
This application handles all interactions with the FastAPIAdapter
and presents the results back to the user.
FastAPI App#
The FastAPI App forms the backend of our system and consists of:
- AutoGen Workflows: These define the core logic and behavior of our application, utilizing agents to perform various tasks and achieve specific goals.
- The
FastAPIAdapter
: This component communicates with AutoGen, and implements routes and websocket for FastAPI. - FastAPI: Provides the infrastructure for building and exposing AutoGen workflows via REST API.
Building Custom Client Applications#
To write a custom application that interacts with FastAPIAdapter
, it's essential to first understand the available server routes it provides and their purposes. This knowledge forms the foundation of the client-server interaction model.
Available API Endpoints#
FastAPIAdapter
provides three primary endpoints for building client applications:
Route | Method | Purpose |
---|---|---|
/fastagency/discovery | GET | Lists the available workflows that can be initiated |
/fastagency/initiate_workflow | POST | Starts a new workflow instance for the chosen workflow |
/fastagency/ws | WebSocket | Handles real-time workflow communication |
Now that we understand the available routes, let's visualize how these components interact in a typical client-server communication flow.
System Interaction Flow#
The following sequence diagram illustrates the step-by-step process of how a custom client application interacts with the FastAPIAdapter
:
sequenceDiagram
participant Client as Custom Client Application
participant FastAPI as FastAPIAdapter
Note over Client,FastAPI: 1. Setup & Discovery Phase
Client->>FastAPI: GET /fastagency/discovery
FastAPI-->>Client: Available Workflows
Client->>Client: Display workflow options to user
Note over Client,FastAPI: 2. Workflow Initiation
Client->>FastAPI: POST /fastagency/initiate_workflow
FastAPI-->>Client: Workflow Configuration
Note over Client,FastAPI: 3. WebSocket Connection
Client->>FastAPI: Initiate a WebSocket Connection (/fastagency/ws)
FastAPI-->>Client: WebSocket Connection Established
Note over Client,FastAPI: 4. Real-time Communication
Client->>FastAPI: Send Initial WebSocket Message
FastAPI-->>Client: Acknowledge Connection
activate FastAPI
activate Client
Note right of FastAPI: Message Processing Loop
FastAPI->>Client: Send Workflow Message
Client->>FastAPI: Send Response If Required
FastAPI->>Client: Send Next Workflow Message
deactivate Client
deactivate FastAPI
To better understand this diagram, let's break down the key steps involved in the client-server interaction:
Understanding the Flow#
The interaction between client and server follows these key steps:
- Discovery: Client fetches available workflows from the server.
- Selection: User selects a workflow to execute.
- Initiation: Client requests to start the chosen workflow.
- Connection: WebSocket connection established for real-time communication. These includes:
- Server sending workflow message to the client.
- Client sends optional response to server if previous server message requires user input.
- Server processes and sends the next workflow message.
Message Types#
Before diving into the implementation, we need to learn a bit about the message types that FastAPIAdapter
provides. Understanding these will help us handle messages in our custom client and display them properly to the users.
FastAgency tags each message sent from the server to the client over WebSockets with a type
attribute. This helps the client differentiate between different types of messages and handle them accordingly. Let’s break them down into two categories:
Messages for Display:
text_message
: A basic text message from the server, intended for display to the user. It doesn’t require any action from the user, serving purely as information or status updates.workflow_started
: This message indicates the start of a new workflow. The message includes the workflow’s name and a description along with other details.workflow_completed
: This message signals that the current workflow has been successfully completed. The client can use this to notify the user or transition to the next step in the application.suggested_function_call
: Indicates that the LLM has suggested a function call.function_call_execution
: Indicates that the LLM has executed the suggested function call.error
: Indicates that an error occurred during the workflow. The client can handle this by displaying an error message or prompting the user to retry the action.
Messages That Require User Response:
text_input
: This message prompts the client to gather input from the user. It could be a question or request for data. The client should provide a way for the user to respond (e.g., a text input or text area) and then send the response back to the server.multiple_choice
: This message requires the user to choose from a predefined set of options provided by the LLM. The client should present these options (e.g., checkboxes or radio buttons) and submit the user’s selection back to the server.
A full list of message types and their detailed usage will be available soon in the FastAgency Adapter’s OpenAPI specification—stay tuned!
Implementation Guide#
In the following sections, we'll walk through the process of creating a custom client application that implements the flow we've just described. We'll build a simple web-based client that demonstrates how to interact with FastAPIAdapter
effectively.
Our implementation will cover these key aspects:
- Fetching and displaying available workflows.
- Handling workflow initiation.
- Managing WebSockets connection.
- Processing real-time messages.
Note
Before we examine the code:
- The below example uses a simple HTML with JavaScript, all in a single string and served directly from the FastAPI App for simplicity.
- This approach is not suitable for production but ideal for demonstrating core concepts.
- In a real-world scenario, you'd use a separate frontend, built with frameworks like React or Vue.js, or other languages such as Java, Go, or Ruby, based on your project needs.
Let's begin by looking at the code structure and then break down each component.
Installation#
We strongly recommend using Cookiecutter for setting up the project. Cookiecutter creates the project folder structure, default workflow, automatically installs all the necessary requirements, and creates a devcontainer that can be used with Visual Studio Code.
You can setup the project using Cookiecutter by following the project setup guide.
Alternatively, you can use pip + venv.
Before getting started, ensure that FastAgency is installed with support for the AutoGen runtime, along with the mesop, fastapi, and server submodules by running the following command:
This command installs FastAgency with support for both the mesop and console interfaces for AutoGen workflows, but with FastAPI serving input requests and running workflows.
Example: Student and Teacher Learning Chat#
In this example, we'll create a simple learning chatbot where a student agent asks questions and a teacher agent responds, simulating a learning environment. We'll use MesopUI
for the web interface and the FastAPIAdapter
to expose the workflow as a REST API.
In this example, we'll create a simple learning chatbot where a student agent asks questions and a teacher agent responds, simulating a learning environment. We'll create a custom client using HTML and JavaScript for the web interface and the FastAPIAdapter
to expose the workflow as a REST API.
Step-by-Step Breakdown#
1. Define Workflow#
To get started, define the workflow that your application will use. This is where you specify how the agents interact and what they do. Here's a simple example of a workflow definition:
import os
from typing import Any
from autogen.agentchat import ConversableAgent
from fastagency import UI
from fastagency.runtimes.autogen import AutoGenWorkflows
llm_config = {
"config_list": [
{
"model": "gpt-4o-mini",
"api_key": os.getenv("OPENAI_API_KEY"),
}
],
"temperature": 0.8,
}
wf = AutoGenWorkflows()
@wf.register(name="simple_learning", description="Student and teacher learning chat") # type: ignore[misc]
def simple_workflow(ui: UI, params: dict[str, Any]) -> str:
initial_message = ui.text_input(
sender="Workflow",
recipient="User",
prompt="I can help you learn about mathematics. What subject you would like to explore?",
)
student_agent = ConversableAgent(
name="Student_Agent",
system_message="You are a student willing to learn.",
llm_config=llm_config,
)
teacher_agent = ConversableAgent(
name="Teacher_Agent",
system_message="You are a math teacher.",
llm_config=llm_config,
)
chat_result = student_agent.initiate_chat(
teacher_agent,
message=initial_message,
summary_method="reflection_with_llm",
max_turns=3,
)
return str(chat_result.summary)
2. Import Required Modules#
Next, import the required modules from the FastAgency. Import the FastAPIAdapter
class to expose the workflows as a REST API.
Next, import the required modules from the FastAgency and AutoGen. These imports provide the essential building blocks for creating agents, workflows, and integrating with the custom client. Additionally, import the FastAPIAdapter
and HTMLResponse
class to expose the workflows as a REST API.
3. Define FastAgency Application#
Create an instance of the FastAPIAdapter
and pass your workflow to it. Then, include a router to the FastAPI application. The adapter will have all REST API and WebSocket routes for communicating with the client.
4. Adapter Chaining#
Finally, create an additional specification file for an application using MesopUI
to connect to the FastAPIAdapter
.
main_2_mesop.py
4. Serving the Custom HTML Client#
Finally, use the HTML Response from FastAPI to serve the custom client code.
html = """
<!DOCTYPE html>
<html>
<head>
<title>FastAgency Chat App</title>
</head>
<body>
<h1>FastAgency Chat App</h1>
<div id="workflows"></div>
<ul id="messages"></ul>
<script>
const API_URL = 'http://127.0.0.1:8008/fastagency';
const WS_URL = 'ws://127.0.0.1:8008/fastagency/ws'; // nosemgrep
let socket;
async function fetchWorkflows() {
const response = await fetch(`${API_URL}/discovery`);
const workflows = await response.json();
const container = document.getElementById('workflows');
workflows.forEach(workflow => {
const button = document.createElement('button');
button.textContent = workflow.description;
button.onclick = () => startWorkflow(workflow.name);
container.appendChild(button);
});
}
async function startWorkflow(name) {
const payload = {
workflow_name: name,
workflow_uuid: generateUUID(),
user_id: null, // Set to null for single-user applications; otherwise, provide the appropriate user ID
params: {}
};
const response = await fetch(`${API_URL}/initiate_workflow`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const workflowJson = await response.json();
connectWebSocket(workflowJson);
}
function connectWebSocket(workflowJson) {
socket = new WebSocket(WS_URL);
socket.onopen = () => {
const initMessage = {
name: workflowJson.name,
workflow_uuid: workflowJson.workflow_uuid,
user_id: workflowJson.user_id,
params: {}
};
socket.send(JSON.stringify(initMessage));
};
socket.onmessage = (event) => handleMessage(JSON.parse(event.data));
}
function handleMessage(message) {
const messagesList = document.getElementById('messages');
const li = document.createElement('li');
if (message.type === 'text_input') {
const response = prompt(message.content.prompt);
socket.send(response);
li.textContent = `${message.sender} -> ${message.recipient}: ${message.content.prompt}`;
} else {
li.textContent = `${message.sender} -> ${message.recipient}: ${message.content?.body || message?.type || JSON.stringify(message)}`;
}
messagesList.appendChild(li);
}
fetchWorkflows();
// Helper function for generating UUID
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
if (c === 'x') {
return (Math.random() * 16 | 0).toString(16);
} else {
return (Math.random() * 16 | 0 & 0x3 | 0x8).toString(16);
}
});
}
</script>
</body>
</html>
"""
@app.get("/")
async def get() -> HTMLResponse:
return HTMLResponse(html)
Complete Application Code#
Please copy and paste the following code into the same folder, using the file names exactly as mentioned below.
workflow.py
import os
from typing import Any
from autogen.agentchat import ConversableAgent
from fastagency import UI
from fastagency.runtimes.autogen import AutoGenWorkflows
llm_config = {
"config_list": [
{
"model": "gpt-4o-mini",
"api_key": os.getenv("OPENAI_API_KEY"),
}
],
"temperature": 0.8,
}
wf = AutoGenWorkflows()
@wf.register(name="simple_learning", description="Student and teacher learning chat") # type: ignore[misc]
def simple_workflow(ui: UI, params: dict[str, Any]) -> str:
initial_message = ui.text_input(
sender="Workflow",
recipient="User",
prompt="I can help you learn about mathematics. What subject you would like to explore?",
)
student_agent = ConversableAgent(
name="Student_Agent",
system_message="You are a student willing to learn.",
llm_config=llm_config,
)
teacher_agent = ConversableAgent(
name="Teacher_Agent",
system_message="You are a math teacher.",
llm_config=llm_config,
)
chat_result = student_agent.initiate_chat(
teacher_agent,
message=initial_message,
summary_method="reflection_with_llm",
max_turns=3,
)
return str(chat_result.summary)
main_1_fastapi.py
from typing import Any
from fastagency.adapters.fastapi import FastAPIAdapter
from fastapi import FastAPI
from ..workflow import wf
adapter = FastAPIAdapter(provider=wf)
app = FastAPI()
app.include_router(adapter.router)
# this is optional, but we would like to see the list of available workflows
@app.get("/")
def list_workflows() -> dict[str, Any]:
return {"Workflows": {name: wf.get_description(name) for name in wf.names}}
# start the adapter with the following command
# uvicorn my_fastagency_app.deployment.main_1_fastapi:app --reload
main_2_mesop.py
from fastagency.adapters.fastapi import FastAPIAdapter
from fastagency.app import FastAgency
from fastagency.ui.mesop import MesopUI
fastapi_url = "http://localhost:8008"
provider = FastAPIAdapter.create_provider(
fastapi_url=fastapi_url,
)
ui = MesopUI()
app = FastAgency(
provider=provider,
ui=ui,
title="My FastAgency App",
)
# start the provider with the following command
# gunicorn my_fastagency_app.deployment.main_2_mesop:app -b 0.0.0.0:8888 --reload
main_fastapi_custom_client.py
import os
from typing import Any
from autogen.agentchat import ConversableAgent
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastagency import UI
from fastagency.adapters.fastapi import FastAPIAdapter
from fastagency.runtimes.autogen import AutoGenWorkflows
html = """
<!DOCTYPE html>
<html>
<head>
<title>FastAgency Chat App</title>
</head>
<body>
<h1>FastAgency Chat App</h1>
<div id="workflows"></div>
<ul id="messages"></ul>
<script>
const API_URL = 'http://127.0.0.1:8008/fastagency';
const WS_URL = 'ws://127.0.0.1:8008/fastagency/ws'; // nosemgrep
let socket;
async function fetchWorkflows() {
const response = await fetch(`${API_URL}/discovery`);
const workflows = await response.json();
const container = document.getElementById('workflows');
workflows.forEach(workflow => {
const button = document.createElement('button');
button.textContent = workflow.description;
button.onclick = () => startWorkflow(workflow.name);
container.appendChild(button);
});
}
async function startWorkflow(name) {
const payload = {
workflow_name: name,
workflow_uuid: generateUUID(),
user_id: null, // Set to null for single-user applications; otherwise, provide the appropriate user ID
params: {}
};
const response = await fetch(`${API_URL}/initiate_workflow`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const workflowJson = await response.json();
connectWebSocket(workflowJson);
}
function connectWebSocket(workflowJson) {
socket = new WebSocket(WS_URL);
socket.onopen = () => {
const initMessage = {
name: workflowJson.name,
workflow_uuid: workflowJson.workflow_uuid,
user_id: workflowJson.user_id,
params: {}
};
socket.send(JSON.stringify(initMessage));
};
socket.onmessage = (event) => handleMessage(JSON.parse(event.data));
}
function handleMessage(message) {
const messagesList = document.getElementById('messages');
const li = document.createElement('li');
if (message.type === 'text_input') {
const response = prompt(message.content.prompt);
socket.send(response);
li.textContent = `${message.sender} -> ${message.recipient}: ${message.content.prompt}`;
} else {
li.textContent = `${message.sender} -> ${message.recipient}: ${message.content?.body || message?.type || JSON.stringify(message)}`;
}
messagesList.appendChild(li);
}
fetchWorkflows();
// Helper function for generating UUID
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
if (c === 'x') {
return (Math.random() * 16 | 0).toString(16);
} else {
return (Math.random() * 16 | 0 & 0x3 | 0x8).toString(16);
}
});
}
</script>
</body>
</html>
"""
llm_config = {
"config_list": [
{
"model": "gpt-4o-mini",
"api_key": os.getenv("OPENAI_API_KEY"),
}
],
"temperature": 0.8,
}
wf = AutoGenWorkflows()
@wf.register(name="simple_learning", description="Student and teacher learning chat")
def simple_workflow(ui: UI, params: dict[str, Any]) -> str:
initial_message = ui.text_input(
sender="Workflow",
recipient="User",
prompt="I can help you learn about mathematics. What subject you would like to explore?",
)
student_agent = ConversableAgent(
name="Student_Agent",
system_message="You are a student willing to learn.",
llm_config=llm_config,
# human_input_mode="ALWAYS",
)
teacher_agent = ConversableAgent(
name="Teacher_Agent",
system_message="You are a math teacher.",
llm_config=llm_config,
# human_input_mode="ALWAYS",
)
chat_result = student_agent.initiate_chat(
teacher_agent,
message=initial_message,
summary_method="reflection_with_llm",
max_turns=5,
)
return chat_result.summary # type: ignore[no-any-return]
adapter = FastAPIAdapter(provider=wf)
app = FastAPI()
app.include_router(adapter.router)
@app.get("/")
async def get() -> HTMLResponse:
return HTMLResponse(html)
# start the provider with the following command
# uvicorn main_fastapi_custom_client:app --port 8008 --reload
Run Application#
In this setup, we need to run two commands in separate terminal windows:
- Start FastAPI application using uvicorn:
- Start Mesop web interface using gunicorn:
First, install the package using package manager such as pip
and then run it:
- Start FastAPI application using uvicorn:
- Start Mesop web interface using gunicorn:
Once everything is set up, you can run your FastAgency application using the following command.
- Start FastAPI application using uvicorn:
Output#
The outputs will vary based on the interface. Here is the output of the last terminal starting the UI:
INFO: Will watch for changes in these directories: ['/tmp/custom_fastapi_demo']
INFO: Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit)
INFO: Started reloader process [73937] using StatReload
INFO: Started server process [73940]
INFO: Waiting for application startup.
INFO: Application startup complete.
The FastAPIAdapter
provides a powerful solution for developers seeking a user-friendly and efficient way to expose their FastAgency workflows as REST API, contributing to building production-ready applications.