Merge pull request #62 from rowboatlabs/dev

dev changes
This commit is contained in:
Ramnique Singh 2025-04-10 00:28:47 +05:30 committed by GitHub
commit 45e09a8f32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 779 additions and 1379 deletions

View file

@ -1,6 +1,5 @@
# Basic configuration # Basic configuration
# ------------------------------------------------------------ # ------------------------------------------------------------
MONGODB_CONNECTION_STRING=mongodb://host.docker.internal:27017/rowboat
OPENAI_API_KEY=<OPENAI_API_KEY> OPENAI_API_KEY=<OPENAI_API_KEY>

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.DS_Store .DS_Store
.env .env
.vscode/ .vscode/
data/

439
README.md
View file

@ -1,339 +1,62 @@
![ui](/assets/banner.png) ![ui](/assets/banner.png)
<h2 align="center">The AI-assisted agent builder</h2> <h2 align="center">Let AI build multi-agent workflows for you in minutes</h2>
<h5 align="center"> <h5 align="center">
[Quickstart](#quick-start) | [Docs](https://docs.rowboatlabs.com/) | [Website](https://www.rowboatlabs.com/) | [Discord](https://discord.gg/jHhUKkKHn8) [Quickstart](#quick-start) | [Docs](https://docs.rowboatlabs.com/) | [Website](https://www.rowboatlabs.com/) | [Discord](https://discord.gg/jHhUKkKHn8)
</h5> </h5>
A Cursor-like, AI-assisted, no-code IDE for building production-ready multi-agents. - ✨ **Start from an idea -> copilot builds your multi-agent workflows**
- E.g. "Build me an assistant for a food delivery company to handle delivery status and missing items. Include the necessary tools."
- 🌐 **Connect MCP servers**
- Add the MCP servers in settings -> import the tools into Rowboat.
- 📞 **Integrate into your app using the HTTP API**
- Grab the project ID and generated API key from settings and use the API.
- ✨ Start from a simple prompt to create fully functional agents with the Copilot Powered by OpenAI's Agents SDK, Rowboat is the fastest way to build multi-agents!
- 🧪 Test them in AI-simulated scenarios
- 🌐 Connect MCP servers and tools
- 📞 Interact through the Python SDK, a web widget, or a Twilio phone number
- ♻️ Continuously refine your agents by providing feedback to the Copilot
## Quick start
1. Set your OpenAI key
```bash
export OPENAI_API_KEY=your-openai-api-key
```
2. Clone the repository and start Rowboat docker
Built on OpenAI's Agents SDK, **Rowboat is the fastest way to build multi-agents!**
![ui](/assets/ui_revamp_screenshot.png)
# Quick start
## Prerequisites
Before running Rowboat, ensure you have:
1. **Docker Desktop**
- [Download Docker Desktop](https://www.docker.com/products/docker-desktop)
2. **OpenAI API Key**
- Obtain from your OpenAI account.
3. **MongoDB**
- macOS (Homebrew)
```bash
brew tap mongodb/brew
brew install mongodb-community@8.0
brew services start mongodb-community@8.0
```
- Other platforms: Refer to the [MongoDB documentation](https://www.mongodb.com/docs/manual/installation/) for details.
## Setup Rowboat
1. **Clone the Repository**
```bash ```bash
git clone git@github.com:rowboatlabs/rowboat.git git clone git@github.com:rowboatlabs/rowboat.git
cd rowboat cd rowboat
```
2. **Environment Configuration**
- Copy the `.env.example` file and rename it to `.env`:
```bash
cp .env.example .env
```
- Open the new .env file and update the OPENAI_API_KEY:
```ini
# OpenAI Configuration
OPENAI_API_KEY=your-openai-api-key
```
3. **Start the App**
```bash
docker-compose up --build docker-compose up --build
``` ```
4. **Access the App** 3. Access the app at [http://localhost:3000](http://localhost:3000).
- Visit [http://localhost:3000](http://localhost:3000).
## Demos
#### Create a multi-agent assistant with tools from a single prompt
[![Prompt to agents](https://img.youtube.com/vi/3t2Fpn6Vyds/0.jpg)](https://www.youtube.com/watch?v=3t2Fpn6Vyds)
#### Add MCP servers
[![MCP server](https://img.youtube.com/vi/EbkIPCTyD58/0.jpg)](https://www.youtube.com/watch?v=EbkIPCTyD58)
#### Use Firecrawl's MCP server and build a quick url scraping agent
[![Firecrawl MCP](https://img.youtube.com/vi/_KZWla3Khco/0.jpg)](https://www.youtube.com/watch?v=_KZWla3Khco)
#### Improve agents with feedback
[![Feedback](https://img.youtube.com/vi/uoCEQtOe7eE/0.jpg)](https://www.youtube.com/watch?v=uoCEQtOe7eE)
Refer to [Docs](https://docs.rowboatlabs.com/) to learn how to start building agents with Rowboat. ## Integrate with Rowboat agents
# Advanced There are 2 ways to integrate with the agents you create in Rowboat
## 1. Tool Use
You can add your tools / APIs to Rowboat through (a) connecting MCP servers, or (b) connecting a webhook.
### 1.1 MCP Servers
You can intergrate any MCP server in Settings -> Tools -> MCP Servers. The Tools on the servers will show up inside Rowboats Tools section.
<img src="/assets/mcp-import.png" alt="ui" width="400"/>
Tip: You might want to set the MCP url as http://host.docker.internal/... to allow services to access the MCP servers on your localhost.
### 1.2 Webhook
You can point Rowboat to any webhook in Settings -> Tools -> Webhook.
Rowboat also includes a built-in webhook service that allows you to implement custom tool functions easily. To use this feature:
1. **Generate Signing Secret**
Generate a secret for securing webhook requests:
```bash
openssl rand -hex 32
```
2. **Update Environment Variables**
```ini
SIGNING_SECRET=<your-generated-secret>
```
3. **Implement Your Functions**
Add your custom functions to `apps/tools_webhook/function_map.py`:
```python
def get_weather(location: str, units: str = "metric"):
"""Return weather data for the given location."""
# Your implementation here
return {"temperature": 20, "conditions": "sunny"}
def check_inventory(product_id: str):
"""Check inventory levels for a product."""
# Your implementation here
return {"in_stock": 42, "warehouse": "NYC"}
# Add your functions to the map
FUNCTIONS_MAP = {
"get_weather": get_weather,
"check_inventory": check_inventory
}
```
4. **Start the Tools Webhook Service**
```bash
docker compose --profile tools_webhook up -d
```
5. **Register Tools in Rowboat**
- Navigate to your project config at `/projects/<PROJECT_ID>/config`
- Ensure that the webhook URL is set to: `http://tools_webhook:3005/tool_call`
- Tools will automatically be forwarded to your webhook implementation
The webhook service handles all the security and parameter validation, allowing you to focus on implementing your tool logic.
## 2. Retrieve Augmented Generation (RAG)
Rowboat supports adding text directly, document uploads or scraping URLs to enhance the responses with your custom knowledge base. Rowboat uses Qdrant as the vector DB.
### 2.1 Setup Qdrant
To enable RAG you need to first setup Qdrant.
1. Option1: Run Qdrant locally
- Run Qdrant docker
```bash
docker run -p 6333:6333 qdrant/qdrant
```
- Update environment variables
```ini
USE_RAG=true
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=<your-api-key> # Only needed for Qdrant Cloud
```
2. Option2: Use [Qdrant Cloud](https://cloud.qdrant.io/)
- Note your cluster URL and API key
- Update environment variables
```ini
USE_RAG=true
QDRANT_URL=<your-qdrant-cloud-url>
QDRANT_API_KEY=<your-api-key>
```
3. Initialize Qdrant Collections
```bash
docker compose --profile setup_qdrant up setup_qdrant
```
If you need to delete the collections and start fresh, you can run:
```bash
docker compose --profile delete_qdrant up delete_qdrant
```
### 2.2 Adding Knowledge Base for RAG
You can add a knowledge corpus to Rowboat by directly adding text information, uploading supported files or by pointing Rowboat to URLs for scraping.
#### (a) Create Text for Knowledge
Rowboat support directly creating a corpus of knowledge inside the platform.
- Start the Text Worker
```bash
docker compose --profile rag_text_worker up -d
```
#### (b) Scrape URLs for Knowledge
Rowboat supports scraping urls using Firecrawl. To setup scraping:
1. Get Firecrawl API Key
- Sign up at [Firecrawl](https://firecrawl.co)
- Generate an API key
2. Update Environment Variables
```ini
USE_RAG_SCRAPING=true
FIRECRAWL_API_KEY=<your-firecrawl-api-key>
```
3. Start the URLs Worker
```bash
docker compose --profile rag_urls_worker up -d
```
#### (c) Upload Files for Knowledge
Rowboat supports file uploads (PDF, DOCX, TXT) for your knowledge base. It uses Google's Gemini LLM to convert the documents to Markdown before indexing:
1. Prerequisites
- An AWS S3 bucket for file storage
- Google Cloud API key with Generative Language (Gemini) API enabled (for enhanced document parsing)
2. Configure AWS S3
- Create an S3 bucket
- Add the following CORS configuration to your bucket:
```json
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT",
"POST",
"DELETE",
"GET"
],
"AllowedOrigins": [
"http://localhost:3000",
],
"ExposeHeaders": [
"ETag"
]
}
]
```
- Ensure your AWS credentials have the following IAM policy:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<your-bucket-name>/*",
"arn:aws:s3:::<your-bucket-name>"
]
}
]
}
```
3. Update Environment Variables
```ini
USE_RAG_UPLOADS=true
AWS_ACCESS_KEY_ID=<your-aws-access-key>
AWS_SECRET_ACCESS_KEY=<your-aws-secret-key>
RAG_UPLOADS_S3_BUCKET=<your-s3-bucket-name>
RAG_UPLOADS_S3_REGION=<your-s3-region>
GOOGLE_API_KEY=<your-google-api-key>
```
4. Start the Files Worker
```bash
docker compose --profile rag_files_worker up -d
```
After enabling RAG and starting the required workers, you can manage your knowledge base through the Rowboat UI at `/projects/<PROJECT_ID>/sources`.
## 3. Chat Widget
Rowboat provides an embeddable chat widget that you can add to any website. To enable and use the chat widget:
1. **Generate JWT Secret**
Generate a secret for securing chat widget sessions:
```bash
openssl rand -hex 32
```
2. **Update Environment Variables**
```ini
USE_CHAT_WIDGET=true
CHAT_WIDGET_SESSION_JWT_SECRET=<your-generated-secret>
```
3. **Start the Chat Widget Service**
```bash
docker compose --profile chat_widget up -d
```
4. **Add Widget to Your Website**
You can find the chat-widget embed code under `/projects/<PROJECT_ID>/config`
After setup, the chat widget will appear on your website and connect to your Rowboat project.
## 4. Interact with Rowboat API
There are two ways to interact with Rowboat's API:
1. **Option 1: Python SDK**
For Python applications, we provide an official SDK for easier integration:
```bash
pip install rowboat
```
```python
from rowboat import Client
client = Client(
host="http://localhost:3000",
project_id="<PROJECT_ID>",
api_key="<API_KEY>" # Generate this from /projects/<PROJECT_ID>/config
)
# Simple chat interaction
messages = [{"role": "user", "content": "Tell me the weather in London"}]
response_messages, state = client.chat(messages=messages)
```
For more details, see the [Python SDK documentation](./apps/python-sdk/README.md).
1. **Option 2: HTTP API**
You can use the API directly at [http://localhost:3000/api/v1/](http://localhost:3000/api/v1/)
- Project ID is available in the URL of the project page
- API Key can be generated from the project config page at `/projects/<PROJECT_ID>/config`
1. HTTP API
- You can use the API directly at [http://localhost:3000/api/v1/](http://localhost:3000/api/v1/)
- See [API Docs](https://docs.rowboatlabs.com/using_the_api/) for details
```bash ```bash
curl --location 'http://localhost:3000/api/v1/<PROJECT_ID>/chat' \ curl --location 'http://localhost:3000/api/v1/<PROJECT_ID>/chat' \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
@ -347,61 +70,37 @@ There are two ways to interact with Rowboat's API:
] ]
}' }'
``` ```
which gives:
```json
{ 2. Python SDK
"messages": [ - You can use the included Python SDK to interact with the Agents
{ - See [SDK Docs](https://docs.rowboatlabs.com/using_the_sdk/) for details
"role": "assistant", ```python
"tool_calls": [ from rowboat import Client
{ from rowboat.schema import UserMessage, SystemMessage
"function": {
"arguments": "{\"location\":\"London\",\"units\":\"metric\"}", # Initialize the client
"name": "weather_lookup_tool" client = Client(
}, host="<HOST>",
"id": "call_r6XKuVxmGRogofkyFZIacdL0", project_id="<PROJECT_ID>",
"type": "function" api_key="<API_KEY>"
} )
],
"agenticSender": "Example Agent", # Create messages
"agenticResponseType": "internal" messages = [
} SystemMessage(role='system', content="You are a helpful assistant"),
], UserMessage(role='user', content="Hello, how are you?")
"state": { ]
// .. state data
} # Get response
} response_messages, state = client.chat(messages=messages)
print(response_messages[-1].content)
# For subsequent messages, include previous messages and state
messages.extend(response_messages)
messages.append(UserMessage(role='user', content="What's your name?"))
response_messages, state = client.chat(messages=messages, state=state)
``` ```
### 5. Authentication
By default, Rowboat runs without authentication. To enable user authentication using Auth0:
1. **Auth0 Setup**
- **Create an Auth0 Account**: Sign up at [Auth0](https://auth0.com).
- **Create a New Application**: Choose "Regular Web Application", select "Next.js" as the application type, and name it "Rowboat".
- **Configure Application**:
- **Allowed Callback URLs**: In the Auth0 Dashboard, go to your "Rowboat" application settings and set `http://localhost:3000/api/auth/callback` as an Allowed Callback URL.
- **Get Credentials**: Collect the following from your Auth0 application settings:
- **Domain**: Copy your Auth0 domain (ensure you append `https://` to the Domain that the Auth0 dashboard shows you)
- **Client ID**: Your application's unique identifier
- **Client Secret**: Your application's secret key
- **Generate secret**: Generate a session encryption secret in your terminal and note the output for later:
```bash
openssl rand -hex 32
```
2. **Update Environment Variables**
Add the following to your `.env` file:
```ini
USE_AUTH=true
AUTH0_SECRET=your-generated-secret # Generated using openssl command
AUTH0_BASE_URL=http://localhost:3000 # Your application's base URL
AUTH0_ISSUER_BASE_URL=https://example.auth0.com # Your Auth0 domain (ensure it is prefixed with https://)
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
```
After enabling authentication, users will need to sign in to access the application.
Refer to [Docs](https://docs.rowboatlabs.com/) to learn how to start building agents with Rowboat.

View file

@ -1,19 +1,19 @@
## Add tools to agents ## Add tools to agents
Copilot can help you add tools to agents. Copilot can help you add tools to agents. You can (a) add a mock tool, (b) add a tool from an MCP server, (c) integrate with you own tools using a webhook.
### Instruct copilot to add tools
![Add Tool](img/add-tool.png)
![Example Tool](img/example-tool.png) ### Adding mock tools
You can mock any tool you have created by checking the 'Mock tool responses' option.
### Inspect tools and agents
Note how copilot not only creates the tool definitions for you, but also updates the relevant agent instructions to use the tool and connects the tool to the agent.
![Inspect Agent Tools](img/inspect-agent-tools.png) ![Example Tool](img/mock-tool.png)
![Inspect Agent Tool Connections](img/inspect-agent-tool-connections.png)
### Adding MCP tools
[![MCP server](https://img.youtube.com/vi/EbkIPCTyD58/0.jpg)](https://www.youtube.com/watch?v=EbkIPCTyD58)
### Debug tool calls in the playground ### Debug tool calls in the playground
When agents call tools during a chat in the playground, the tool call parameters and response are available for debugging real-time. For testing purposes, the platform can produce mock tool responses in the playground, without integrating actual tools. When agents call tools during a chat in the playground, the tool call parameters and response are available for debugging real-time. For testing purposes, the platform can produce mock tool responses in the playground, without integrating actual tools.
![Mock Tool Responses](img/mock-tool-responses.png) ![Mock Tool Responses](img/mock-response.png)
![Debug Tool Calls](img/debug-tool-calls.png)

View file

@ -19,21 +19,11 @@ The agent uses examples as a reference for behavior in different scenarios. Whil
### Prompts ### Prompts
Prompts attached to an agent will be used by the agent in addition to instructions. Prompts attached to an agent will be used by the agent in addition to instructions.
### RAG
Data sources added to an agent will be used as knowledge, retrieved using embedding match in a typical RAG fashion. Advanced configurations allow for setting number of matches, etc. RAG is currently implemented as a predefined tool call which the agent will use when it determines that it needs to retrieve knowledge. This behavior can be further fine-tuned by specifying corresponding instructions or prompts.
### Tools ### Tools
Tools attached to an agent will be put out as tool calls. The behavior of when to invoke tools can be fine-tuned by specifying corresponding instructions or prompts. Adding examples to agents can also be useful in controlling tool call behavior. Tools attached to an agent will be put out as tool calls. The behavior of when to invoke tools can be fine-tuned by specifying corresponding instructions or prompts. Adding examples to agents can also be useful in controlling tool call behavior.
### Connected Agents ### Connected Agents
In the agent graph, connected agents refer to children of an agent. An agent can choose to transfer control of the conversation to one of its children, by using internal tool calls (need not be configured separately). Similar to tools, the behavior of when to transfer the chat to a child agent can be fine-tuned by specifying corresponding instructions, examples and prompts. In the agent instructions, the connected agents are shown with an '@mention'. If the agent mentioned in an instruction (connected agent) does not actually exist, the connected agent's name would show up with an '!' to call to attention.
### Model ### Model
RowBoat currently supports OpenAI LLMs. Agents can be configured to use any of the OpenAI LLMs. RowBoat currently supports OpenAI LLMs. Agents can be configured to use GPT-4o or GPT-4o-mini.
### Conversation control after turn
This setting specifies different options for control of conversation after the current agent has put out a user-facing response (i.e., completed the turn). Currently available options are:
1. Retain control for the next turn of conversation (most common and default setting)
2. Give up control to the parent agent (used when the agent has narrow scope such as answering a FAQ)
3. Give up control to the agent designated as Start agent

View file

@ -4,20 +4,17 @@ Copilot can set up agents for you from scratch.
### Instruct copilot ### Instruct copilot
First, tell it about the initial set of agents that make up your assistant. First, tell it about the initial set of agents that make up your assistant.
![Create Initial Agents](img/copilot-create.png) [![Prompt to agents](https://img.youtube.com/vi/3t2Fpn6Vyds/0.jpg)](https://www.youtube.com/watch?v=3t2Fpn6Vyds)
Using copilot to create your initial set of agents helps you leverage best practices in formatting agent instructions and connecting agents to each other as a graph, all of which have been baked into copilot. Using copilot to create your initial set of agents helps you leverage best practices in formatting agent instructions and connecting agents to each other as a graph, all of which have been baked into copilot.
### Inspect the agents ### Inspect the agents
Once you apply changes, inspect the agents to see how copilot has built them. Specifically, note the Instructions, Examples and Connected Agents in each agent. Once you apply changes, inspect the agents to see how copilot has built them. Specifically, note the Instructions, and Examples in each agent.
![Agent Config](img/agent-config.png) ![Agent Config](img/agent-instruction.png)
Also notice that copilot would likely have created a "Hub" agent that is "connected" to other agents.
![Hub Agent Config](img/hub-config.png)
### Make changes if needed ### Make changes if needed
Tweak the instructions and examples manually if needed. Tweak the instructions and examples through the copilot, or generate instructions button, or by manually editing it.
![Edit Agent Manually](img/edit-agent-manually.png) [![Feedback](https://img.youtube.com/vi/uoCEQtOe7eE/0.jpg)](https://www.youtube.com/watch?v=uoCEQtOe7eE)

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

View file

@ -1,8 +1,8 @@
# Welcome to Rowboat # Welcome to Rowboat
Rowboat is an open-source Cursor like IDE that helps you build, test, and deploy multi-agent AI systems. Rowboat is a low-code AI IDE to build MCP tools connected multi-agent assistants. Rowboat copilot builds the agents for you based on your requirements with the option do everything manually as well.
**Note:** These docs are intended for both developers who would like to self-host our [open-source code](https://github.com/rowboatlabs/rowboat/) as well as users of our [hosted (managed) app](https://app.rowboatlabs.com/). **Note:** These docs are intended for developers who would like to use our [open-source code](https://github.com/rowboatlabs/rowboat/).
- Our source code is on GitHub at [@rowboatlabs/rowboat](https://github.com/rowboatlabs/rowboat/) - Our source code is on GitHub at [@rowboatlabs/rowboat](https://github.com/rowboatlabs/rowboat/)
- Join us on [discord](https://discord.gg/jHhUKkKHn8) - Join us on [discord](https://discord.gg/jHhUKkKHn8)
@ -12,7 +12,7 @@ Rowboat is an open-source Cursor like IDE that helps you build, test, and deploy
## What is RowBoat? ## What is RowBoat?
**RowBoat is a state-of-art platform to build multi-agent AI systems in a visual interface, with the help of a copilot.** **RowBoat is a state-of-art platform to build multi-agent AI systems in a visual interface, with the help of a copilot.**
RowBoat enables you to build, manage and deploy user-facing assistants. An assistant is made up of multiple agents, each having access to a set of tools and working together to interact with the user as a single assistant. RowBoat enables you to build, manage and deploy user-facing assistants. An assistant is made up of multiple agents, each having access to a set of tools and working together to interact with the user as a single assistant. You can connect any MCP tools to the agents.
For example, you can build a *credit card assistant*, where each agent handles a workflow such as *outstanding payments*, *balance inquiries* and *transaction disputes*. You can equip agents with tools to carry out tasks such as *fetching payment options*, *checking outstanding balance* and *updating user information*. The assistant would help your end-users their credit card-related needs without having to talk to a human agent on your end. For example, you can build a *credit card assistant*, where each agent handles a workflow such as *outstanding payments*, *balance inquiries* and *transaction disputes*. You can equip agents with tools to carry out tasks such as *fetching payment options*, *checking outstanding balance* and *updating user information*. The assistant would help your end-users their credit card-related needs without having to talk to a human agent on your end.
@ -26,29 +26,22 @@ RowBoat Studio lets you create AI agents in minutes, using a visual interface an
| Agent | Handles a specific part of the conversation and<br>performs tasks using tools, based on instructions |• Configurable using plain language instructions<br>• Orchestrate between agents connected as a graph<br>• Can access tools and knowledge sources (RAG)| | Agent | Handles a specific part of the conversation and<br>performs tasks using tools, based on instructions |• Configurable using plain language instructions<br>• Orchestrate between agents connected as a graph<br>• Can access tools and knowledge sources (RAG)|
| Playground | Interactive environment to test assistants<br>conversationally as you build them |• Real-time testing and debugging<br>• Inspect parameters and results of tool calls in-line<br>• Converse with individual agents or the entire assistant| | Playground | Interactive environment to test assistants<br>conversationally as you build them |• Real-time testing and debugging<br>• Inspect parameters and results of tool calls in-line<br>• Converse with individual agents or the entire assistant|
| Copilot | AI-powered concierge that creates and<br>updates agents and tools on your behalf |• Context-aware of all components including playground<br>• Improves agents based on conversations and feedback <br>• Understands your requests in plain language| | Copilot | AI-powered concierge that creates and<br>updates agents and tools on your behalf |• Context-aware of all components including playground<br>• Improves agents based on conversations and feedback <br>• Understands your requests in plain language|
| Simulator | Simulates real-world user interactions<br>with your assistant |• Maintain and run a test-bench of different scenarios<br>• Mock tool responses for quick testing<br> Reproduce your end-user's experience comprehensively|
### RowBoat Chat API & SDK ### RowBoat Chat API & SDK
- RowBoat Chat API is a stateless HTTP API to interface with the assistant created on RowBoat Studio. You can use the API to drive end-user facing conversations in your app or website. - [RowBoat Chat API](/using_the_api) is a stateless HTTP API to interface with the assistant created on RowBoat Studio. You can use the API to drive end-user facing conversations in your app or website.
- RowBoat Chat SDK is a simple SDK (currently available in Python) which wraps the HTTP API under the hood. It offers both stateful and stateless (OpenAI-style) implementations. - [RowBoat Chat SDK](/using_the_sdk) is a simple SDK (currently available in Python) which wraps the HTTP API under the hood. It offers both stateful and stateless (OpenAI-style) implementations.
### Steps ### Steps
**RowBoat Studio:** **RowBoat Studio:**
1. Describe the assistant you are looking to build, to **copilot** 1. Describe the assistant you are looking to build, to **copilot**
2. Review and apply the **agents** (and tools) created by copilot 2. Review and apply the **agents** (and tools) created by copilot
3. Configure **tools** by connecting them to your APIs 3. Configure **MCP servers** and **tools** and connect them to agents
4. Chat with your assistant in the **playground** 4. Chat with your assistant in the **playground**
5. Create and run a test-bench of scenarios in the **simulator** 6. Deploy and use the HTTP API or Python SDK to integrate the agents into your system
6. Deploy the current version to production, with **version control**
**RowBoat SDK:**
1. **Integrate** the SDK into your end-user facing chat application. Use the latest deployed version of your assistant from RowBoat Studio, by specifying your RowBoat API key.
2. Alternatively, **export** your assistant as a JSON artifact from RowBoat Studio and use it to power your custom implementations.
## Why RowBoat? ## Why RowBoat?
Accelerate your path to production-ready multi-agent systems. Rowboat is the fastest way to build and deploy MCP connected multi-agents
1. **Build** complex assistants using plain language and a visual interface 1. **Build** complex assistants using plain language and a visual interface
2. **Integrate** tools and MCP servers in minutes 2. **Integrate** tools and MCP servers in minutes
@ -56,5 +49,5 @@ Accelerate your path to production-ready multi-agent systems.
## Getting started ## Getting started
- To set up our open-source installation, see [this guide](/installation) - To set up our open-source installation, see [Github Readme](https://github.com/rowboatlabs/rowboat)
- To sign up for our managed offering (beta), please email us at [founders@rowboatlabs.com](mailto:founders@rowboatlabs.com) - To sign up for our managed offering (beta), please email us at [founders@rowboatlabs.com](mailto:founders@rowboatlabs.com)

View file

@ -4,12 +4,4 @@
The playground is intended to test out the assistant as you build it. The User and Assistant messages represent the conversation that your end-user will have if your assistant is deployed in production. The playground also has debug elements which show the flow of control between different agents in your system, as well as which agent finally responded to the user. The playground is intended to test out the assistant as you build it. The User and Assistant messages represent the conversation that your end-user will have if your assistant is deployed in production. The playground also has debug elements which show the flow of control between different agents in your system, as well as which agent finally responded to the user.
![Try Chat](img/try-chat.png) ![Try Chat](img/chat-delivery.png)
In the playground, you can also set initial context at start of chat, that will be passed to all agents. This is typically used for providing user identity information such as user ID, login email, etc.
![Use System Message](img/sys-msg.png)
### Ask copilot questions
You can ask copilot clarifications about the chat, such as why the agents responded a certain way or why an agent was invoked.
![Copilot Clarifications](img/copilot-clarifications.png)

View file

@ -3,4 +3,4 @@
- Tools can be defined once in RowBoat Studio and reused across different agents. - Tools can be defined once in RowBoat Studio and reused across different agents.
- RowBoat uses OpenAI style tools with name, description and parameters. - RowBoat uses OpenAI style tools with name, description and parameters.
- For the purposes of quick testing in the Playground, RowBoat Studio can mock tool responses based on tool descriptions. - For the purposes of quick testing in the Playground, RowBoat Studio can mock tool responses based on tool descriptions.
- Developers can easily connect tools to APIs by configuring a Webhook URL in Studio, to which all tool calls will be routed. - Developers can easily connect tools to APIs by configuring MCP servers or Webhook URL in Settings.

View file

@ -10,27 +10,27 @@ This is a guide on using the HTTP API to power conversations with the assistant
Generate API keys via the developer configs in your project. Copy the Project ID from the same page. Generate API keys via the developer configs in your project. Copy the Project ID from the same page.
![Developer Configs](img/dev-config.png) ![Developer Configs](img/dev-config.png)
## Call the API ## API Endpoint
When you provide your Project ID in the API call, RowBoat uses the version of your assistant deployed to production. ```
POST <HOST>/api/v1/<PROJECT_ID>/chat
```
**Request parameters:** Where:
- `messages`: history of all messages in the conversation till now (system, user, tool and assistant messages) - For self-hosted: `<HOST>` is `http://localhost:3000`
- `state`: generated from the previous turn (this is needed because the API does not maintain state on its own)
**Response parameters:** ## Authentication
- `messages`: assistant responses for the current turn (the last message in `messages` is either the user-facing response or a tool call by the assistant) Include your API key in the Authorization header:
- `state`: to be passed to the next turn
### API Host ```
- For the open source installation, the `<HOST>` is [http://localhost:3000](http://localhost:3000) Authorization: Bearer <API_KEY>
- When using the hosted app, the `<HOST>` is [https://app.rowboatlabs.com](https://app.rowboatlabs.com) ```
### Example first turn of a chat ## Examples
#### Request ### First Turn
```bash ```bash
curl --location '<HOST>/api/v1/<PROJECT_ID>/chat' \ curl --location '<HOST>/api/v1/<PROJECT_ID>/chat' \
@ -38,206 +38,129 @@ curl --location '<HOST>/api/v1/<PROJECT_ID>/chat' \
--header 'Authorization: Bearer <API_KEY>' \ --header 'Authorization: Bearer <API_KEY>' \
--data '{ --data '{
"messages": [ "messages": [
{
"role": "system",
"content": "UserID: 345227"
// Provide context to be passed to all agents in the assistant
// E.g. user identity info (user ID) for logged in users
},
{ {
"role": "user", "role": "user",
"content": "What is my outstanding balance and how do I make the payment?" "content": "Hello, can you help me?"
} }
], ],
"state": { "state": null
"last_agent_name": "Credit Card Hub"
// Last agent used in the previous turn
// Set to the "start agent" for first turn of chats
}
}' }'
``` ```
#### Response
Response:
```json ```json
{ {
"messages": [ "messages": [
{ {
"sender": "Credit Card Hub",
"role": "assistant", "role": "assistant",
"response_type": "internal", "content": "Hello! Yes, I'd be happy to help you. What can I assist you with today?",
"content": null, "agenticResponseType": "external"
"current_turn": true,
"tool_calls": [
{
"function": {
// Internal tool calls are used to transfer between agents
"name": "transfer_to_outstanding_payments",
"arguments": "{\"args\":\"\",\"kwargs\":\"\"}"
},
"id": "call_SLyQKXt9ZMqnxSqJjo9j1fU5",
"type": "function"
}
]
},
{
"role": "tool",
"tool_name": "transfer_to_outstanding_payments",
"content": "{\"assistant\": \"Outstanding Payments\"}",
"tool_call_id": "call_SLyQKXt9ZMqnxSqJjo9j1fU5"
},
{
// Last message in response messages is a tool call
"sender": "Outstanding Payments",
"role": "assistant",
"response_type": "internal",
"content": null,
"current_turn": true,
"tool_calls": [
{
"function": {
"name": "get_outstanding_balance",
"arguments": "{\"user_id\":\"345227\"}"
},
"id": "call_MNAUg7UTszYMt5RL4n5QqUTw",
"type": "function"
}
]
} }
], ],
"state": { "state": {
"agent_data": [ "last_agent_name": "MainAgent"
// Agents that were involved in this turn
{
"name": "Credit Card Hub",
"instructions": "// agent instructions",
"history": [
// History of agent-relevant messages
// in the same format as "messages"
],
"child_functions": [
"transfer_to_outstanding_payments",
"transfer_to_transaction_disputes",
"transfer_to_rewards_redemption"
],
},
{
"name": "Outstanding Payments",
"instructions": // Agent instructions,
"history": [
// History of agent-relevant messages
// in the same format as "messages"
],
"external_tools": [
"get_outstanding_balance",
"get_saved_credit_card"
],
},
// Other agents - have not yet participated in the conversation
{
"name": "Rewards Redemption",
"instructions": // Agent instructions,
"history": [], //
}
],
"last_agent_name": "Outstanding Payments"
} }
} }
``` ```
### Example where the assistant is expecting a tool response ### Subsequent Turn
#### Request
Notice how we include both the previous messages and the state from the last response:
```bash ```bash
curl --location 'http://localhost:3000/api/v1/<PROJECT_ID>/chat' \ curl --location '<HOST>/api/v1/<PROJECT_ID>/chat' \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API_KEY>' \ --header 'Authorization: Bearer <API_KEY>' \
--data '{ --data '{
"messages": [ "messages": [
{ {
"role": "system", "role": "user",
"content": "UserID: 345227" "content": "Hello, can you help me?"
},
{
"role": "assistant",
"content": "Hello! Yes, I'd be happy to help you. What can I assist you with today?",
"agenticResponseType": "external"
}, },
{ {
"role": "user", "role": "user",
"content": "What is my outstanding balance and how do I make the payment?" "content": "What services do you offer?"
},
{
"sender": "Credit Card Hub",
"role": "assistant",
"response_type": "internal",
"content": null,
"tool_calls": [
{
"function": {
"arguments": "{\"args\":\"\",\"kwargs\":\"\"}",
"name": "transfer_to_outstanding_payments"
},
"id": "call_SLyQKXt9ZMqnxSqJjo9j1fU5",
"type": "function"
}
],
},
{
"role": "tool",
"tool_name": "transfer_to_outstanding_payments",
"content": "{\"assistant\": \"Outstanding Payments\"}",
"tool_call_id": "call_SLyQKXt9ZMqnxSqJjo9j1fU5"
},
{
"sender": "Outstanding Payments",
"role": "assistant",
"response_type": "internal",
"content": null,
"tool_calls": [
{
"function": {
"arguments": "{\"user_id\":\"345227\"}",
"name": "get_outstanding_balance"
},
"id": "call_MNAUg7UTszYMt5RL4n5QqUTw",
"type": "function"
}
],
},
{
// New message is a tool response to the previous tool call
"role": "tool",
"tool_name": "get_outstanding_balance"
"content": "{\"result\":{\"outstanding_balance\":\"$250.00\",\"due_date\":\"2025-02-15\",\"payment_methods\":[\"Credit Card\",\"Bank Transfer\",\"PayPal\"]}}",
"tool_call_id": "call_MNAUg7UTszYMt5RL4n5QqUTw",
},
],
"state": {
// State returned by the API in the previous turn
}
}'
```
#### Response
```json
{
"messages": [
{
"sender": "Outstanding Payments",
"role": "assistant",
// Response is not user-facing, to enable further post processing
"response_type": "internal",
"content": "Your outstanding balance is $250.00, due by February 15, 2025.\n\nYou have several payment options available, including:\n- **Credit Card**\n- **Bank Transfer**\n- **PayPal**\n\nPlease let me know which option you'd like to use, and I'll guide you through the process!",
"current_turn": true
},
{
"sender": "Outstanding Payments >> Post process",
"role": "assistant",
// Response is user-facing
"response_type": "external",
"content": "Your outstanding balance is $250.00, due by February 15, 2025. \n\nPayment options include:\n- **Credit Card:** You can use your saved Visa card ending in 1234.\n- **Bank Transfer**\n- **PayPal**\n\nLet me know your preferred payment method, and Ill assist you!",
"current_turn": true,
} }
], ],
"state": { "state": {
"agent_data": [ "last_agent_name": "MainAgent"
// Omitted for brevity
],
"last_agent_name": "Outstanding Payments"
} }
}'
```
## API Specification
### Request Schema
```typescript
{
// Required fields
messages: Message[]; // Array of message objects representing the conversation history
state: any; // State object from previous response, or null for first message
// Optional fields
workflowId?: string; // Specific workflow ID to use (defaults to production workflow)
testProfileId?: string; // Test profile ID for simulation
} }
``` ```
### Message Types
Messages can be one of the following types:
1. System Message
```typescript
{
role: "system";
content: string;
}
```
2. User Message
```typescript
{
role: "user";
content: string;
}
```
3. Assistant Message
```typescript
{
role: "assistant";
content: string;
agenticResponseType: "internal" | "external";
agenticSender?: string | null;
}
```
### Response Schema
```typescript
{
messages: Message[]; // Array of new messages from this turn
state: any; // State object to pass in the next request
}
```
## Important Notes
1. Always pass the complete conversation history in the `messages` array
2. Always include the `state` from the previous response in your next request
3. The last message in the response's `messages` array will be a user-facing assistant message (`agenticResponseType: "external"`)
## Rate Limiting
The API has rate limits per project. If exceeded, you'll receive a 429 status code.
## Error Responses
- 400: Invalid request body or missing/invalid Authorization header
- 403: Invalid API key
- 404: Project or workflow not found
- 429: Rate limit exceeded

View file

@ -13,13 +13,68 @@ This is a guide on using the RowBoat Python SDK as an alternative to the [RowBoa
## Usage ## Usage
### Basic Usage ### Basic Usage with StatefulChat
Initialize a client and use the chat method directly: The easiest way to interact with Rowboat is using the `StatefulChat` class, which maintains conversation state automatically:
```python ```python
from rowboat import Client from rowboat import Client, StatefulChat
from rowboat.schema import UserMessage, SystemMessage
# Initialize the client
client = Client(
host="<HOST>",
project_id="<PROJECT_ID>",
api_key="<API_KEY>"
)
# Create a stateful chat session
chat = StatefulChat(client)
# Have a conversation
response = chat.run("What is the capital of France?")
print(response)
# The capital of France is Paris.
# Continue the conversation - the context is maintained automatically
response = chat.run("What other major cities are in that country?")
print(response)
# Other major cities in France include Lyon, Marseille, Toulouse, and Nice.
response = chat.run("What's the population of the first city you mentioned?")
print(response)
# Lyon has a population of approximately 513,000 in the city proper.
```
### Advanced Usage
#### Using a specific workflow
You can specify a workflow ID to use a particular conversation configuration:
```python
chat = StatefulChat(
client,
workflow_id="<WORKFLOW_ID>"
)
```
#### Using a test profile
You can specify a test profile ID to use a specific test configuration:
```python
chat = StatefulChat(
client,
test_profile_id="<TEST_PROFILE_ID>"
)
```
### Low-Level Usage
For more control over the conversation, you can use the `Client` class directly:
```python
from rowboat.schema import UserMessage
# Initialize the client # Initialize the client
client = Client( client = Client(
@ -30,57 +85,15 @@ client = Client(
# Create messages # Create messages
messages = [ messages = [
SystemMessage(role='system', content="You are a helpful assistant"),
UserMessage(role='user', content="Hello, how are you?") UserMessage(role='user', content="Hello, how are you?")
] ]
# Get response # Get response
response_messages, state = client.chat(messages=messages) response = client.chat(messages=messages)
print(response_messages[-1].content) print(response.messages[-1].content)
# For subsequent messages, include previous messages and state # For subsequent messages, you need to manage the message history and state manually
messages.extend(response_messages) messages.extend(response.messages)
messages.append(UserMessage(role='user', content="What's your name?")) messages.append(UserMessage(role='user', content="What's your name?"))
response_messages, state = client.chat(messages=messages, state=state) response = client.chat(messages=messages, state=response.state)
```
### Using Tools
The SDK supports function calling through tools:
```python
def weather_lookup(city_name: str) -> str:
return f"The weather in {city_name} is 22°C."
# Create a tools dictionary
tools = {
'weather_lookup': weather_lookup
}
# Use tools with the chat method
response_messages, state = client.chat(
messages=messages,
tools=tools
)
```
The last message in `response_messages` is either a user-facing response or a tool call by the assistant.
### Stateful Chat (Convenience Wrapper)
For simpler use cases, the SDK provides a `StatefulChat` class that maintains conversation state automatically:
```python
from rowboat import StatefulChat
# Initialize stateful chat
chat = StatefulChat(
client,
tools=tools,
system_prompt="You are a helpful assistant."
)
# Simply send messages and get responses
response = chat.run("Hello, how are you?")
print(response)
# I'm good, thanks! How can I help you today?
``` ```

View file

@ -8,18 +8,11 @@ nav:
- Introduction: index.md - Introduction: index.md
- Open Source License: license.md - Open Source License: license.md
- Getting Started:
- Option 1 - Open Source: oss_installation.md
- Option 2 - Hosted App: hosted_setup.md
- Testing Your Setup: testing.md
- Building in Studio: - Building in Studio:
- Overview: studio_overview.md
- Create agents: create_agents.md - Create agents: create_agents.md
- Test chats in the playground: playground.md - Test chats in the playground: playground.md
- Add tools: add_tools.md - Add tools: add_tools.md
- Update agents: update_agents.md - Update agents: update_agents.md
- Simulate scenarios: simulate.md
- API & SDK: - API & SDK:
- Using the API: using_the_api.md - Using the API: using_the_api.md
@ -27,7 +20,5 @@ nav:
- Concepts: - Concepts:
- Agents: agents.md - Agents: agents.md
- Graph: graph.md
- Tools: tools.md - Tools: tools.md
- Prompts: prompts.md - Prompts: prompts.md
- Data Sources: data_sources.md

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 391 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 128 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

Before After
Before After

View file

@ -12,13 +12,68 @@ pip install rowboat
## Usage ## Usage
### Basic Usage ### Basic Usage with StatefulChat
Initialize a client and use the chat method directly: The easiest way to interact with Rowboat is using the `StatefulChat` class, which maintains conversation state automatically:
```python ```python
from rowboat import Client from rowboat import Client, StatefulChat
from rowboat.schema import UserMessage, SystemMessage
# Initialize the client
client = Client(
host="<HOST>",
project_id="<PROJECT_ID>",
api_key="<API_KEY>"
)
# Create a stateful chat session
chat = StatefulChat(client)
# Have a conversation
response = chat.run("What is the capital of France?")
print(response)
# The capital of France is Paris.
# Continue the conversation - the context is maintained automatically
response = chat.run("What other major cities are in that country?")
print(response)
# Other major cities in France include Lyon, Marseille, Toulouse, and Nice.
response = chat.run("What's the population of the first city you mentioned?")
print(response)
# Lyon has a population of approximately 513,000 in the city proper.
```
### Advanced Usage
#### Using a specific workflow
You can specify a workflow ID to use a particular conversation configuration:
```python
chat = StatefulChat(
client,
workflow_id="<WORKFLOW_ID>"
)
```
#### Using a test profile
You can specify a test profile ID to use a specific test configuration:
```python
chat = StatefulChat(
client,
test_profile_id="<TEST_PROFILE_ID>"
)
```
### Low-Level Usage
For more control over the conversation, you can use the `Client` class directly:
```python
from rowboat.schema import UserMessage
# Initialize the client # Initialize the client
client = Client( client = Client(
@ -29,108 +84,15 @@ client = Client(
# Create messages # Create messages
messages = [ messages = [
SystemMessage(role='system', content="You are a helpful assistant"),
UserMessage(role='user', content="Hello, how are you?") UserMessage(role='user', content="Hello, how are you?")
] ]
# Get response # Get response
response_messages, state = client.chat(messages=messages) response = client.chat(messages=messages)
print(response_messages[-1].content) print(response.messages[-1].content)
# For subsequent messages, include previous messages and state # For subsequent messages, you need to manage the message history and state manually
messages.extend(response_messages) messages.extend(response.messages)
messages.append(UserMessage(role='user', content="What's your name?")) messages.append(UserMessage(role='user', content="What's your name?"))
response_messages, state = client.chat(messages=messages, state=state) response = client.chat(messages=messages, state=response.state)
```
### Using Tools
The SDK supports function calling through tools:
```python
def weather_lookup(city_name: str) -> str:
return f"The weather in {city_name} is 22°C."
# Create a tools dictionary
tools = {
'weather_lookup': weather_lookup
}
# Use tools with the chat method
response_messages, state = client.chat(
messages=messages,
tools=tools
)
```
### Stateful Chat (Convenience Wrapper)
For simpler use cases, the SDK provides a `StatefulChat` class that maintains conversation state automatically:
```python
from rowboat import StatefulChat
# Initialize stateful chat
chat = StatefulChat(
client,
tools=tools,
system_prompt="You are a helpful assistant."
)
# Simply send messages and get responses
response = chat.run("Hello, how are you?")
print(response)
# I'm good, thanks! How can I help you today?
```
### Advanced Usage
#### Using a specific workflow
```python
response_messages, state = client.chat(
messages=messages,
workflow_id="<WORKFLOW_ID>"
)
# or
chat = StatefulChat(
client,
workflow_id="<WORKFLOW_ID>"
)
```
#### Using a test profile
You can specify a test profile ID to use a specific test configuration:
```python
response_messages, state = client.chat(
messages=messages,
test_profile_id="<TEST_PROFILE_ID>"
)
# or
chat = StatefulChat(
client,
test_profile_id="<TEST_PROFILE_ID>"
)
```
#### Skip tool call runs
This will surface the tool calls to the SDK instead of running them automatically on the Rowboat server.
```python
response_messages, state = client.chat(
messages=messages,
skip_tool_calls=True
)
# or
chat = StatefulChat(
client,
skip_tool_calls=True
)
``` ```

View file

@ -4,9 +4,9 @@ build-backend = "hatchling.build"
[project] [project]
name = "rowboat" name = "rowboat"
version = "2.1.0" version = "3.0.0"
authors = [ authors = [
{ name = "Your Name", email = "your.email@example.com" }, { name = "Ramnique Singh", email = "ramnique@rowboatlabs.com" },
] ]
description = "Python sdk for the Rowboat API" description = "Python sdk for the Rowboat API"
readme = "README.md" readme = "README.md"

View file

@ -1,18 +1,14 @@
from typing import Dict, List, Optional, Any, Callable, Union, Tuple from typing import Dict, List, Optional, Any, Union
import requests import requests
import json
from .schema import ( from .schema import (
ApiRequest, ApiRequest,
ApiResponse, ApiResponse,
ApiMessage, ApiMessage,
ToolMessage,
UserMessage, UserMessage,
SystemMessage,
AssistantMessage, AssistantMessage,
AssistantMessageWithToolCalls AssistantMessageWithToolCalls
) )
class Client: class Client:
def __init__(self, host: str, project_id: str, api_key: str) -> None: def __init__(self, host: str, project_id: str, api_key: str) -> None:
self.base_url: str = f'{host}/api/v1/{project_id}/chat' self.base_url: str = f'{host}/api/v1/{project_id}/chat'
@ -25,16 +21,12 @@ class Client:
self, self,
messages: List[ApiMessage], messages: List[ApiMessage],
state: Optional[Dict[str, Any]] = None, state: Optional[Dict[str, Any]] = None,
skip_tool_calls: bool = False,
max_turns: int = 3,
workflow_id: Optional[str] = None, workflow_id: Optional[str] = None,
test_profile_id: Optional[str] = None test_profile_id: Optional[str] = None
) -> ApiResponse: ) -> ApiResponse:
request = ApiRequest( request = ApiRequest(
messages=messages, messages=messages,
state=state, state=state,
skipToolCalls=skip_tool_calls,
maxTurns=max_turns,
workflowId=workflow_id, workflowId=workflow_id,
testProfileId=test_profile_id testProfileId=test_profile_id
) )
@ -55,86 +47,27 @@ class Client:
return response_data return response_data
def _process_tool_calls(
self,
tool_calls: List[Any],
tools: Dict[str, Callable[..., str]]
) -> List[ToolMessage]:
"""Process tool calls and return a list of tool response messages"""
tool_messages = []
for tool_call in tool_calls:
tool_name = tool_call.function.name
tool_arguments = json.loads(tool_call.function.arguments)
if tool_name not in tools:
raise ValueError(f'Missing tool: {tool_name}')
tool_response = tools[tool_name](**tool_arguments)
tool_msg = ToolMessage(
role='tool',
content=tool_response,
tool_call_id=tool_call.id,
tool_name=tool_name
)
tool_messages.append(tool_msg)
return tool_messages
def chat( def chat(
self, self,
messages: List[ApiMessage], messages: List[ApiMessage],
tools: Optional[Dict[str, Callable[..., str]]] = None,
state: Optional[Dict[str, Any]] = None, state: Optional[Dict[str, Any]] = None,
max_turns: int = 3,
skip_tool_calls: bool = False,
workflow_id: Optional[str] = None, workflow_id: Optional[str] = None,
test_profile_id: Optional[str] = None test_profile_id: Optional[str] = None
) -> Tuple[List[ApiMessage], Optional[Dict[str, Any]]]: ) -> ApiResponse:
"""Stateless chat method that handles a single conversation turn with multiple tool call rounds""" """Stateless chat method that handles a single conversation turn"""
current_messages = messages[:] # call api
current_state = state response_data = self._call_api(
turns = 0 messages=messages,
state=state,
workflow_id=workflow_id,
test_profile_id=test_profile_id
)
response_messages = [] if not response_data.messages[-1].agenticResponseType == 'external':
response_state = None
has_tool_calls = False
while turns < max_turns:
# call api
response_data = self._call_api(
messages=current_messages,
state=current_state,
skip_tool_calls=skip_tool_calls,
max_turns=max_turns,
workflow_id=workflow_id,
test_profile_id=test_profile_id
)
current_messages.extend(response_data.messages)
current_state = response_data.state
response_messages = response_data.messages
response_state = response_data.state
# Process tool calls if present and tools are provided
last_message = response_data.messages[-1]
has_tool_calls = hasattr(last_message, 'tool_calls') and last_message.tool_calls
if has_tool_calls:
tool_messages = self._process_tool_calls(last_message.tool_calls, tools)
current_messages.extend(tool_messages)
# If no tool calls were made, we're done
if not has_tool_calls:
break
turns += 1
if turns == max_turns and has_tool_calls:
raise ValueError("Max turns reached")
if not last_message.agenticResponseType == 'external':
raise ValueError("Last message was not an external message") raise ValueError("Last message was not an external message")
return response_messages, response_state return response_data
class StatefulChat: class StatefulChat:
"""Maintains conversation state across multiple turns""" """Maintains conversation state across multiple turns"""
@ -142,23 +75,14 @@ class StatefulChat:
def __init__( def __init__(
self, self,
client: Client, client: Client,
tools: Optional[Dict[str, Callable[..., str]]] = None,
system_prompt: Optional[str] = None,
max_turns: int = 3,
skip_tool_calls: bool = False,
workflow_id: Optional[str] = None, workflow_id: Optional[str] = None,
test_profile_id: Optional[str] = None test_profile_id: Optional[str] = None
) -> None: ) -> None:
self.client = client self.client = client
self.tools = tools
self.messages: List[ApiMessage] = [] self.messages: List[ApiMessage] = []
self.state: Optional[Dict[str, Any]] = None self.state: Optional[Dict[str, Any]] = None
self.max_turns = max_turns
self.skip_tool_calls = skip_tool_calls
self.workflow_id = workflow_id self.workflow_id = workflow_id
self.test_profile_id = test_profile_id self.test_profile_id = test_profile_id
if system_prompt:
self.messages.append(SystemMessage(role='system', content=system_prompt))
def run(self, message: Union[str]) -> str: def run(self, message: Union[str]) -> str:
"""Handle a single user turn in the conversation""" """Handle a single user turn in the conversation"""
@ -168,22 +92,19 @@ class StatefulChat:
self.messages.append(user_msg) self.messages.append(user_msg)
# Get response using the client's chat method # Get response using the client's chat method
new_messages, new_state = self.client.chat( response_data = self.client.chat(
messages=self.messages, messages=self.messages,
tools=self.tools,
state=self.state, state=self.state,
max_turns=self.max_turns,
skip_tool_calls=self.skip_tool_calls,
workflow_id=self.workflow_id, workflow_id=self.workflow_id,
test_profile_id=self.test_profile_id test_profile_id=self.test_profile_id
) )
# Update internal state # Update internal state
self.messages = new_messages self.messages = response_data.messages
self.state = new_state self.state = response_data.state
# Return only the final message content # Return only the final message content
last_message = new_messages[-1] last_message = self.messages[-1]
return last_message.content return last_message.content
@ -197,9 +118,13 @@ if __name__ == "__main__":
api_key: str = "<API_KEY>" api_key: str = "<API_KEY>"
client = Client(host, project_id, api_key) client = Client(host, project_id, api_key)
tools: Dict[str, Callable[..., str]] = { result = client.chat(
'weather_lookup': weather_lookup_tool messages=[
} UserMessage(role='user', content="Hello")
chat_session = StatefulChat(client, tools) ]
resp = chat_session.run("whats the weather in london?") )
print(result.messages[-1].content)
chat_session = StatefulChat(client)
resp = chat_session.run("Hello")
print(resp) print(resp)

View file

@ -48,8 +48,6 @@ ApiMessage = Union[
class ApiRequest(BaseModel): class ApiRequest(BaseModel):
messages: List[ApiMessage] messages: List[ApiMessage]
state: Any state: Any
skipToolCalls: Optional[bool] = None
maxTurns: Optional[int] = None
workflowId: Optional[str] = None workflowId: Optional[str] = None
testProfileId: Optional[str] = None testProfileId: Optional[str] = None

View file

@ -1,11 +1,18 @@
import { redisClient } from "@/app/lib/redis";
export async function GET(request: Request, { params }: { params: { streamId: string } }) { export async function GET(request: Request, { params }: { params: { streamId: string } }) {
// Replace with your actual upstream SSE endpoint. // get the payload from redis
const upstreamUrl = `${process.env.AGENTS_API_URL}/chat_stream/${params.streamId}`; const payload = await redisClient.get(`chat-stream-${params.streamId}`);
console.log('upstreamUrl', upstreamUrl); if (!payload) {
return new Response("Stream not found", { status: 404 });
}
// Fetch the upstream SSE stream. // Fetch the upstream SSE stream.
const upstreamResponse = await fetch(upstreamUrl, { const upstreamResponse = await fetch(`${process.env.AGENTS_API_URL}/chat_stream`, {
method: 'POST',
body: payload,
headers: { headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`, 'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`,
}, },
cache: 'no-store', cache: 'no-store',

View file

@ -3,3 +3,8 @@ export const USE_RAG_UPLOADS = process.env.USE_RAG_UPLOADS === 'true';
export const USE_RAG_SCRAPING = process.env.USE_RAG_SCRAPING === 'true'; export const USE_RAG_SCRAPING = process.env.USE_RAG_SCRAPING === 'true';
export const USE_CHAT_WIDGET = process.env.USE_CHAT_WIDGET === 'true'; export const USE_CHAT_WIDGET = process.env.USE_CHAT_WIDGET === 'true';
export const USE_AUTH = process.env.USE_AUTH === 'true'; export const USE_AUTH = process.env.USE_AUTH === 'true';
// Hardcoded flags
export const USE_MULTIPLE_PROJECTS = true;
export const USE_TESTING_FEATURE = false;
export const USE_VOICE_FEATURE = false;

View file

@ -111,8 +111,6 @@ export const ApiMessage = z.union([
export const ApiRequest = z.object({ export const ApiRequest = z.object({
messages: z.array(ApiMessage), messages: z.array(ApiMessage),
state: z.unknown(), state: z.unknown(),
skipToolCalls: z.boolean().nullable().optional(),
maxTurns: z.number().nullable().optional(),
workflowId: z.string().nullable().optional(), workflowId: z.string().nullable().optional(),
testProfileId: z.string().nullable().optional(), testProfileId: z.string().nullable().optional(),
}); });

View file

@ -3,6 +3,7 @@ import { z } from "zod";
import { generateObject } from "ai"; import { generateObject } from "ai";
import { ApiMessage } from "./types/types"; import { ApiMessage } from "./types/types";
import { openai } from "@ai-sdk/openai"; import { openai } from "@ai-sdk/openai";
import { redisClient } from "./redis";
export async function getAgenticApiResponse( export async function getAgenticApiResponse(
request: z.infer<typeof AgenticAPIChatRequest>, request: z.infer<typeof AgenticAPIChatRequest>,
@ -38,24 +39,20 @@ export async function getAgenticApiResponse(
export async function getAgenticResponseStreamId( export async function getAgenticResponseStreamId(
request: z.infer<typeof AgenticAPIChatRequest>, request: z.infer<typeof AgenticAPIChatRequest>,
): Promise<z.infer<typeof AgenticAPIInitStreamResponse>> { ): Promise<z.infer<typeof AgenticAPIInitStreamResponse>> {
// call agentic api // serialize the request
console.log(`sending agentic api init stream request`, JSON.stringify(request)); const payload = JSON.stringify(request);
const response = await fetch(process.env.AGENTS_API_URL + '/chat_stream_init', {
method: 'POST', // create a uuid for the stream
body: JSON.stringify(request), const streamId = crypto.randomUUID();
headers: {
'Content-Type': 'application/json', // store payload in redis
'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`, await redisClient.set(`chat-stream-${streamId}`, payload, {
}, EX: 60 * 10, // expire in 10 minutes
}); });
if (!response.ok) {
console.error('Failed to call agentic init stream api', response); return {
throw new Error(`Failed to call agentic init stream api: ${response.statusText}`); streamId,
} };
const responseJson = await response.json();
console.log(`received agentic api init stream response`, JSON.stringify(responseJson));
const result: z.infer<typeof AgenticAPIInitStreamResponse> = responseJson;
return result;
} }
// create a PrefixLogger class that wraps console.log with a prefix // create a PrefixLogger class that wraps console.log with a prefix

View file

@ -27,6 +27,7 @@ import { ToolsSection } from './components/tools';
import { Panel } from "@/components/common/panel-common"; import { Panel } from "@/components/common/panel-common";
import { Settings, Wrench, Phone } from "lucide-react"; import { Settings, Wrench, Phone } from "lucide-react";
import { clsx } from "clsx"; import { clsx } from "clsx";
import { USE_VOICE_FEATURE } from "@/app/lib/feature_flags";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Project config", title: "Project config",
@ -799,7 +800,7 @@ function NavigationMenu({
const items = [ const items = [
{ id: 'Project', icon: <Settings className="w-4 h-4" /> }, { id: 'Project', icon: <Settings className="w-4 h-4" /> },
{ id: 'Tools', icon: <Wrench className="w-4 h-4" /> }, { id: 'Tools', icon: <Wrench className="w-4 h-4" /> },
{ id: 'Voice', icon: <Phone className="w-4 h-4" /> } ...(USE_VOICE_FEATURE ? [{ id: 'Voice', icon: <Phone className="w-4 h-4" /> }] : [])
]; ];
return ( return (

View file

@ -11,6 +11,7 @@ import { TestProfile } from "@/app/lib/types/testing_types";
import { WithStringId } from "@/app/lib/types/types"; import { WithStringId } from "@/app/lib/types/types";
import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector"; import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector";
import { CheckIcon, CopyIcon, PlusIcon, UserIcon } from "lucide-react"; import { CheckIcon, CopyIcon, PlusIcon, UserIcon } from "lucide-react";
import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags";
const defaultSystemMessage = ''; const defaultSystemMessage = '';
@ -103,15 +104,17 @@ export function App({
} }
rightActions={ rightActions={
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button {USE_TESTING_FEATURE && (
variant="secondary" <Button
size="sm" variant="secondary"
onClick={() => setIsProfileSelectorOpen(true)} size="sm"
showHoverContent={true} onClick={() => setIsProfileSelectorOpen(true)}
hoverContent={testProfile?.name || 'Select test profile'} showHoverContent={true}
> hoverContent={testProfile?.name || 'Select test profile'}
<UserIcon className="w-4 h-4" /> >
</Button> <UserIcon className="w-4 h-4" />
</Button>
)}
<Button <Button
variant="secondary" variant="secondary"
size="sm" size="sm"

View file

@ -14,6 +14,7 @@ import { apiV1 } from "rowboat-shared";
import { TestProfile } from "@/app/lib/types/testing_types"; import { TestProfile } from "@/app/lib/types/testing_types";
import { WithStringId } from "@/app/lib/types/types"; import { WithStringId } from "@/app/lib/types/types";
import { ProfileContextBox } from "./profile-context-box"; import { ProfileContextBox } from "./profile-context-box";
import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags";
export function Chat({ export function Chat({
chat, chat,
@ -225,11 +226,13 @@ export function Chat({
return <div className="relative max-w-3xl mx-auto h-full flex flex-col"> return <div className="relative max-w-3xl mx-auto h-full flex flex-col">
<div className="sticky top-0 z-10 bg-white dark:bg-zinc-900 pt-4 pb-4"> <div className="sticky top-0 z-10 bg-white dark:bg-zinc-900 pt-4 pb-4">
<ProfileContextBox {USE_TESTING_FEATURE && (
content={testProfile?.context || systemMessage || ''} <ProfileContextBox
onChange={onSystemMessageChange} content={testProfile?.context || systemMessage || ''}
locked={testProfile !== null} onChange={onSystemMessageChange}
/> locked={testProfile !== null}
/>
)}
</div> </div>
<div className="flex-1 overflow-auto pr-1 <div className="flex-1 overflow-auto pr-1

View file

@ -535,6 +535,7 @@ function reducer(state: State, action: Action): State {
draft.workflow.tools.push(newTool); draft.workflow.tools.push(newTool);
} }
}); });
draft.pendingChanges = true;
draft.chatKey++; draft.chatKey++;
break; break;
} }

View file

@ -16,6 +16,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { getProjectConfig } from "@/app/actions/project_actions"; import { getProjectConfig } from "@/app/actions/project_actions";
import { useTheme } from "@/app/providers/theme-provider"; import { useTheme } from "@/app/providers/theme-provider";
import { USE_TESTING_FEATURE } from '@/app/lib/feature_flags';
interface SidebarProps { interface SidebarProps {
projectId: string; projectId: string;
@ -56,12 +57,12 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
icon: WorkflowIcon, icon: WorkflowIcon,
requiresProject: true requiresProject: true
}, },
{ ...(USE_TESTING_FEATURE ? [{
href: 'test', href: 'test',
label: 'Test', label: 'Test',
icon: PlayIcon, icon: PlayIcon,
requiresProject: true requiresProject: true
}, }] : []),
...(useRag ? [{ ...(useRag ? [{
href: 'sources', href: 'sources',
label: 'RAG', label: 'RAG',

View file

@ -14,11 +14,18 @@ import { SearchProjects } from "./components/search-projects";
import { CustomPromptCard } from "./components/custom-prompt-card"; import { CustomPromptCard } from "./components/custom-prompt-card";
import { Submit } from "./components/submit-button"; import { Submit } from "./components/submit-button";
import { PageHeading } from "@/components/ui/page-heading"; import { PageHeading } from "@/components/ui/page-heading";
import { USE_MULTIPLE_PROJECTS } from "@/app/lib/feature_flags";
const sectionHeaderStyles = clsx( const sectionHeaderStyles = clsx(
"text-sm font-medium", "text-sm font-medium",
"text-gray-900 dark:text-gray-100" "text-gray-900 dark:text-gray-100"
); );
const largeSectionHeaderStyles = clsx(
"text-lg font-medium",
"text-gray-900 dark:text-gray-100"
);
const textareaStyles = clsx( const textareaStyles = clsx(
"w-full", "w-full",
"rounded-lg p-3", "rounded-lg p-3",
@ -34,12 +41,12 @@ export default function App() {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [selectedCard, setSelectedCard] = useState<'custom' | any>('custom'); const [selectedCard, setSelectedCard] = useState<'custom' | any>('custom');
const [customPrompt, setCustomPrompt] = useState("Create a customer support assistant with one example agent"); const [customPrompt, setCustomPrompt] = useState("");
const [name, setName] = useState(""); const [name, setName] = useState("");
const [defaultName, setDefaultName] = useState('Assistant 1'); const [defaultName, setDefaultName] = useState('Assistant 1');
const [isExamplesExpanded, setIsExamplesExpanded] = useState(false); const [isExamplesExpanded, setIsExamplesExpanded] = useState(false);
const [selectedTemplate, setSelectedTemplate] = useState<string>('blank'); const [selectedTemplate, setSelectedTemplate] = useState<string>('custom');
const [showCustomPrompt, setShowCustomPrompt] = useState(false); const [showCustomPrompt, setShowCustomPrompt] = useState(true);
const [promptError, setPromptError] = useState<string | null>(null); const [promptError, setPromptError] = useState<string | null>(null);
const [hasEditedPrompt, setHasEditedPrompt] = useState(false); const [hasEditedPrompt, setHasEditedPrompt] = useState(false);
@ -88,7 +95,7 @@ export default function App() {
setSelectedCard(card); setSelectedCard(card);
if (card === 'custom') { if (card === 'custom') {
setCustomPrompt("Create a customer support assistant with one example agent"); setCustomPrompt("");
} else { } else {
setCustomPrompt(card.prompt || card.description); setCustomPrompt(card.prompt || card.description);
} }
@ -178,64 +185,121 @@ export default function App() {
"flex-1 px-12 pt-4 pb-32" "flex-1 px-12 pt-4 pb-32"
)}> )}>
<PageHeading <PageHeading
title="Projects" title={USE_MULTIPLE_PROJECTS ? "Projects" : "Let's get started"}
description="Select an existing project or create a new one" description={USE_MULTIPLE_PROJECTS
? "Select an existing project or create a new one"
: "Create a multi-agent assistant in minutes"
}
/> />
<div className="grid grid-cols-1 lg:grid-cols-[1fr,2fr] gap-8 mt-8"> <div className={clsx(
USE_MULTIPLE_PROJECTS
? "grid grid-cols-1 lg:grid-cols-[1fr,2fr] gap-8 mt-8"
: "mt-8 -mx-12"
)}>
{/* Left side: Project Selection */} {/* Left side: Project Selection */}
<div className="overflow-auto"> {USE_MULTIPLE_PROJECTS && (
<SearchProjects <div className="overflow-auto">
projects={projects} <SearchProjects
isLoading={isLoading} projects={projects}
heading="Select an existing project" isLoading={isLoading}
subheading="Choose from your projects" heading="Select an existing project"
className="h-full" subheading="Choose from your projects"
/> className="h-full"
</div> />
</div>
)}
{/* Right side: Project Creation */} {/* Right side: Project Creation */}
<div className="overflow-auto"> <div className={clsx(
<section className="card h-full"> "overflow-auto",
<div className="px-4 pt-4"> !USE_MULTIPLE_PROJECTS && "max-w-none px-12 py-12"
<SectionHeading subheading="Set up a new AI assistant"> )}>
Create a new project <section className={clsx(
</SectionHeading> "card h-full",
</div> !USE_MULTIPLE_PROJECTS && "px-24",
USE_MULTIPLE_PROJECTS && "px-8"
)}>
{USE_MULTIPLE_PROJECTS && (
<div className="pt-12">
<SectionHeading subheading="Set up a new AI assistant">
Create a new project
</SectionHeading>
</div>
)}
<form <form
id="create-project-form" id="create-project-form"
action={handleSubmit} action={handleSubmit}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
className="px-4 pt-4 pb-8 space-y-8" className="pt-12 pb-16 space-y-12"
> >
{/* Name Section */} {/* Name Section */}
<div className="space-y-4"> {USE_MULTIPLE_PROJECTS && (
<div className="flex flex-col gap-2"> <div className="space-y-4">
<label className={sectionHeaderStyles}> <div className="flex flex-col gap-4">
Name <label className={largeSectionHeaderStyles}>
</label> Name
<Textarea </label>
required <Textarea
name="name" required
value={name} name="name"
onChange={(e) => setName(e.target.value)} value={name}
className={clsx( onChange={(e) => setName(e.target.value)}
textareaStyles, className={clsx(
"min-h-[60px]", textareaStyles,
"text-base", "min-h-[60px]",
"text-gray-900 dark:text-gray-100" "text-base",
)} "text-gray-900 dark:text-gray-100"
placeholder={defaultName} )}
/> placeholder={defaultName}
/>
</div>
</div> </div>
</div> )}
{/* Custom Prompt Section - Only show when needed */}
{showCustomPrompt && (
<div className="space-y-4">
<div className="flex flex-col gap-4">
<label className={largeSectionHeaderStyles}>
{selectedTemplate === 'custom' ? 'What do you want to build?' : 'Customize the description'}
</label>
<div className="space-y-2">
<Textarea
value={customPrompt}
onChange={(e) => {
setCustomPrompt(e.target.value);
setPromptError(null);
}}
placeholder="Example: Create a customer support assistant that can handle product inquiries and returns"
className={clsx(
textareaStyles,
"text-base",
"text-gray-900 dark:text-gray-100",
promptError && "border-red-500 focus:ring-red-500/20"
)}
style={{ minHeight: "120px" }}
autoFocus
autoResize
required
/>
{promptError && (
<p className="text-sm text-red-500">
{promptError}
</p>
)}
</div>
</div>
</div>
)}
{/* Template Selection Section */} {/* Template Selection Section */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-4">
<label className={sectionHeaderStyles}> <label className={largeSectionHeaderStyles}>
Choose how to start How do you want to start?
</label> </label>
<select <select
value={selectedTemplate} value={selectedTemplate}
@ -259,9 +323,9 @@ export default function App() {
"dark:bg-[url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E')]" "dark:bg-[url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E')]"
)} )}
> >
<option value="blank">Start with a blank template</option> <option value="custom">Tell us what you want to build</option>
<option value="custom">Write your own starting prompt</option> <option value="blank">I&apos;ll provide a description later</option>
<optgroup label="Example Prompts"> <optgroup label="Customizable Examples">
{starting_copilot_prompts && {starting_copilot_prompts &&
Object.entries(starting_copilot_prompts) Object.entries(starting_copilot_prompts)
.filter(([name]) => name !== 'Blank Template') .filter(([name]) => name !== 'Blank Template')
@ -276,40 +340,6 @@ export default function App() {
</div> </div>
</div> </div>
{/* Custom Prompt Section - Only show when needed */}
{showCustomPrompt && (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<label className={sectionHeaderStyles}>
{selectedTemplate === 'custom' ? 'Write your prompt' : 'Customize the prompt'}
</label>
<div className="space-y-2">
<Textarea
value={customPrompt}
onChange={(e) => {
setCustomPrompt(e.target.value);
setPromptError(null);
}}
placeholder="Example: Create a customer support assistant that can handle product inquiries and returns"
className={clsx(
textareaStyles,
"min-h-[100px]",
"text-base",
"text-gray-900 dark:text-gray-100",
promptError && "border-red-500 focus:ring-red-500/20"
)}
autoResize
required
/>
{promptError && (
<p className="text-sm text-red-500">
{promptError}
</p>
)}
</div>
</div>
</div>
)}
{/* Submit Button */} {/* Submit Button */}
<div className="pt-6 w-full"> <div className="pt-6 w-full">

View file

@ -27,7 +27,7 @@ export function Submit() {
isLoading={pending} isLoading={pending}
startContent={<PlusIcon size={16} />} startContent={<PlusIcon size={16} />}
> >
Create project Create assistant
</Button> </Button>
</div> </div>
); );

View file

@ -35,6 +35,11 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(({
const [validationError, setValidationError] = useState<string | undefined>(); const [validationError, setValidationError] = useState<string | undefined>();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
// update local value when prop value changes
useEffect(() => {
setLocalValue(propValue as string);
}, [propValue]);
// Sync local state with prop value when not editing // Sync local state with prop value when not editing
useEffect(() => { useEffect(() => {
if (!isEditing) { if (!isEditing) {

View file

@ -10275,16 +10275,17 @@
} }
}, },
"node_modules/@modelcontextprotocol/sdk": { "node_modules/@modelcontextprotocol/sdk": {
"version": "1.7.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz",
"integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==", "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==",
"dependencies": { "dependencies": {
"content-type": "^1.0.5", "content-type": "^1.0.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"eventsource": "^3.0.2", "eventsource": "^3.0.2",
"express": "^5.0.1", "express": "^5.0.1",
"express-rate-limit": "^7.5.0", "express-rate-limit": "^7.5.0",
"pkce-challenge": "^4.1.0", "pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0", "raw-body": "^3.0.0",
"zod": "^3.23.8", "zod": "^3.23.8",
"zod-to-json-schema": "^3.24.1" "zod-to-json-schema": "^3.24.1"
@ -20112,9 +20113,9 @@
} }
}, },
"node_modules/pkce-challenge": { "node_modules/pkce-challenge": {
"version": "4.1.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
"integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
"engines": { "engines": {
"node": ">=16.20.0" "node": ">=16.20.0"
} }

File diff suppressed because it is too large Load diff

View file

@ -83,7 +83,6 @@ pytz = "^2024.2"
qdrant-client = "*" qdrant-client = "*"
Quart = "^0.20.0" Quart = "^0.20.0"
RapidFuzz = "^3.11.0" RapidFuzz = "^3.11.0"
redis = "^5.2.1"
requests = "^2.32.3" requests = "^2.32.3"
requests-toolbelt = "^1.0.0" requests-toolbelt = "^1.0.0"
setuptools = "^75.8.0" setuptools = "^75.8.0"

View file

@ -1,7 +1,11 @@
aiohttp==3.9.3 aiofiles==24.1.0
aiohappyeyeballs==2.6.1
aiohttp==3.11.14
aiosignal==1.3.2
annotated-types==0.7.0 annotated-types==0.7.0
anyio==4.8.0 anyio==4.8.0
asgiref asgiref==3.8.1
attrs==25.3.0
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
blinker==1.9.0 blinker==1.9.0
build==1.2.2.post1 build==1.2.2.post1
@ -11,22 +15,32 @@ cffi==1.17.1
charset-normalizer==3.4.1 charset-normalizer==3.4.1
cleo==2.1.0 cleo==2.1.0
click==8.1.8 click==8.1.8
colorama==0.4.6
crashtest==0.4.1 crashtest==0.4.1
distlib==0.3.9 distlib==0.3.9
distro==1.9.0 distro==1.9.0
dnspython==2.7.0 dnspython==2.7.0
dulwich==0.22.7 dulwich==0.22.8
et_xmlfile==2.0.0 et_xmlfile==2.0.0
eval_type_backport==0.2.2 eval_type_backport==0.2.2
fastjsonschema==2.21.1 fastjsonschema==2.21.1
filelock==3.17.0 filelock==3.18.0
findpython==0.6.3
firecrawl==1.9.0 firecrawl==1.9.0
Flask==3.1.0 Flask==3.1.0
frozenlist==1.5.0
griffe==1.6.2
grpcio==1.71.0
grpcio-tools==1.71.0
gunicorn==23.0.0 gunicorn==23.0.0
h11==0.14.0 h11==0.14.0
h2==4.2.0
hpack==4.1.0
httpcore==1.0.7 httpcore==1.0.7
httpx==0.27.2 httpx==0.27.2
hypercorn httpx-sse==0.4.0
Hypercorn==0.17.3
hyperframe==6.1.0
idna==3.10 idna==3.10
installer==0.7.0 installer==0.7.0
itsdangerous==2.2.0 itsdangerous==2.2.0
@ -40,24 +54,31 @@ keyring==25.6.0
lxml==5.3.0 lxml==5.3.0
markdownify==0.13.1 markdownify==0.13.1
MarkupSafe==3.0.2 MarkupSafe==3.0.2
mcp mcp==1.5.0
more-itertools==10.6.0 more-itertools==10.6.0
motor motor==3.7.0
msgpack==1.1.0 msgpack==1.1.0
multidict==6.2.0
mypy-extensions==1.0.0 mypy-extensions==1.0.0
nest-asyncio==1.6.0 nest-asyncio==1.6.0
numpy==2.2.1 numpy==2.2.1
openai openai==1.68.0
openai-agents openai-agents==0.0.4
openpyxl==3.1.5 openpyxl==3.1.5
packaging==24.2 packaging==24.2
pandas==2.2.3 pandas==2.2.3
pkginfo==1.12.0 pbs-installer==2025.3.17
platformdirs==4.3.6 pkginfo==1.12.1.2
poetry==2.0.1 platformdirs==4.3.7
poetry-core==2.0.1 poetry==2.1.1
poetry-core==2.1.1
portalocker==2.10.1
priority==2.0.0
propcache==0.3.0
protobuf==5.29.4
pycparser==2.22 pycparser==2.22
pydantic==2.10.5 pydantic==2.10.5
pydantic-settings==2.8.1
pydantic_core==2.27.2 pydantic_core==2.27.2
PyJWT==2.10.1 PyJWT==2.10.1
pymongo==4.10.1 pymongo==4.10.1
@ -66,28 +87,35 @@ python-dateutil==2.9.0.post0
python-docx==1.1.2 python-docx==1.1.2
python-dotenv==1.0.1 python-dotenv==1.0.1
pytz==2024.2 pytz==2024.2
qdrant-client qdrant-client==1.13.3
Quart==0.20.0 Quart==0.20.0
RapidFuzz==3.11.0 RapidFuzz==3.12.2
redis==5.2.1
requests==2.32.3 requests==2.32.3
requests-toolbelt==1.0.0 requests-toolbelt==1.0.0
setuptools==75.8.0 setuptools==75.8.0
shellingham==1.5.4 shellingham==1.5.4
six==1.17.0 six==1.17.0
sniffio==1.3.1 sniffio==1.3.1
sounddevice==0.5.1
soupsieve==2.6 soupsieve==2.6
sse-starlette==2.2.1
starlette==0.46.1
tabulate==0.9.0 tabulate==0.9.0
tomlkit==0.13.2 tomlkit==0.13.2
tqdm==4.67.1 tqdm==4.67.1
trove-classifiers==2025.1.15.22 trove-classifiers==2025.3.19.19
types-requests==2.32.0.20250306
typing-inspect==0.9.0 typing-inspect==0.9.0
typing_extensions==4.12.2 typing_extensions==4.12.2
tzdata==2024.2 tzdata==2024.2
urllib3==2.3.0 urllib3==2.3.0
virtualenv==20.29.1 uvicorn==0.34.0
virtualenv==20.29.3
waitress==2.1.2 waitress==2.1.2
websockets==13.1 websockets==13.1
Werkzeug==3.1.3 Werkzeug==3.1.3
wheel==0.44.0 wheel==0.44.0
wsproto==1.2.0
xattr==1.1.4 xattr==1.1.4
yarl==1.18.3
zstandard==0.23.0

View file

@ -3,8 +3,6 @@ from quart import Quart, request, jsonify, Response
from datetime import datetime from datetime import datetime
from functools import wraps from functools import wraps
import os import os
import redis
import uuid
import json import json
from hypercorn.config import Config from hypercorn.config import Config
from hypercorn.asyncio import serve from hypercorn.asyncio import serve
@ -17,7 +15,6 @@ from src.utils.common import common_logger, read_json_from_file
from pprint import pprint from pprint import pprint
logger = common_logger logger = common_logger
redis_client = redis.from_url(os.environ.get('REDIS_URL', 'redis://localhost:6379'))
app = Quart(__name__) app = Quart(__name__)
config = read_json_from_file("./configs/default_config.json") config = read_json_from_file("./configs/default_config.json")
@ -122,31 +119,17 @@ async def chat():
logger.error(f"Error: {str(e)}") logger.error(f"Error: {str(e)}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@app.route("/chat_stream_init", methods=["POST"])
@require_api_key
async def chat_stream_init():
# create a uuid for the stream
stream_id = str(uuid.uuid4())
# store the request data in redis with 10 minute TTL
data = await request.get_json()
redis_client.setex(f"stream_request_{stream_id}", 600, json.dumps(data))
return jsonify({"streamId": stream_id})
def format_sse(data: dict, event: str = None) -> str: def format_sse(data: dict, event: str = None) -> str:
msg = f"data: {json.dumps(data)}\n\n" msg = f"data: {json.dumps(data)}\n\n"
if event is not None: if event is not None:
msg = f"event: {event}\n{msg}" msg = f"event: {event}\n{msg}"
return msg return msg
@app.route("/chat_stream/<stream_id>", methods=["GET"]) @app.route("/chat_stream", methods=["POST"])
@require_api_key @require_api_key
async def chat_stream(stream_id): async def chat_stream():
# get the request data from redis # get the request data from the request
request_data = redis_client.get(f"stream_request_{stream_id}") request_data = await request.get_data()
if not request_data:
return jsonify({"error": "Stream not found"}), 404
print("Request:", request_data.decode('utf-8')) print("Request:", request_data.decode('utf-8'))
request_data = json.loads(request_data) request_data = json.loads(request_data)

View file

@ -65,7 +65,7 @@ def create_final_response(response, turn_messages, tokens_used, all_agents):
# Ensure tokens_used is a valid dictionary # Ensure tokens_used is a valid dictionary
if not isinstance(tokens_used, dict): if not isinstance(tokens_used, dict):
tokens_used = {"total": 100, "prompt": 50, "completion": 50} # Default values if not a dictionary tokens_used = {"total": 0, "prompt": 0, "completion": 0} # Default values if not a dictionary
# Ensure response has a tokens_used attribute that's a dictionary # Ensure response has a tokens_used attribute that's a dictionary
if not hasattr(response, 'tokens_used') or not isinstance(response.tokens_used, dict): if not hasattr(response, 'tokens_used') or not isinstance(response.tokens_used, dict):
@ -187,8 +187,8 @@ async def run_turn(
logger.info("Converted message added to response messages") logger.info("Converted message added to response messages")
print("Converted message added to response messages") print("Converted message added to response messages")
# Use a dictionary for tokens_used instead of a hard-coded integer # Use a dictionary for tokens_used
tokens_used = {"total": 100, "prompt": 50, "completion": 50} # Dummy values as placeholders tokens_used = {"total": 0, "prompt": 0, "completion": 0} # Default values as placeholders
# Ensure turn_messages can be extended with response.messages # Ensure turn_messages can be extended with response.messages
if hasattr(response, 'messages') and isinstance(response.messages, list): if hasattr(response, 'messages') and isinstance(response.messages, list):
@ -208,7 +208,7 @@ async def run_turn(
# Ensure tokens_used remains a proper dictionary # Ensure tokens_used remains a proper dictionary
if not isinstance(tokens_used, dict): if not isinstance(tokens_used, dict):
tokens_used = {"total": 100, "prompt": 50, "completion": 50} # Default values if not a dictionary tokens_used = {"total": 0, "prompt": 0, "completion": 0} # Default values if not a dictionary
response = create_response( response = create_response(
messages=[duplicate_msg], messages=[duplicate_msg],

View file

@ -38,62 +38,45 @@ class NewResponse(BaseModel):
error_msg: Optional[str] = "" error_msg: Optional[str] = ""
async def mock_tool(tool_name: str, args: str, description: str, mock_instructions: str) -> str: async def mock_tool(tool_name: str, args: str, description: str, mock_instructions: str) -> str:
""" try:
Handles tool execution by either using mock instructions or generating a response. print(f"Mock tool called for: {tool_name}")
Args: messages = [
tool_name: The name of the tool {"role": "system", "content": f"You are simulating the execution of a tool called '{tool_name}'.Here is the description of the tool: {description}. Here are the instructions for the mock tool: {mock_instructions}. Generate a realistic response as if the tool was actually executed with the given parameters."},
args: The arguments passed to the tool {"role": "user", "content": f"Generate a realistic response for the tool '{tool_name}' with these parameters: {args}. The response should be concise and focused on what the tool would actually return."}
tool_config: The configuration of the tool ]
Returns: print(f"Generating simulated response for tool: {tool_name}")
The response from the tool response_content = generate_openai_output(messages, output_type='text', model="gpt-4o")
""" return response_content
print(f"Mock tool called for: {tool_name}") except Exception as e:
logger.error(f"Error in mock_tool: {str(e)}")
return f"Error: {str(e)}"
messages = [
{"role": "system", "content": f"You are simulating the execution of a tool called '{tool_name}'.Here is the description of the tool: {description}. Here are the instructions for the mock tool: {mock_instructions}. Generate a realistic response as if the tool was actually executed with the given parameters."},
{"role": "user", "content": f"Generate a realistic response for the tool '{tool_name}' with these parameters: {args}. The response should be concise and focused on what the tool would actually return."}
]
print(f"Generating simulated response for tool: {tool_name}")
response_content = generate_openai_output(messages, output_type='text', model="gpt-4o")
return response_content
async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secret: str) -> str: async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secret: str) -> str:
""" try:
Calls the webhook with the given tool name and arguments. print(f"Calling webhook for tool: {tool_name}")
content_dict = {
Args: "toolCall": {
tool_name (str): The name of the tool to call. "function": {
args (str): The arguments for the tool as a JSON string. "name": tool_name,
"arguments": args
Returns: }
str: The response from the webhook, or an error message if the call fails.
"""
content_dict = {
"toolCall": {
"function": {
"name": tool_name,
"arguments": args
} }
} }
} request_body = {
request_body = { "content": json.dumps(content_dict)
"content": json.dumps(content_dict) }
}
# Prepare headers # Prepare headers
headers = {} headers = {}
if signing_secret: if signing_secret:
content_str = request_body["content"] content_str = request_body["content"]
body_hash = hashlib.sha256(content_str.encode('utf-8')).hexdigest() body_hash = hashlib.sha256(content_str.encode('utf-8')).hexdigest()
payload = {"bodyHash": body_hash} payload = {"bodyHash": body_hash}
signature_jwt = jwt.encode(payload, signing_secret, algorithm="HS256") signature_jwt = jwt.encode(payload, signing_secret, algorithm="HS256")
headers["X-Signature-Jwt"] = signature_jwt headers["X-Signature-Jwt"] = signature_jwt
try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(webhook_url, json=request_body, headers=headers) as response: async with session.post(webhook_url, json=request_body, headers=headers) as response:
if response.status == 200: if response.status == 200:
@ -104,58 +87,60 @@ async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secr
print(f"Webhook error: {error_msg}") print(f"Webhook error: {error_msg}")
return f"Error: {error_msg}" return f"Error: {error_msg}"
except Exception as e: except Exception as e:
print(f"Exception in call_webhook: {str(e)}") logger.error(f"Exception in call_webhook: {str(e)}")
return f"Error: Failed to call webhook - {str(e)}" return f"Error: Failed to call webhook - {str(e)}"
async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str: async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str:
""" try:
Calls the MCP with the given tool name and arguments. print(f"MCP tool called for: {tool_name}")
""" async with sse_client(url=mcp_server_url) as streams:
async with ClientSession(*streams) as session:
await session.initialize()
jargs = json.loads(args)
response = await session.call_tool(tool_name, arguments=jargs)
json_output = json.dumps([item.__dict__ for item in response.content], indent=2)
async with sse_client(url=mcp_server_url) as streams: return json_output
async with ClientSession(*streams) as session: except Exception as e:
await session.initialize() logger.error(f"Error in call_mcp: {str(e)}")
jargs = json.loads(args) return f"Error: {str(e)}"
response = await session.call_tool(tool_name, arguments=jargs)
json_output = json.dumps([item.__dict__ for item in response.content], indent=2)
return json_output
async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool_config: dict, complete_request: dict) -> str: async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool_config: dict, complete_request: dict) -> str:
"""
Handles all tool calls by dispatching to appropriate functions.
"""
print(f"Catch all called for tool: {tool_name}")
print(f"Args: {args}")
print(f"Tool config: {tool_config}")
# Create event loop for async operations
try: try:
loop = asyncio.get_event_loop() print(f"Catch all called for tool: {tool_name}")
except RuntimeError: print(f"Args: {args}")
loop = asyncio.new_event_loop() print(f"Tool config: {tool_config}")
asyncio.set_event_loop(loop)
response_content = None # Create event loop for async operations
if tool_config.get("mockTool", False) or complete_request.get("testProfile", {}).get("mockTools", False): try:
# Call mock_tool to handle the response (it will decide whether to use mock instructions or generate a response) loop = asyncio.get_event_loop()
if complete_request.get("testProfile", {}).get("mockPrompt", ""): except RuntimeError:
response_content = await mock_tool(tool_name, args, tool_config.get("description", ""), complete_request.get("testProfile", {}).get("mockPrompt", "")) loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
response_content = None
if tool_config.get("mockTool", False) or complete_request.get("testProfile", {}).get("mockTools", False):
# Call mock_tool to handle the response (it will decide whether to use mock instructions or generate a response)
if complete_request.get("testProfile", {}).get("mockPrompt", ""):
response_content = await mock_tool(tool_name, args, tool_config.get("description", ""), complete_request.get("testProfile", {}).get("mockPrompt", ""))
else:
response_content = await mock_tool(tool_name, args, tool_config.get("description", ""), tool_config.get("mockInstructions", ""))
print(response_content)
elif tool_config.get("isMcp", False):
mcp_server_name = tool_config.get("mcpServerName", "")
mcp_servers = complete_request.get("mcpServers", {})
mcp_server_url = next((server.get("url", "") for server in mcp_servers if server.get("name") == mcp_server_name), "")
response_content = await call_mcp(tool_name, args, mcp_server_url)
else: else:
response_content = await mock_tool(tool_name, args, tool_config.get("description", ""), tool_config.get("mockInstructions", "")) collection = db["projects"]
print(response_content) doc = collection.find_one({"_id": complete_request.get("projectId", "")})
elif tool_config.get("isMcp", False): signing_secret = doc.get("secret", "")
mcp_server_name = tool_config.get("mcpServerName", "") webhook_url = complete_request.get("toolWebhookUrl", "")
mcp_servers = complete_request.get("mcpServers", {}) response_content = await call_webhook(tool_name, args, webhook_url, signing_secret)
mcp_server_url = next((server.get("url", "") for server in mcp_servers if server.get("name") == mcp_server_name), "") return response_content
response_content = await call_mcp(tool_name, args, mcp_server_url) except Exception as e:
else: logger.error(f"Error in catch_all: {str(e)}")
collection = db["projects"] return f"Error: {str(e)}"
doc = collection.find_one({"_id": complete_request.get("projectId", "")})
signing_secret = doc.get("secret", "")
webhook_url = complete_request.get("toolWebhookUrl", "")
response_content = await call_webhook(tool_name, args, webhook_url, signing_secret)
return response_content
def get_rag_tool(config: dict, complete_request: dict) -> FunctionTool: def get_rag_tool(config: dict, complete_request: dict) -> FunctionTool:
@ -236,12 +221,11 @@ def get_agents(agent_configs, tool_configs, complete_request):
"type": "function", "type": "function",
"function": tool_config "function": tool_config
}) })
#TODO: Remove this once we have a way to handle the additionalProperties
tool_config['parameters']['additionalProperties'] = False
tool = FunctionTool( tool = FunctionTool(
name=tool_name, name=tool_name,
description=tool_config["description"], description=tool_config["description"],
params_json_schema=tool_config["parameters"], params_json_schema=tool_config["parameters"],
strict_json_schema=False,
on_invoke_tool=lambda ctx, args, _tool_name=tool_name, _tool_config=tool_config, _complete_request=complete_request: on_invoke_tool=lambda ctx, args, _tool_name=tool_name, _tool_config=tool_config, _complete_request=complete_request:
catch_all(ctx, args, _tool_name, _tool_config, _complete_request) catch_all(ctx, args, _tool_name, _tool_config, _complete_request)
) )
@ -255,10 +239,13 @@ def get_agents(agent_configs, tool_configs, complete_request):
# Create the agent object # Create the agent object
logger.debug(f"Creating Agent object for {agent_config['name']}") logger.debug(f"Creating Agent object for {agent_config['name']}")
print(f"Creating Agent object for {agent_config['name']}") print(f"Creating Agent object for {agent_config['name']}")
# add the name and description to the agent instructions
agent_instructions = f"## Your Name\n{agent_config['name']}\n\n## Description\n{agent_config['description']}\n\n## Instructions\n{agent_config['instructions']}"
try: try:
new_agent = NewAgent( new_agent = NewAgent(
name=agent_config["name"], name=agent_config["name"],
instructions=agent_config["instructions"], instructions=agent_instructions,
handoff_description=agent_config["description"], handoff_description=agent_config["description"],
tools=new_tools, tools=new_tools,
model=agent_config["model"], model=agent_config["model"],

View file

@ -9,7 +9,7 @@ services:
- "3000:3000" - "3000:3000"
environment: environment:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING} - MONGODB_CONNECTION_STRING=mongodb://mongo:27017/rowboat
- USE_AUTH=${USE_AUTH} - USE_AUTH=${USE_AUTH}
- AUTH0_SECRET=${AUTH0_SECRET} - AUTH0_SECRET=${AUTH0_SECRET}
- AUTH0_BASE_URL=${AUTH0_BASE_URL} - AUTH0_BASE_URL=${AUTH0_BASE_URL}
@ -49,7 +49,7 @@ services:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
- API_KEY=${AGENTS_API_KEY} - API_KEY=${AGENTS_API_KEY}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- MONGODB_URI=${MONGODB_CONNECTION_STRING} - MONGODB_URI=mongodb://mongo:27017/rowboat
- QDRANT_URL=${QDRANT_URL} - QDRANT_URL=${QDRANT_URL}
- QDRANT_API_KEY=${QDRANT_API_KEY} - QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped restart: unless-stopped
@ -65,25 +65,25 @@ services:
- API_KEY=${COPILOT_API_KEY} - API_KEY=${COPILOT_API_KEY}
restart: unless-stopped restart: unless-stopped
tools_webhook: # tools_webhook:
build: # build:
context: ./apps/tools_webhook # context: ./apps/experimental/tools_webhook
dockerfile: Dockerfile # dockerfile: Dockerfile
ports: # ports:
- "3005:3005" # - "3005:3005"
environment: # environment:
- SIGNING_SECRET=${SIGNING_SECRET} # - SIGNING_SECRET=${SIGNING_SECRET}
restart: unless-stopped # restart: unless-stopped
simulation_runner: # simulation_runner:
build: # build:
context: ./apps/simulation_runner # context: ./apps/experimental/simulation_runner
dockerfile: Dockerfile # dockerfile: Dockerfile
environment: # environment:
- MONGODB_URI=${MONGODB_CONNECTION_STRING} # - MONGODB_URI=mongodb://mongo:27017/rowboat
- ROWBOAT_API_HOST=http://rowboat:3000 # - ROWBOAT_API_HOST=http://rowboat:3000
- OPENAI_API_KEY=${OPENAI_API_KEY} # - OPENAI_API_KEY=${OPENAI_API_KEY}
restart: unless-stopped # restart: unless-stopped
setup_qdrant: setup_qdrant:
build: build:
@ -115,7 +115,7 @@ services:
profiles: [ "rag_files_worker" ] profiles: [ "rag_files_worker" ]
environment: environment:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING} - MONGODB_CONNECTION_STRING=mongodb://mongo:27017/rowboat
- GOOGLE_API_KEY=${GOOGLE_API_KEY} - GOOGLE_API_KEY=${GOOGLE_API_KEY}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
@ -133,7 +133,7 @@ services:
profiles: [ "rag_urls_worker" ] profiles: [ "rag_urls_worker" ]
environment: environment:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING} - MONGODB_CONNECTION_STRING=mongodb://mongo:27017/rowboat
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY} - FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY}
- QDRANT_URL=${QDRANT_URL} - QDRANT_URL=${QDRANT_URL}
- QDRANT_API_KEY=${QDRANT_API_KEY} - QDRANT_API_KEY=${QDRANT_API_KEY}
@ -147,23 +147,32 @@ services:
profiles: [ "rag_text_worker" ] profiles: [ "rag_text_worker" ]
environment: environment:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING} - MONGODB_CONNECTION_STRING=mongodb://mongo:27017/rowboat
- QDRANT_URL=${QDRANT_URL} - QDRANT_URL=${QDRANT_URL}
- QDRANT_API_KEY=${QDRANT_API_KEY} - QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped restart: unless-stopped
chat_widget: # chat_widget:
build: # build:
context: ./apps/chat_widget # context: ./apps/experimental/chat_widget
dockerfile: Dockerfile # dockerfile: Dockerfile
profiles: [ "chat_widget" ] # profiles: [ "chat_widget" ]
# ports:
# - "3006:3006"
# environment:
# - PORT=3006
# - CHAT_WIDGET_HOST=http://localhost:3006
# - ROWBOAT_HOST=http://localhost:3000
# restart: unless-stopped
mongo:
image: mongo:latest
ports: ports:
- "3006:3006" - "27017:27017"
environment:
- PORT=3006
- CHAT_WIDGET_HOST=http://localhost:3006
- ROWBOAT_HOST=http://localhost:3000
restart: unless-stopped restart: unless-stopped
attach: false
volumes:
- ./data/mongo:/data/db
redis: redis:
image: redis:latest image: redis:latest
@ -181,12 +190,12 @@ services:
# twilio_handler: # twilio_handler:
# build: # build:
# context: ./apps/twilio_handler # context: ./apps/experimental/twilio_handler
# dockerfile: Dockerfile # dockerfile: Dockerfile
# ports: # ports:
# - "4010:4010" # - "4010:4010"
# environment: # environment:
# - ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY} # - ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY}
# - ROWBOAT_API_HOST=http://rowboat:3000 # - ROWBOAT_API_HOST=http://rowboat:3000
# - MONGODB_URI=${MONGODB_CONNECTION_STRING} # - MONGODB_URI=mongodb://mongo:27017/rowboat
# restart: unless-stopped # restart: unless-stopped