mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
Update docs and simplify sdk
This commit is contained in:
parent
c338ec4dec
commit
97feb71869
10 changed files with 265 additions and 446 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 41 KiB |
|
|
@ -28,8 +28,8 @@ RowBoat Studio lets you create AI agents in minutes, using a visual interface an
|
|||
| 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|
|
||||
|
||||
### 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:**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||

|
||||
|
||||
## 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)
|
||||
- `state`: generated from the previous turn (this is needed because the API does not maintain state on its own)
|
||||
- For self-hosted: `<HOST>` 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 `<HOST>` is [http://localhost:3000](http://localhost:3000)
|
||||
- When using the hosted app, the `<HOST>` is [https://app.rowboatlabs.com](https://app.rowboatlabs.com)
|
||||
|
||||
### Example first turn of a chat
|
||||
```
|
||||
Authorization: Bearer <API_KEY>
|
||||
```
|
||||
|
||||
#### Request
|
||||
## Examples
|
||||
|
||||
### First Turn
|
||||
|
||||
```bash
|
||||
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>' \
|
||||
--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/<PROJECT_ID>/chat' \
|
||||
curl --location '<HOST>/api/v1/<PROJECT_ID>/chat' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer <API_KEY>' \
|
||||
--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
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### 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="<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
|
||||
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)
|
||||
```
|
||||
|
|
@ -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="<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
|
||||
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="<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
|
||||
)
|
||||
response = client.chat(messages=messages, state=response.state)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = "<API_KEY>"
|
||||
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)
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue