mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(deployed-chat): added file upload to workflow execute API, added to deployed chat, updated chat panel (#1588)
* feat(deployed-chat): updated chat panel UI, deployed chat and API can now accept files * added nested tag dropdown for files * added duplicate file validation to chat panel * update docs & SDKs * fixed build * rm extraneous comments * ack PR comments, cut multiple DB roundtrips for permissions & api key checks in api/workflows * allow read-only users to access deployment info, but not take actions * add downloadable file to logs for files passed in via API * protect files/serve route that is only used client-side --------- Co-authored-by: waleed <waleed>
This commit is contained in:
committed by
waleed
co-authored by
waleed
parent
8ce5a1b7c0
commit
2d49892aaa
@@ -57,7 +57,7 @@ result = client.execute_workflow(
|
||||
|
||||
**Parameters:**
|
||||
- `workflow_id` (str): The ID of the workflow to execute
|
||||
- `input_data` (dict, optional): Input data to pass to the workflow
|
||||
- `input_data` (dict, optional): Input data to pass to the workflow. File objects are automatically converted to base64.
|
||||
- `timeout` (float): Timeout in seconds (default: 30.0)
|
||||
|
||||
**Returns:** `WorkflowExecutionResult`
|
||||
@@ -265,6 +265,57 @@ client = SimStudioClient(
|
||||
)
|
||||
```
|
||||
|
||||
### File Upload
|
||||
|
||||
File objects are automatically detected and converted to base64 format. Include them in your input under the field name matching your workflow's API trigger input format:
|
||||
|
||||
The SDK converts file objects to this format:
|
||||
```python
|
||||
{
|
||||
'type': 'file',
|
||||
'data': 'data:mime/type;base64,base64data',
|
||||
'name': 'filename',
|
||||
'mime': 'mime/type'
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can manually provide files using the URL format:
|
||||
```python
|
||||
{
|
||||
'type': 'url',
|
||||
'data': 'https://example.com/file.pdf',
|
||||
'name': 'file.pdf',
|
||||
'mime': 'application/pdf'
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
# Upload a single file - include it under the field name from your API trigger
|
||||
with open('document.pdf', 'rb') as f:
|
||||
result = client.execute_workflow(
|
||||
'workflow-id',
|
||||
input_data={
|
||||
'documents': [f], # Must match your workflow's "files" field name
|
||||
'instructions': 'Analyze this document'
|
||||
}
|
||||
)
|
||||
|
||||
# Upload multiple files
|
||||
with open('doc1.pdf', 'rb') as f1, open('doc2.pdf', 'rb') as f2:
|
||||
result = client.execute_workflow(
|
||||
'workflow-id',
|
||||
input_data={
|
||||
'attachments': [f1, f2], # Must match your workflow's "files" field name
|
||||
'query': 'Compare these documents'
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Batch Workflow Execution
|
||||
|
||||
```python
|
||||
@@ -276,14 +327,14 @@ client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
results = []
|
||||
|
||||
|
||||
for workflow_id, input_data in workflow_data_pairs:
|
||||
try:
|
||||
# Validate workflow before execution
|
||||
if not client.validate_workflow(workflow_id):
|
||||
print(f"Skipping {workflow_id}: not deployed")
|
||||
continue
|
||||
|
||||
|
||||
result = client.execute_workflow(workflow_id, input_data)
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
@@ -291,14 +342,14 @@ def execute_workflows_batch(workflow_data_pairs):
|
||||
"output": result.output,
|
||||
"error": result.error
|
||||
})
|
||||
|
||||
|
||||
except Exception as error:
|
||||
results.append({
|
||||
"workflow_id": workflow_id,
|
||||
"success": False,
|
||||
"error": str(error)
|
||||
})
|
||||
|
||||
|
||||
return results
|
||||
|
||||
# Example usage
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Example: Upload files with workflow execution
|
||||
|
||||
This example demonstrates how to upload files when executing a workflow.
|
||||
Files are automatically detected and converted to base64 format.
|
||||
"""
|
||||
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
# Initialize the client
|
||||
api_key = os.getenv('SIM_API_KEY')
|
||||
if not api_key:
|
||||
raise ValueError('SIM_API_KEY environment variable is required')
|
||||
|
||||
client = SimStudioClient(api_key=api_key)
|
||||
|
||||
# Example 1: Upload a single file
|
||||
# Include file under the field name from your workflow's API trigger input format
|
||||
print("Example 1: Upload a single file")
|
||||
with open('document.pdf', 'rb') as f:
|
||||
result = client.execute_workflow(
|
||||
workflow_id='your-workflow-id',
|
||||
input_data={
|
||||
'documents': [f], # Field name must match your API trigger's file input field
|
||||
'instructions': 'Analyze this document'
|
||||
}
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print(f"Success! Output: {result.output}")
|
||||
else:
|
||||
print(f"Failed: {result.error}")
|
||||
|
||||
# Example 2: Upload multiple files
|
||||
print("\nExample 2: Upload multiple files")
|
||||
with open('document1.pdf', 'rb') as f1, open('document2.pdf', 'rb') as f2:
|
||||
result = client.execute_workflow(
|
||||
workflow_id='your-workflow-id',
|
||||
input_data={
|
||||
'attachments': [f1, f2], # Field name must match your API trigger's file input field
|
||||
'query': 'Compare these documents'
|
||||
}
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print(f"Success! Output: {result.output}")
|
||||
else:
|
||||
print(f"Failed: {result.error}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -8,6 +8,7 @@ from typing import Any, Dict, Optional, Union
|
||||
from dataclasses import dataclass
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
@@ -109,6 +110,53 @@ class SimStudioClient:
|
||||
})
|
||||
self._rate_limit_info: Optional[RateLimitInfo] = None
|
||||
|
||||
def _convert_files_to_base64(self, value: Any) -> Any:
|
||||
"""
|
||||
Convert file objects in input to API format (base64).
|
||||
Recursively processes nested dicts and lists.
|
||||
"""
|
||||
import base64
|
||||
import io
|
||||
|
||||
# Check if this is a file-like object
|
||||
if hasattr(value, 'read') and callable(value.read):
|
||||
# Save current position if seekable
|
||||
initial_pos = value.tell() if hasattr(value, 'tell') else None
|
||||
|
||||
# Read file bytes
|
||||
file_bytes = value.read()
|
||||
|
||||
# Restore position if seekable
|
||||
if initial_pos is not None and hasattr(value, 'seek'):
|
||||
value.seek(initial_pos)
|
||||
|
||||
# Encode to base64
|
||||
base64_data = base64.b64encode(file_bytes).decode('utf-8')
|
||||
|
||||
# Get file metadata
|
||||
filename = getattr(value, 'name', 'file')
|
||||
if isinstance(filename, str):
|
||||
filename = os.path.basename(filename)
|
||||
|
||||
content_type = getattr(value, 'content_type', 'application/octet-stream')
|
||||
|
||||
return {
|
||||
'type': 'file',
|
||||
'data': f'data:{content_type};base64,{base64_data}',
|
||||
'name': filename,
|
||||
'mime': content_type
|
||||
}
|
||||
|
||||
# Recursively process lists
|
||||
if isinstance(value, list):
|
||||
return [self._convert_files_to_base64(item) for item in value]
|
||||
|
||||
# Recursively process dicts
|
||||
if isinstance(value, dict):
|
||||
return {k: self._convert_files_to_base64(v) for k, v in value.items()}
|
||||
|
||||
return value
|
||||
|
||||
def execute_workflow(
|
||||
self,
|
||||
workflow_id: str,
|
||||
@@ -122,9 +170,11 @@ class SimStudioClient:
|
||||
Execute a workflow with optional input data.
|
||||
If async_execution is True, returns immediately with a task ID.
|
||||
|
||||
File objects in input_data will be automatically detected and converted to base64.
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow to execute
|
||||
input_data: Input data to pass to the workflow
|
||||
input_data: Input data to pass to the workflow (can include file-like objects)
|
||||
timeout: Timeout in seconds (default: 30.0)
|
||||
stream: Enable streaming responses (default: None)
|
||||
selected_outputs: Block outputs to stream (e.g., ["agent1.content"])
|
||||
@@ -138,19 +188,23 @@ class SimStudioClient:
|
||||
"""
|
||||
url = f"{self.base_url}/api/workflows/{workflow_id}/execute"
|
||||
|
||||
# Build request body - spread input at root level, then add API control parameters
|
||||
body = input_data.copy() if input_data is not None else {}
|
||||
if stream is not None:
|
||||
body['stream'] = stream
|
||||
if selected_outputs is not None:
|
||||
body['selectedOutputs'] = selected_outputs
|
||||
|
||||
# Build headers - async execution uses X-Execution-Mode header
|
||||
headers = self._session.headers.copy()
|
||||
if async_execution:
|
||||
headers['X-Execution-Mode'] = 'async'
|
||||
|
||||
try:
|
||||
# Build JSON body - spread input at root level, then add API control parameters
|
||||
body = input_data.copy() if input_data is not None else {}
|
||||
|
||||
# Convert any file objects in the input to base64 format
|
||||
body = self._convert_files_to_base64(body)
|
||||
|
||||
if stream is not None:
|
||||
body['stream'] = stream
|
||||
if selected_outputs is not None:
|
||||
body['selectedOutputs'] = selected_outputs
|
||||
|
||||
response = self._session.post(
|
||||
url,
|
||||
json=body,
|
||||
@@ -281,7 +335,7 @@ class SimStudioClient:
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow to execute
|
||||
input_data: Input data to pass to the workflow
|
||||
input_data: Input data to pass to the workflow (can include file-like objects)
|
||||
timeout: Timeout for the initial request in seconds
|
||||
stream: Enable streaming responses (default: None)
|
||||
selected_outputs: Block outputs to stream (e.g., ["agent1.content"])
|
||||
@@ -373,7 +427,7 @@ class SimStudioClient:
|
||||
|
||||
Args:
|
||||
workflow_id: The ID of the workflow to execute
|
||||
input_data: Input data to pass to the workflow
|
||||
input_data: Input data to pass to the workflow (can include file-like objects)
|
||||
timeout: Timeout in seconds
|
||||
stream: Enable streaming responses
|
||||
selected_outputs: Block outputs to stream
|
||||
|
||||
Reference in New Issue
Block a user