|
|
@ -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
|
|
@ -1,3 +1,4 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
.vscode/
|
.vscode/
|
||||||
|
data/
|
||||||
|
|
|
||||||
435
README.md
|
|
@ -1,339 +1,62 @@
|
||||||

|

|
||||||
|
|
||||||
<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
|
||||||
Built on OpenAI's Agents SDK, **Rowboat is the fastest way to build multi-agents!**
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# 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
|
```bash
|
||||||
brew tap mongodb/brew
|
export OPENAI_API_KEY=your-openai-api-key
|
||||||
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
|
2. Clone the repository and start Rowboat docker
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=3t2Fpn6Vyds)
|
||||||
|
|
||||||
|
#### Add MCP servers
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=EbkIPCTyD58)
|
||||||
|
|
||||||
|
#### Use Firecrawl's MCP server and build a quick url scraping agent
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=_KZWla3Khco)
|
||||||
|
|
||||||
|
#### Improve agents with feedback
|
||||||
|
|
||||||
|
[](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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||

|
|
||||||
|
|
||||||

|
### 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.
|
|
||||||
|
|
||||||

|

|
||||||

|
|
||||||
|
### Adding MCP tools
|
||||||
|
|
||||||
|
[](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.
|
||||||
|
|
||||||

|

|
||||||

|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||

|
[](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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Also notice that copilot would likely have created a "Hub" agent that is "connected" to other agents.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 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.
|
||||||
|
|
||||||

|
[](https://www.youtube.com/watch?v=uoCEQtOe7eE)
|
||||||
BIN
apps/docs/docs/img/agent-instruction.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
apps/docs/docs/img/chat-delivery.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
BIN
apps/docs/docs/img/mock-response.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
apps/docs/docs/img/mock-tool.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 41 KiB |
|
|
@ -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)
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
|
||||||

|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||

|

|
||||||
|
|
||||||
## 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": {
|
||||||
// State returned by the API in the previous turn
|
"last_agent_name": "MainAgent"
|
||||||
}
|
}
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
#### Response
|
|
||||||
```json
|
## API Specification
|
||||||
|
|
||||||
|
### Request Schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
{
|
{
|
||||||
"messages": [
|
// Required fields
|
||||||
{
|
messages: Message[]; // Array of message objects representing the conversation history
|
||||||
"sender": "Outstanding Payments",
|
state: any; // State object from previous response, or null for first message
|
||||||
"role": "assistant",
|
|
||||||
// Response is not user-facing, to enable further post processing
|
// Optional fields
|
||||||
"response_type": "internal",
|
workflowId?: string; // Specific workflow ID to use (defaults to production workflow)
|
||||||
"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!",
|
testProfileId?: string; // Test profile ID for simulation
|
||||||
"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 I’ll assist you!",
|
|
||||||
"current_turn": true,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"state": {
|
|
||||||
"agent_data": [
|
|
||||||
// Omitted for brevity
|
|
||||||
],
|
|
||||||
"last_agent_name": "Outstanding Payments"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
@ -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?
|
|
||||||
```
|
```
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 391 B |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
|
|
@ -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
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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[:]
|
|
||||||
current_state = state
|
|
||||||
turns = 0
|
|
||||||
|
|
||||||
response_messages = []
|
|
||||||
response_state = None
|
|
||||||
has_tool_calls = False
|
|
||||||
|
|
||||||
while turns < max_turns:
|
|
||||||
# call api
|
# call api
|
||||||
response_data = self._call_api(
|
response_data = self._call_api(
|
||||||
messages=current_messages,
|
messages=messages,
|
||||||
state=current_state,
|
state=state,
|
||||||
skip_tool_calls=skip_tool_calls,
|
|
||||||
max_turns=max_turns,
|
|
||||||
workflow_id=workflow_id,
|
workflow_id=workflow_id,
|
||||||
test_profile_id=test_profile_id
|
test_profile_id=test_profile_id
|
||||||
)
|
)
|
||||||
|
|
||||||
current_messages.extend(response_data.messages)
|
if not response_data.messages[-1].agenticResponseType == 'external':
|
||||||
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)
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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,6 +104,7 @@ export function App({
|
||||||
}
|
}
|
||||||
rightActions={
|
rightActions={
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
{USE_TESTING_FEATURE && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -112,6 +114,7 @@ export function App({
|
||||||
>
|
>
|
||||||
<UserIcon className="w-4 h-4" />
|
<UserIcon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
{USE_TESTING_FEATURE && (
|
||||||
<ProfileContextBox
|
<ProfileContextBox
|
||||||
content={testProfile?.context || systemMessage || ''}
|
content={testProfile?.context || systemMessage || ''}
|
||||||
onChange={onSystemMessageChange}
|
onChange={onSystemMessageChange}
|
||||||
locked={testProfile !== null}
|
locked={testProfile !== null}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto pr-1
|
<div className="flex-1 overflow-auto pr-1
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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,12 +185,20 @@ 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 */}
|
||||||
|
{USE_MULTIPLE_PROJECTS && (
|
||||||
<div className="overflow-auto">
|
<div className="overflow-auto">
|
||||||
<SearchProjects
|
<SearchProjects
|
||||||
projects={projects}
|
projects={projects}
|
||||||
|
|
@ -193,26 +208,37 @@ export default function App() {
|
||||||
className="h-full"
|
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"
|
||||||
|
)}>
|
||||||
|
<section className={clsx(
|
||||||
|
"card h-full",
|
||||||
|
!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">
|
<SectionHeading subheading="Set up a new AI assistant">
|
||||||
Create a new project
|
Create a new project
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
</div>
|
</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 */}
|
||||||
|
{USE_MULTIPLE_PROJECTS && (
|
||||||
<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}>
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
|
@ -230,12 +256,50 @@ 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-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'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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
15
apps/rowboat/package-lock.json
generated
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
237
apps/rowboat_agents/poetry.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -38,20 +38,9 @@ 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.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tool_name: The name of the tool
|
|
||||||
args: The arguments passed to the tool
|
|
||||||
tool_config: The configuration of the tool
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The response from the tool
|
|
||||||
"""
|
|
||||||
print(f"Mock tool called for: {tool_name}")
|
print(f"Mock tool called for: {tool_name}")
|
||||||
|
|
||||||
|
|
||||||
messages = [
|
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": "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."}
|
{"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."}
|
||||||
|
|
@ -60,18 +49,13 @@ async def mock_tool(tool_name: str, args: str, description: str, mock_instructio
|
||||||
print(f"Generating simulated response for tool: {tool_name}")
|
print(f"Generating simulated response for tool: {tool_name}")
|
||||||
response_content = generate_openai_output(messages, output_type='text', model="gpt-4o")
|
response_content = generate_openai_output(messages, output_type='text', model="gpt-4o")
|
||||||
return response_content
|
return response_content
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in mock_tool: {str(e)}")
|
||||||
|
return f"Error: {str(e)}"
|
||||||
|
|
||||||
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}")
|
||||||
|
|
||||||
Args:
|
|
||||||
tool_name (str): The name of the tool to call.
|
|
||||||
args (str): The arguments for the tool as a JSON string.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The response from the webhook, or an error message if the call fails.
|
|
||||||
"""
|
|
||||||
content_dict = {
|
content_dict = {
|
||||||
"toolCall": {
|
"toolCall": {
|
||||||
"function": {
|
"function": {
|
||||||
|
|
@ -93,7 +77,6 @@ async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secr
|
||||||
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,14 +87,12 @@ 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 sse_client(url=mcp_server_url) as streams:
|
||||||
async with ClientSession(*streams) as session:
|
async with ClientSession(*streams) as session:
|
||||||
await session.initialize()
|
await session.initialize()
|
||||||
|
|
@ -120,11 +101,12 @@ async def call_mcp(tool_name: str, args: str, mcp_server_url: str) -> str:
|
||||||
json_output = json.dumps([item.__dict__ for item in response.content], indent=2)
|
json_output = json.dumps([item.__dict__ for item in response.content], indent=2)
|
||||||
|
|
||||||
return json_output
|
return json_output
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in call_mcp: {str(e)}")
|
||||||
|
return f"Error: {str(e)}"
|
||||||
|
|
||||||
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:
|
||||||
"""
|
try:
|
||||||
Handles all tool calls by dispatching to appropriate functions.
|
|
||||||
"""
|
|
||||||
print(f"Catch all called for tool: {tool_name}")
|
print(f"Catch all called for tool: {tool_name}")
|
||||||
print(f"Args: {args}")
|
print(f"Args: {args}")
|
||||||
print(f"Tool config: {tool_config}")
|
print(f"Tool config: {tool_config}")
|
||||||
|
|
@ -156,6 +138,9 @@ async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool
|
||||||
webhook_url = complete_request.get("toolWebhookUrl", "")
|
webhook_url = complete_request.get("toolWebhookUrl", "")
|
||||||
response_content = await call_webhook(tool_name, args, webhook_url, signing_secret)
|
response_content = await call_webhook(tool_name, args, webhook_url, signing_secret)
|
||||||
return response_content
|
return response_content
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in catch_all: {str(e)}")
|
||||||
|
return f"Error: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
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"],
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||