diff --git a/.env.example b/.env.example index 1d7210dd..1d0adf59 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ # Basic configuration # ------------------------------------------------------------ -MONGODB_CONNECTION_STRING=mongodb://host.docker.internal:27017/rowboat OPENAI_API_KEY= diff --git a/.gitignore b/.gitignore index 1a196ddf..6e2254a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store .env -.vscode/ \ No newline at end of file +.vscode/ +data/ diff --git a/README.md b/README.md index c44e852c..f4626a00 100644 --- a/README.md +++ b/README.md @@ -1,339 +1,62 @@ ![ui](/assets/banner.png) -

The AI-assisted agent builder

+

Let AI build multi-agent workflows for you in minutes

[Quickstart](#quick-start) | [Docs](https://docs.rowboatlabs.com/) | [Website](https://www.rowboatlabs.com/) | [Discord](https://discord.gg/jHhUKkKHn8)
-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 -- 🧪 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 +Powered by OpenAI's Agents SDK, Rowboat is the fastest way to build multi-agents! - - -Built on OpenAI's Agents SDK, **Rowboat is the fastest way to build multi-agents!** - - -![ui](/assets/ui_revamp_screenshot.png) - -# Quick start - -## Prerequisites - -Before running Rowboat, ensure you have: - -1. **Docker Desktop** - - [Download Docker Desktop](https://www.docker.com/products/docker-desktop) - -2. **OpenAI API Key** - - Obtain from your OpenAI account. - -3. **MongoDB** - - macOS (Homebrew) - ```bash - brew tap mongodb/brew - brew install mongodb-community@8.0 - brew services start mongodb-community@8.0 - ``` - - Other platforms: Refer to the [MongoDB documentation](https://www.mongodb.com/docs/manual/installation/) for details. - -## Setup Rowboat - -1. **Clone the Repository** +## Quick start +1. Set your OpenAI key + ```bash + export OPENAI_API_KEY=your-openai-api-key + ``` + +2. Clone the repository and start Rowboat docker ```bash git clone git@github.com:rowboatlabs/rowboat.git 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 ``` -4. **Access the App** - - Visit [http://localhost:3000](http://localhost:3000). +3. Access the app at [http://localhost:3000](http://localhost:3000). + +## Demos + +#### Create a multi-agent assistant with tools from a single prompt + +[![Prompt to agents](https://img.youtube.com/vi/3t2Fpn6Vyds/0.jpg)](https://www.youtube.com/watch?v=3t2Fpn6Vyds) + +#### Add MCP servers + +[![MCP server](https://img.youtube.com/vi/EbkIPCTyD58/0.jpg)](https://www.youtube.com/watch?v=EbkIPCTyD58) + +#### Use Firecrawl's MCP server and build a quick url scraping agent + +[![Firecrawl MCP](https://img.youtube.com/vi/_KZWla3Khco/0.jpg)](https://www.youtube.com/watch?v=_KZWla3Khco) + +#### Improve agents with feedback + +[![Feedback](https://img.youtube.com/vi/uoCEQtOe7eE/0.jpg)](https://www.youtube.com/watch?v=uoCEQtOe7eE) -Refer to [Docs](https://docs.rowboatlabs.com/) to learn how to start building agents with Rowboat. +## Integrate with Rowboat agents -# Advanced - -## 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. - -ui - -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= - ``` - -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//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= # 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= - QDRANT_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= - ``` - -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:::/*", - "arn:aws:s3:::" - ] - } - ] - } - ``` - -3. Update Environment Variables - ```ini - USE_RAG_UPLOADS=true - AWS_ACCESS_KEY_ID= - AWS_SECRET_ACCESS_KEY= - RAG_UPLOADS_S3_BUCKET= - RAG_UPLOADS_S3_REGION= - 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//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= - ``` - -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//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="", - api_key="" # Generate this from /projects//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//config` +There are 2 ways to integrate with the agents you create in Rowboat +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 curl --location 'http://localhost:3000/api/v1//chat' \ --header 'Content-Type: application/json' \ @@ -347,61 +70,37 @@ There are two ways to interact with Rowboat's API: ] }' ``` - which gives: - ```json - { - "messages": [ - { - "role": "assistant", - "tool_calls": [ - { - "function": { - "arguments": "{\"location\":\"London\",\"units\":\"metric\"}", - "name": "weather_lookup_tool" - }, - "id": "call_r6XKuVxmGRogofkyFZIacdL0", - "type": "function" - } - ], - "agenticSender": "Example Agent", - "agenticResponseType": "internal" - } - ], - "state": { - // .. state data - } - } + + +2. Python SDK + - You can use the included Python SDK to interact with the Agents + - See [SDK Docs](https://docs.rowboatlabs.com/using_the_sdk/) for details + ```python + from rowboat import Client + from rowboat.schema import UserMessage, SystemMessage + + # Initialize the client + client = Client( + host="", + project_id="", + api_key="" + ) + + # Create messages + messages = [ + SystemMessage(role='system', content="You are a helpful assistant"), + UserMessage(role='user', content="Hello, how are you?") + ] + + # 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. diff --git a/apps/docs/docs/add_tools.md b/apps/docs/docs/add_tools.md index a1e151da..3e95e175 100644 --- a/apps/docs/docs/add_tools.md +++ b/apps/docs/docs/add_tools.md @@ -1,19 +1,19 @@ ## Add tools to agents -Copilot can help you add tools to agents. +Copilot can help you add tools to agents. You can (a) add a mock tool, (b) add a tool from an MCP server, (c) integrate with you own tools using a webhook. -### Instruct copilot to add tools -![Add Tool](img/add-tool.png) -![Example Tool](img/example-tool.png) +### Adding mock tools +You can mock any tool you have created by checking the 'Mock tool responses' option. -### Inspect tools and agents -Note how copilot not only creates the tool definitions for you, but also updates the relevant agent instructions to use the tool and connects the tool to the agent. -![Inspect Agent Tools](img/inspect-agent-tools.png) -![Inspect Agent Tool Connections](img/inspect-agent-tool-connections.png) +![Example Tool](img/mock-tool.png) + +### Adding MCP tools + +[![MCP server](https://img.youtube.com/vi/EbkIPCTyD58/0.jpg)](https://www.youtube.com/watch?v=EbkIPCTyD58) + ### Debug tool calls in the playground When agents call tools during a chat in the playground, the tool call parameters and response are available for debugging real-time. For testing purposes, the platform can produce mock tool responses in the playground, without integrating actual tools. -![Mock Tool Responses](img/mock-tool-responses.png) -![Debug Tool Calls](img/debug-tool-calls.png) \ No newline at end of file +![Mock Tool Responses](img/mock-response.png) \ No newline at end of file diff --git a/apps/docs/docs/agents.md b/apps/docs/docs/agents.md index 93e8a37d..6ffb91a3 100644 --- a/apps/docs/docs/agents.md +++ b/apps/docs/docs/agents.md @@ -19,21 +19,11 @@ The agent uses examples as a reference for behavior in different scenarios. Whil ### Prompts 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 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 -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 -RowBoat currently supports OpenAI LLMs. Agents can be configured to use any of the OpenAI LLMs. - -### 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 \ No newline at end of file +RowBoat currently supports OpenAI LLMs. Agents can be configured to use GPT-4o or GPT-4o-mini. \ No newline at end of file diff --git a/apps/docs/docs/create_agents.md b/apps/docs/docs/create_agents.md index c9aa40cd..9237cfc4 100644 --- a/apps/docs/docs/create_agents.md +++ b/apps/docs/docs/create_agents.md @@ -4,20 +4,17 @@ Copilot can set up agents for you from scratch. ### Instruct copilot First, tell it about the initial set of agents that make up your assistant. -![Create Initial Agents](img/copilot-create.png) +[![Prompt to agents](https://img.youtube.com/vi/3t2Fpn6Vyds/0.jpg)](https://www.youtube.com/watch?v=3t2Fpn6Vyds) Using copilot to create your initial set of agents helps you leverage best practices in formatting agent instructions and connecting agents to each other as a graph, all of which have been baked into copilot. ### Inspect the agents -Once you apply changes, inspect the agents to see how copilot has built them. Specifically, note the Instructions, Examples and Connected Agents in each agent. +Once you apply changes, inspect the agents to see how copilot has built them. Specifically, note the Instructions, and Examples in each agent. -![Agent Config](img/agent-config.png) +![Agent Config](img/agent-instruction.png) -Also notice that copilot would likely have created a "Hub" agent that is "connected" to other agents. - -![Hub Agent Config](img/hub-config.png) ### Make changes if needed -Tweak the instructions and examples manually if needed. +Tweak the instructions and examples through the copilot, or generate instructions button, or by manually editing it. -![Edit Agent Manually](img/edit-agent-manually.png) \ No newline at end of file +[![Feedback](https://img.youtube.com/vi/uoCEQtOe7eE/0.jpg)](https://www.youtube.com/watch?v=uoCEQtOe7eE) \ No newline at end of file diff --git a/apps/docs/docs/img/agent-instruction.png b/apps/docs/docs/img/agent-instruction.png new file mode 100644 index 00000000..07c203d4 Binary files /dev/null and b/apps/docs/docs/img/agent-instruction.png differ diff --git a/apps/docs/docs/img/chat-delivery.png b/apps/docs/docs/img/chat-delivery.png new file mode 100644 index 00000000..b20dd369 Binary files /dev/null and b/apps/docs/docs/img/chat-delivery.png differ diff --git a/apps/docs/docs/img/dev-config.png b/apps/docs/docs/img/dev-config.png index 3010566d..d9b45efa 100644 Binary files a/apps/docs/docs/img/dev-config.png and b/apps/docs/docs/img/dev-config.png differ diff --git a/apps/docs/docs/img/mock-response.png b/apps/docs/docs/img/mock-response.png new file mode 100644 index 00000000..3f3b68c7 Binary files /dev/null and b/apps/docs/docs/img/mock-response.png differ diff --git a/apps/docs/docs/img/mock-tool.png b/apps/docs/docs/img/mock-tool.png new file mode 100644 index 00000000..ab3c492f Binary files /dev/null and b/apps/docs/docs/img/mock-tool.png differ diff --git a/apps/docs/docs/img/prod-deploy.png b/apps/docs/docs/img/prod-deploy.png index fa22f93a..f1745213 100644 Binary files a/apps/docs/docs/img/prod-deploy.png and b/apps/docs/docs/img/prod-deploy.png differ diff --git a/apps/docs/docs/index.md b/apps/docs/docs/index.md index 07385527..89626228 100644 --- a/apps/docs/docs/index.md +++ b/apps/docs/docs/index.md @@ -1,8 +1,8 @@ # 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/) - 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? **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. @@ -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
performs tasks using tools, based on instructions |• Configurable using plain language instructions
• Orchestrate between agents connected as a graph
• Can access tools and knowledge sources (RAG)| | Playground | Interactive environment to test assistants
conversationally as you build them |• Real-time testing and debugging
• Inspect parameters and results of tool calls in-line
• Converse with individual agents or the entire assistant| | Copilot | AI-powered concierge that creates and
updates agents and tools on your behalf |• Context-aware of all components including playground
• Improves agents based on conversations and feedback
• Understands your requests in plain language| -| Simulator | Simulates real-world user interactions
with your assistant |• Maintain and run a test-bench of different scenarios
• Mock tool responses for quick testing
• Reproduce your end-user's experience comprehensively| ### 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 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 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](/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 **RowBoat Studio:** 1. Describe the assistant you are looking to build, to **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** -5. Create and run a test-bench of scenarios in the **simulator** -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. +6. Deploy and use the HTTP API or Python SDK to integrate the agents into your system ## 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 2. **Integrate** tools and MCP servers in minutes @@ -56,5 +49,5 @@ Accelerate your path to production-ready multi-agent systems. ## 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) \ No newline at end of file diff --git a/apps/docs/docs/playground.md b/apps/docs/docs/playground.md index a18c6411..3f1f0590 100644 --- a/apps/docs/docs/playground.md +++ b/apps/docs/docs/playground.md @@ -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. -![Try Chat](img/try-chat.png) - -In the playground, you can also set initial context at start of chat, that will be passed to all agents. This is typically used for providing user identity information such as user ID, login email, etc. -![Use System Message](img/sys-msg.png) - -### Ask copilot questions -You can ask copilot clarifications about the chat, such as why the agents responded a certain way or why an agent was invoked. - -![Copilot Clarifications](img/copilot-clarifications.png) \ No newline at end of file +![Try Chat](img/chat-delivery.png) \ No newline at end of file diff --git a/apps/docs/docs/tools.md b/apps/docs/docs/tools.md index c5f17128..162dad36 100644 --- a/apps/docs/docs/tools.md +++ b/apps/docs/docs/tools.md @@ -3,4 +3,4 @@ - Tools can be defined once in RowBoat Studio and reused across different agents. - 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. -- Developers can easily connect tools to APIs by configuring a Webhook URL in Studio, to which all tool calls will be routed. \ No newline at end of file +- Developers can easily connect tools to APIs by configuring MCP servers or Webhook URL in Settings. \ No newline at end of file diff --git a/apps/docs/docs/using_the_api.md b/apps/docs/docs/using_the_api.md index 5e599a3a..413540ab 100644 --- a/apps/docs/docs/using_the_api.md +++ b/apps/docs/docs/using_the_api.md @@ -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. ![Developer Configs](img/dev-config.png) -## Call the API +## API Endpoint -When you provide your Project ID in the API call, RowBoat uses the version of your assistant deployed to production. +``` +POST /api/v1//chat +``` -**Request parameters:** +Where: -- `messages`: history of all messages in the conversation till now (system, user, tool and assistant messages) -- `state`: generated from the previous turn (this is needed because the API does not maintain state on its own) +- For self-hosted: `` is `http://localhost:3000` -**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) -- `state`: to be passed to the next turn +Include your API key in the Authorization header: -### API Host -- For the open source installation, the `` is [http://localhost:3000](http://localhost:3000) -- When using the hosted app, the `` is [https://app.rowboatlabs.com](https://app.rowboatlabs.com) - -### Example first turn of a chat +``` +Authorization: Bearer +``` -#### Request +## Examples + +### First Turn ```bash curl --location '/api/v1//chat' \ @@ -38,206 +38,129 @@ curl --location '/api/v1//chat' \ --header 'Authorization: Bearer ' \ --data '{ "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", - "content": "What is my outstanding balance and how do I make the payment?" + "content": "Hello, can you help me?" } ], - "state": { - "last_agent_name": "Credit Card Hub" - // Last agent used in the previous turn - // Set to the "start agent" for first turn of chats - } + "state": null }' ``` -#### Response + +Response: ```json { "messages": [ { - "sender": "Credit Card Hub", "role": "assistant", - "response_type": "internal", - "content": null, - "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" - } - ] + "content": "Hello! Yes, I'd be happy to help you. What can I assist you with today?", + "agenticResponseType": "external" } ], "state": { - "agent_data": [ - // 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" + "last_agent_name": "MainAgent" } } - ``` -### Example where the assistant is expecting a tool response -#### Request +### Subsequent Turn + +Notice how we include both the previous messages and the state from the last response: + ```bash -curl --location 'http://localhost:3000/api/v1//chat' \ +curl --location '/api/v1//chat' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ "messages": [ { - "role": "system", - "content": "UserID: 345227" + "role": "user", + "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", - "content": "What is my outstanding balance and how do I make the payment?" - }, - { - "sender": "Credit Card Hub", - "role": "assistant", - "response_type": "internal", - "content": null, - "tool_calls": [ - { - "function": { - "arguments": "{\"args\":\"\",\"kwargs\":\"\"}", - "name": "transfer_to_outstanding_payments" - }, - "id": "call_SLyQKXt9ZMqnxSqJjo9j1fU5", - "type": "function" - } - ], - }, - { - "role": "tool", - "tool_name": "transfer_to_outstanding_payments", - "content": "{\"assistant\": \"Outstanding Payments\"}", - "tool_call_id": "call_SLyQKXt9ZMqnxSqJjo9j1fU5" - }, - { - "sender": "Outstanding Payments", - "role": "assistant", - "response_type": "internal", - "content": null, - "tool_calls": [ - { - "function": { - "arguments": "{\"user_id\":\"345227\"}", - "name": "get_outstanding_balance" - }, - "id": "call_MNAUg7UTszYMt5RL4n5QqUTw", - "type": "function" - } - ], - }, - { - // New message is a tool response to the previous tool call - "role": "tool", - "tool_name": "get_outstanding_balance" - "content": "{\"result\":{\"outstanding_balance\":\"$250.00\",\"due_date\":\"2025-02-15\",\"payment_methods\":[\"Credit Card\",\"Bank Transfer\",\"PayPal\"]}}", - "tool_call_id": "call_MNAUg7UTszYMt5RL4n5QqUTw", - }, - ], - "state": { - // State returned by the API in the previous turn - } -}' -``` -#### Response -```json -{ - "messages": [ - { - "sender": "Outstanding Payments", - "role": "assistant", - // Response is not user-facing, to enable further post processing - "response_type": "internal", - "content": "Your outstanding balance is $250.00, due by February 15, 2025.\n\nYou have several payment options available, including:\n- **Credit Card**\n- **Bank Transfer**\n- **PayPal**\n\nPlease let me know which option you'd like to use, and I'll guide you through the process!", - "current_turn": true - }, - { - "sender": "Outstanding Payments >> Post process", - "role": "assistant", - // Response is user-facing - "response_type": "external", - "content": "Your outstanding balance is $250.00, due by February 15, 2025. \n\nPayment options include:\n- **Credit Card:** You can use your saved Visa card ending in 1234.\n- **Bank Transfer**\n- **PayPal**\n\nLet me know your preferred payment method, and I’ll assist you!", - "current_turn": true, + "content": "What services do you offer?" } ], "state": { - "agent_data": [ - // Omitted for brevity - ], - "last_agent_name": "Outstanding Payments" + "last_agent_name": "MainAgent" } +}' +``` + +## API Specification + +### Request Schema + +```typescript +{ + // Required fields + messages: Message[]; // Array of message objects representing the conversation history + state: any; // State object from previous response, or null for first message + + // Optional fields + workflowId?: string; // Specific workflow ID to use (defaults to production workflow) + testProfileId?: string; // Test profile ID for simulation } -``` \ No newline at end of file +``` + +### 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 \ No newline at end of file diff --git a/apps/docs/docs/using_the_sdk.md b/apps/docs/docs/using_the_sdk.md index 802b5a3e..c867bcbc 100644 --- a/apps/docs/docs/using_the_sdk.md +++ b/apps/docs/docs/using_the_sdk.md @@ -13,13 +13,68 @@ This is a guide on using the RowBoat Python SDK as an alternative to the [RowBoa ## 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 -from rowboat import Client -from rowboat.schema import UserMessage, SystemMessage +from rowboat import Client, StatefulChat + +# Initialize the client +client = Client( + host="", + project_id="", + 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="" +) +``` + +#### Using a test profile + +You can specify a test profile ID to use a specific test configuration: + +```python +chat = StatefulChat( + client, + 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 client = Client( @@ -30,57 +85,15 @@ client = Client( # Create messages messages = [ - SystemMessage(role='system', content="You are a helpful assistant"), UserMessage(role='user', content="Hello, how are you?") ] # Get response -response_messages, state = client.chat(messages=messages) -print(response_messages[-1].content) +response = client.chat(messages=messages) +print(response.messages[-1].content) -# For subsequent messages, include previous messages and state -messages.extend(response_messages) +# For subsequent messages, you need to manage the message history and state manually +messages.extend(response.messages) messages.append(UserMessage(role='user', content="What's your name?")) -response_messages, state = client.chat(messages=messages, state=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? +response = client.chat(messages=messages, state=response.state) ``` \ No newline at end of file diff --git a/apps/docs/mkdocs.yml b/apps/docs/mkdocs.yml index 147fd033..e6e6ea50 100644 --- a/apps/docs/mkdocs.yml +++ b/apps/docs/mkdocs.yml @@ -8,26 +8,17 @@ nav: - Introduction: index.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: - - Overview: studio_overview.md + - Building in Studio: - Create agents: create_agents.md - Test chats in the playground: playground.md - Add tools: add_tools.md - Update agents: update_agents.md - - Simulate scenarios: simulate.md - API & SDK: - Using the API: using_the_api.md - Using the SDK: using_the_sdk.md - + - Concepts: - Agents: agents.md - - Graph: graph.md - Tools: tools.md - Prompts: prompts.md - - Data Sources: data_sources.md diff --git a/apps/chat_widget/.dockerignore b/apps/experimental/chat_widget/.dockerignore similarity index 100% rename from apps/chat_widget/.dockerignore rename to apps/experimental/chat_widget/.dockerignore diff --git a/apps/chat_widget/.eslintrc.json b/apps/experimental/chat_widget/.eslintrc.json similarity index 100% rename from apps/chat_widget/.eslintrc.json rename to apps/experimental/chat_widget/.eslintrc.json diff --git a/apps/chat_widget/.gitignore b/apps/experimental/chat_widget/.gitignore similarity index 100% rename from apps/chat_widget/.gitignore rename to apps/experimental/chat_widget/.gitignore diff --git a/apps/chat_widget/Dockerfile b/apps/experimental/chat_widget/Dockerfile similarity index 100% rename from apps/chat_widget/Dockerfile rename to apps/experimental/chat_widget/Dockerfile diff --git a/apps/chat_widget/README.md b/apps/experimental/chat_widget/README.md similarity index 100% rename from apps/chat_widget/README.md rename to apps/experimental/chat_widget/README.md diff --git a/apps/chat_widget/app/api/bootstrap.js/route.ts b/apps/experimental/chat_widget/app/api/bootstrap.js/route.ts similarity index 100% rename from apps/chat_widget/app/api/bootstrap.js/route.ts rename to apps/experimental/chat_widget/app/api/bootstrap.js/route.ts diff --git a/apps/chat_widget/app/app.tsx b/apps/experimental/chat_widget/app/app.tsx similarity index 100% rename from apps/chat_widget/app/app.tsx rename to apps/experimental/chat_widget/app/app.tsx diff --git a/apps/chat_widget/app/favicon.ico b/apps/experimental/chat_widget/app/favicon.ico similarity index 100% rename from apps/chat_widget/app/favicon.ico rename to apps/experimental/chat_widget/app/favicon.ico diff --git a/apps/chat_widget/app/fonts/GeistMonoVF.woff b/apps/experimental/chat_widget/app/fonts/GeistMonoVF.woff similarity index 100% rename from apps/chat_widget/app/fonts/GeistMonoVF.woff rename to apps/experimental/chat_widget/app/fonts/GeistMonoVF.woff diff --git a/apps/chat_widget/app/fonts/GeistVF.woff b/apps/experimental/chat_widget/app/fonts/GeistVF.woff similarity index 100% rename from apps/chat_widget/app/fonts/GeistVF.woff rename to apps/experimental/chat_widget/app/fonts/GeistVF.woff diff --git a/apps/chat_widget/app/globals.css b/apps/experimental/chat_widget/app/globals.css similarity index 100% rename from apps/chat_widget/app/globals.css rename to apps/experimental/chat_widget/app/globals.css diff --git a/apps/chat_widget/app/layout.tsx b/apps/experimental/chat_widget/app/layout.tsx similarity index 100% rename from apps/chat_widget/app/layout.tsx rename to apps/experimental/chat_widget/app/layout.tsx diff --git a/apps/chat_widget/app/markdown-content.tsx b/apps/experimental/chat_widget/app/markdown-content.tsx similarity index 100% rename from apps/chat_widget/app/markdown-content.tsx rename to apps/experimental/chat_widget/app/markdown-content.tsx diff --git a/apps/chat_widget/app/page.tsx b/apps/experimental/chat_widget/app/page.tsx similarity index 100% rename from apps/chat_widget/app/page.tsx rename to apps/experimental/chat_widget/app/page.tsx diff --git a/apps/chat_widget/app/providers.tsx b/apps/experimental/chat_widget/app/providers.tsx similarity index 100% rename from apps/chat_widget/app/providers.tsx rename to apps/experimental/chat_widget/app/providers.tsx diff --git a/apps/chat_widget/next.config.mjs b/apps/experimental/chat_widget/next.config.mjs similarity index 100% rename from apps/chat_widget/next.config.mjs rename to apps/experimental/chat_widget/next.config.mjs diff --git a/apps/chat_widget/package-lock.json b/apps/experimental/chat_widget/package-lock.json similarity index 100% rename from apps/chat_widget/package-lock.json rename to apps/experimental/chat_widget/package-lock.json diff --git a/apps/chat_widget/package.json b/apps/experimental/chat_widget/package.json similarity index 100% rename from apps/chat_widget/package.json rename to apps/experimental/chat_widget/package.json diff --git a/apps/chat_widget/postcss.config.mjs b/apps/experimental/chat_widget/postcss.config.mjs similarity index 100% rename from apps/chat_widget/postcss.config.mjs rename to apps/experimental/chat_widget/postcss.config.mjs diff --git a/apps/chat_widget/public/bootstrap.template.js b/apps/experimental/chat_widget/public/bootstrap.template.js similarity index 100% rename from apps/chat_widget/public/bootstrap.template.js rename to apps/experimental/chat_widget/public/bootstrap.template.js diff --git a/apps/chat_widget/public/file.svg b/apps/experimental/chat_widget/public/file.svg similarity index 100% rename from apps/chat_widget/public/file.svg rename to apps/experimental/chat_widget/public/file.svg diff --git a/apps/chat_widget/public/globe.svg b/apps/experimental/chat_widget/public/globe.svg similarity index 100% rename from apps/chat_widget/public/globe.svg rename to apps/experimental/chat_widget/public/globe.svg diff --git a/apps/chat_widget/public/next.svg b/apps/experimental/chat_widget/public/next.svg similarity index 100% rename from apps/chat_widget/public/next.svg rename to apps/experimental/chat_widget/public/next.svg diff --git a/apps/chat_widget/public/vercel.svg b/apps/experimental/chat_widget/public/vercel.svg similarity index 100% rename from apps/chat_widget/public/vercel.svg rename to apps/experimental/chat_widget/public/vercel.svg diff --git a/apps/chat_widget/public/window.svg b/apps/experimental/chat_widget/public/window.svg similarity index 100% rename from apps/chat_widget/public/window.svg rename to apps/experimental/chat_widget/public/window.svg diff --git a/apps/chat_widget/tailwind.config.ts b/apps/experimental/chat_widget/tailwind.config.ts similarity index 100% rename from apps/chat_widget/tailwind.config.ts rename to apps/experimental/chat_widget/tailwind.config.ts diff --git a/apps/chat_widget/tsconfig.json b/apps/experimental/chat_widget/tsconfig.json similarity index 100% rename from apps/chat_widget/tsconfig.json rename to apps/experimental/chat_widget/tsconfig.json diff --git a/apps/simulation_runner/Dockerfile b/apps/experimental/simulation_runner/Dockerfile similarity index 100% rename from apps/simulation_runner/Dockerfile rename to apps/experimental/simulation_runner/Dockerfile diff --git a/apps/simulation_runner/__init__.py b/apps/experimental/simulation_runner/__init__.py similarity index 100% rename from apps/simulation_runner/__init__.py rename to apps/experimental/simulation_runner/__init__.py diff --git a/apps/simulation_runner/db.py b/apps/experimental/simulation_runner/db.py similarity index 100% rename from apps/simulation_runner/db.py rename to apps/experimental/simulation_runner/db.py diff --git a/apps/simulation_runner/requirements.txt b/apps/experimental/simulation_runner/requirements.txt similarity index 100% rename from apps/simulation_runner/requirements.txt rename to apps/experimental/simulation_runner/requirements.txt diff --git a/apps/simulation_runner/scenario_types.py b/apps/experimental/simulation_runner/scenario_types.py similarity index 100% rename from apps/simulation_runner/scenario_types.py rename to apps/experimental/simulation_runner/scenario_types.py diff --git a/apps/simulation_runner/service.py b/apps/experimental/simulation_runner/service.py similarity index 100% rename from apps/simulation_runner/service.py rename to apps/experimental/simulation_runner/service.py diff --git a/apps/simulation_runner/simulation.py b/apps/experimental/simulation_runner/simulation.py similarity index 100% rename from apps/simulation_runner/simulation.py rename to apps/experimental/simulation_runner/simulation.py diff --git a/apps/tools_webhook/Dockerfile b/apps/experimental/tools_webhook/Dockerfile similarity index 100% rename from apps/tools_webhook/Dockerfile rename to apps/experimental/tools_webhook/Dockerfile diff --git a/apps/tools_webhook/__init__.py b/apps/experimental/tools_webhook/__init__.py similarity index 100% rename from apps/tools_webhook/__init__.py rename to apps/experimental/tools_webhook/__init__.py diff --git a/apps/tools_webhook/app.py b/apps/experimental/tools_webhook/app.py similarity index 100% rename from apps/tools_webhook/app.py rename to apps/experimental/tools_webhook/app.py diff --git a/apps/tools_webhook/function_map.py b/apps/experimental/tools_webhook/function_map.py similarity index 100% rename from apps/tools_webhook/function_map.py rename to apps/experimental/tools_webhook/function_map.py diff --git a/apps/tools_webhook/requirements.txt b/apps/experimental/tools_webhook/requirements.txt similarity index 100% rename from apps/tools_webhook/requirements.txt rename to apps/experimental/tools_webhook/requirements.txt diff --git a/apps/tools_webhook/tests/__init__.py b/apps/experimental/tools_webhook/tests/__init__.py similarity index 100% rename from apps/tools_webhook/tests/__init__.py rename to apps/experimental/tools_webhook/tests/__init__.py diff --git a/apps/tools_webhook/tests/test_app.py b/apps/experimental/tools_webhook/tests/test_app.py similarity index 100% rename from apps/tools_webhook/tests/test_app.py rename to apps/experimental/tools_webhook/tests/test_app.py diff --git a/apps/tools_webhook/tests/test_tool_caller.py b/apps/experimental/tools_webhook/tests/test_tool_caller.py similarity index 100% rename from apps/tools_webhook/tests/test_tool_caller.py rename to apps/experimental/tools_webhook/tests/test_tool_caller.py diff --git a/apps/tools_webhook/tool_caller.py b/apps/experimental/tools_webhook/tool_caller.py similarity index 100% rename from apps/tools_webhook/tool_caller.py rename to apps/experimental/tools_webhook/tool_caller.py diff --git a/apps/twilio_handler/.dockerignore b/apps/experimental/twilio_handler/.dockerignore similarity index 100% rename from apps/twilio_handler/.dockerignore rename to apps/experimental/twilio_handler/.dockerignore diff --git a/apps/twilio_handler/.env.example b/apps/experimental/twilio_handler/.env.example similarity index 100% rename from apps/twilio_handler/.env.example rename to apps/experimental/twilio_handler/.env.example diff --git a/apps/twilio_handler/.gitignore b/apps/experimental/twilio_handler/.gitignore similarity index 100% rename from apps/twilio_handler/.gitignore rename to apps/experimental/twilio_handler/.gitignore diff --git a/apps/twilio_handler/Dockerfile b/apps/experimental/twilio_handler/Dockerfile similarity index 100% rename from apps/twilio_handler/Dockerfile rename to apps/experimental/twilio_handler/Dockerfile diff --git a/apps/twilio_handler/app.py b/apps/experimental/twilio_handler/app.py similarity index 100% rename from apps/twilio_handler/app.py rename to apps/experimental/twilio_handler/app.py diff --git a/apps/twilio_handler/load_env.py b/apps/experimental/twilio_handler/load_env.py similarity index 100% rename from apps/twilio_handler/load_env.py rename to apps/experimental/twilio_handler/load_env.py diff --git a/apps/twilio_handler/requirements.txt b/apps/experimental/twilio_handler/requirements.txt similarity index 100% rename from apps/twilio_handler/requirements.txt rename to apps/experimental/twilio_handler/requirements.txt diff --git a/apps/twilio_handler/twilio_api.py b/apps/experimental/twilio_handler/twilio_api.py similarity index 100% rename from apps/twilio_handler/twilio_api.py rename to apps/experimental/twilio_handler/twilio_api.py diff --git a/apps/twilio_handler/util.py b/apps/experimental/twilio_handler/util.py similarity index 100% rename from apps/twilio_handler/util.py rename to apps/experimental/twilio_handler/util.py diff --git a/apps/python-sdk/README.md b/apps/python-sdk/README.md index e4442035..91a0b461 100644 --- a/apps/python-sdk/README.md +++ b/apps/python-sdk/README.md @@ -12,13 +12,68 @@ pip install rowboat ## 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 -from rowboat import Client -from rowboat.schema import UserMessage, SystemMessage +from rowboat import Client, StatefulChat + +# Initialize the client +client = Client( + host="", + project_id="", + 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="" +) +``` + +#### Using a test profile + +You can specify a test profile ID to use a specific test configuration: + +```python +chat = StatefulChat( + client, + 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 client = Client( @@ -29,108 +84,15 @@ client = Client( # Create messages messages = [ - SystemMessage(role='system', content="You are a helpful assistant"), UserMessage(role='user', content="Hello, how are you?") ] # Get response -response_messages, state = client.chat(messages=messages) -print(response_messages[-1].content) +response = client.chat(messages=messages) +print(response.messages[-1].content) -# For subsequent messages, include previous messages and state -messages.extend(response_messages) +# For subsequent messages, you need to manage the message history and state manually +messages.extend(response.messages) messages.append(UserMessage(role='user', content="What's your name?")) -response_messages, state = client.chat(messages=messages, state=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="" -) - -# or - -chat = StatefulChat( - client, - 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="" -) - -# or - -chat = StatefulChat( - client, - 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 -) +response = client.chat(messages=messages, state=response.state) ``` diff --git a/apps/python-sdk/pyproject.toml b/apps/python-sdk/pyproject.toml index 6d1cabdc..b107d8b6 100644 --- a/apps/python-sdk/pyproject.toml +++ b/apps/python-sdk/pyproject.toml @@ -4,9 +4,9 @@ build-backend = "hatchling.build" [project] name = "rowboat" -version = "2.1.0" +version = "3.0.0" authors = [ - { name = "Your Name", email = "your.email@example.com" }, + { name = "Ramnique Singh", email = "ramnique@rowboatlabs.com" }, ] description = "Python sdk for the Rowboat API" readme = "README.md" diff --git a/apps/python-sdk/src/rowboat/client.py b/apps/python-sdk/src/rowboat/client.py index 2997ed08..d270c0c5 100644 --- a/apps/python-sdk/src/rowboat/client.py +++ b/apps/python-sdk/src/rowboat/client.py @@ -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 json from .schema import ( ApiRequest, ApiResponse, ApiMessage, - ToolMessage, UserMessage, - SystemMessage, AssistantMessage, AssistantMessageWithToolCalls ) - class Client: def __init__(self, host: str, project_id: str, api_key: str) -> None: self.base_url: str = f'{host}/api/v1/{project_id}/chat' @@ -25,16 +21,12 @@ class Client: self, messages: List[ApiMessage], state: Optional[Dict[str, Any]] = None, - skip_tool_calls: bool = False, - max_turns: int = 3, workflow_id: Optional[str] = None, test_profile_id: Optional[str] = None ) -> ApiResponse: request = ApiRequest( messages=messages, state=state, - skipToolCalls=skip_tool_calls, - maxTurns=max_turns, workflowId=workflow_id, testProfileId=test_profile_id ) @@ -55,86 +47,27 @@ class Client: 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( self, messages: List[ApiMessage], - tools: Optional[Dict[str, Callable[..., str]]] = None, state: Optional[Dict[str, Any]] = None, - max_turns: int = 3, - skip_tool_calls: bool = False, workflow_id: Optional[str] = None, test_profile_id: Optional[str] = None - ) -> Tuple[List[ApiMessage], Optional[Dict[str, Any]]]: - """Stateless chat method that handles a single conversation turn with multiple tool call rounds""" + ) -> ApiResponse: + """Stateless chat method that handles a single conversation turn""" - current_messages = messages[:] - current_state = state - turns = 0 + # call api + response_data = self._call_api( + messages=messages, + state=state, + workflow_id=workflow_id, + test_profile_id=test_profile_id + ) - response_messages = [] - response_state = None - has_tool_calls = False - - while turns < max_turns: - # call api - response_data = self._call_api( - messages=current_messages, - state=current_state, - skip_tool_calls=skip_tool_calls, - max_turns=max_turns, - workflow_id=workflow_id, - test_profile_id=test_profile_id - ) - - current_messages.extend(response_data.messages) - current_state = response_data.state - response_messages = response_data.messages - response_state = response_data.state - - # Process tool calls if present and tools are provided - last_message = response_data.messages[-1] - has_tool_calls = hasattr(last_message, 'tool_calls') and last_message.tool_calls - if has_tool_calls: - tool_messages = self._process_tool_calls(last_message.tool_calls, tools) - current_messages.extend(tool_messages) - - # If no tool calls were made, we're done - if not has_tool_calls: - break - - turns += 1 - - if turns == max_turns and has_tool_calls: - raise ValueError("Max turns reached") - - if not last_message.agenticResponseType == 'external': + if not response_data.messages[-1].agenticResponseType == 'external': raise ValueError("Last message was not an external message") - return response_messages, response_state + return response_data class StatefulChat: """Maintains conversation state across multiple turns""" @@ -142,23 +75,14 @@ class StatefulChat: def __init__( self, 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, test_profile_id: Optional[str] = None ) -> None: self.client = client - self.tools = tools self.messages: List[ApiMessage] = [] 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.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: """Handle a single user turn in the conversation""" @@ -168,22 +92,19 @@ class StatefulChat: self.messages.append(user_msg) # Get response using the client's chat method - new_messages, new_state = self.client.chat( + response_data = self.client.chat( messages=self.messages, - tools=self.tools, state=self.state, - max_turns=self.max_turns, - skip_tool_calls=self.skip_tool_calls, workflow_id=self.workflow_id, test_profile_id=self.test_profile_id ) # Update internal state - self.messages = new_messages - self.state = new_state + self.messages = response_data.messages + self.state = response_data.state # Return only the final message content - last_message = new_messages[-1] + last_message = self.messages[-1] return last_message.content @@ -197,9 +118,13 @@ if __name__ == "__main__": api_key: str = "" client = Client(host, project_id, api_key) - tools: Dict[str, Callable[..., str]] = { - 'weather_lookup': weather_lookup_tool - } - chat_session = StatefulChat(client, tools) - resp = chat_session.run("whats the weather in london?") + result = client.chat( + messages=[ + UserMessage(role='user', content="Hello") + ] + ) + print(result.messages[-1].content) + + chat_session = StatefulChat(client) + resp = chat_session.run("Hello") print(resp) \ No newline at end of file diff --git a/apps/python-sdk/src/rowboat/schema.py b/apps/python-sdk/src/rowboat/schema.py index c95d4554..0ee959dc 100644 --- a/apps/python-sdk/src/rowboat/schema.py +++ b/apps/python-sdk/src/rowboat/schema.py @@ -48,8 +48,6 @@ ApiMessage = Union[ class ApiRequest(BaseModel): messages: List[ApiMessage] state: Any - skipToolCalls: Optional[bool] = None - maxTurns: Optional[int] = None workflowId: Optional[str] = None testProfileId: Optional[str] = None diff --git a/apps/rowboat/app/api/v1/stream-response/[streamId]/route.ts b/apps/rowboat/app/api/v1/stream-response/[streamId]/route.ts index 563db7bb..06b0fc90 100644 --- a/apps/rowboat/app/api/v1/stream-response/[streamId]/route.ts +++ b/apps/rowboat/app/api/v1/stream-response/[streamId]/route.ts @@ -1,11 +1,18 @@ +import { redisClient } from "@/app/lib/redis"; + export async function GET(request: Request, { params }: { params: { streamId: string } }) { - // Replace with your actual upstream SSE endpoint. - const upstreamUrl = `${process.env.AGENTS_API_URL}/chat_stream/${params.streamId}`; - console.log('upstreamUrl', upstreamUrl); - + // get the payload from redis + const payload = await redisClient.get(`chat-stream-${params.streamId}`); + if (!payload) { + return new Response("Stream not found", { status: 404 }); + } + // 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: { + 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`, }, cache: 'no-store', diff --git a/apps/rowboat/app/lib/feature_flags.ts b/apps/rowboat/app/lib/feature_flags.ts index 37c83deb..caf95116 100644 --- a/apps/rowboat/app/lib/feature_flags.ts +++ b/apps/rowboat/app/lib/feature_flags.ts @@ -2,4 +2,9 @@ export const USE_RAG = process.env.USE_RAG === 'true'; 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_CHAT_WIDGET = process.env.USE_CHAT_WIDGET === 'true'; -export const USE_AUTH = process.env.USE_AUTH === 'true'; \ No newline at end of file +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; \ No newline at end of file diff --git a/apps/rowboat/app/lib/types/types.ts b/apps/rowboat/app/lib/types/types.ts index 911d857c..c4544d6b 100644 --- a/apps/rowboat/app/lib/types/types.ts +++ b/apps/rowboat/app/lib/types/types.ts @@ -111,8 +111,6 @@ export const ApiMessage = z.union([ export const ApiRequest = z.object({ messages: z.array(ApiMessage), state: z.unknown(), - skipToolCalls: z.boolean().nullable().optional(), - maxTurns: z.number().nullable().optional(), workflowId: z.string().nullable().optional(), testProfileId: z.string().nullable().optional(), }); diff --git a/apps/rowboat/app/lib/utils.ts b/apps/rowboat/app/lib/utils.ts index 2f44d4c0..b602a1a9 100644 --- a/apps/rowboat/app/lib/utils.ts +++ b/apps/rowboat/app/lib/utils.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { generateObject } from "ai"; import { ApiMessage } from "./types/types"; import { openai } from "@ai-sdk/openai"; +import { redisClient } from "./redis"; export async function getAgenticApiResponse( request: z.infer, @@ -38,24 +39,20 @@ export async function getAgenticApiResponse( export async function getAgenticResponseStreamId( request: z.infer, ): Promise> { - // call agentic api - console.log(`sending agentic api init stream request`, JSON.stringify(request)); - const response = await fetch(process.env.AGENTS_API_URL + '/chat_stream_init', { - method: 'POST', - body: JSON.stringify(request), - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`, - }, + // serialize the request + const payload = JSON.stringify(request); + + // create a uuid for the stream + const streamId = crypto.randomUUID(); + + // store payload in redis + 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); - throw new Error(`Failed to call agentic init stream api: ${response.statusText}`); - } - const responseJson = await response.json(); - console.log(`received agentic api init stream response`, JSON.stringify(responseJson)); - const result: z.infer = responseJson; - return result; + + return { + streamId, + }; } // create a PrefixLogger class that wraps console.log with a prefix diff --git a/apps/rowboat/app/projects/[projectId]/config/app.tsx b/apps/rowboat/app/projects/[projectId]/config/app.tsx index e78d8855..bfff5392 100644 --- a/apps/rowboat/app/projects/[projectId]/config/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/config/app.tsx @@ -27,6 +27,7 @@ import { ToolsSection } from './components/tools'; import { Panel } from "@/components/common/panel-common"; import { Settings, Wrench, Phone } from "lucide-react"; import { clsx } from "clsx"; +import { USE_VOICE_FEATURE } from "@/app/lib/feature_flags"; export const metadata: Metadata = { title: "Project config", @@ -799,7 +800,7 @@ function NavigationMenu({ const items = [ { id: 'Project', icon: }, { id: 'Tools', icon: }, - { id: 'Voice', icon: } + ...(USE_VOICE_FEATURE ? [{ id: 'Voice', icon: }] : []) ]; return ( diff --git a/apps/rowboat/app/projects/[projectId]/playground/app.tsx b/apps/rowboat/app/projects/[projectId]/playground/app.tsx index 3eb62053..864036fc 100644 --- a/apps/rowboat/app/projects/[projectId]/playground/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/playground/app.tsx @@ -11,6 +11,7 @@ import { TestProfile } from "@/app/lib/types/testing_types"; import { WithStringId } from "@/app/lib/types/types"; import { ProfileSelector } from "@/app/projects/[projectId]/test/[[...slug]]/components/selectors/profile-selector"; import { CheckIcon, CopyIcon, PlusIcon, UserIcon } from "lucide-react"; +import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags"; const defaultSystemMessage = ''; @@ -103,15 +104,17 @@ export function App({ } rightActions={
- + {USE_TESTING_FEATURE && ( + + )}