mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f82ed381a | ||
|
|
3dd36a8a35 | ||
|
|
09cccd5487 | ||
|
|
1773530325 | ||
|
|
2da7a6755c | ||
|
|
1e81cd6850 | ||
|
|
ec73e2e9ce | ||
|
|
4937d72d70 | ||
|
|
219a065a7c | ||
|
|
8f06aec68b | ||
|
|
1de6f09069 | ||
|
|
b10b2461a5 | ||
|
|
34fc8f84f5 | ||
|
|
ee77dea2d6 | ||
|
|
bba407b507 | ||
|
|
ab63978ce8 | ||
|
|
e697e50d4e | ||
|
|
41ec229431 | ||
|
|
c0f5ba75f1 | ||
|
|
5a943bca32 | ||
|
|
923595f57e | ||
|
|
241d9fd12d | ||
|
|
97a8778449 | ||
|
|
833e700b58 | ||
|
|
2d49892aaa | ||
|
|
8ce5a1b7c0 | ||
|
|
88d2e7b97b | ||
|
|
c04eb01aed | ||
|
|
5d887fdca7 | ||
|
|
1a0fdb32fe | ||
|
|
9d45b8df1e | ||
|
|
ae3a7f0865 | ||
|
|
25f5e31378 | ||
|
|
7bdf0e94d7 | ||
|
|
8e43774b5e | ||
|
|
715f42c1a6 | ||
|
|
8200e9a88f | ||
|
|
c6f6c9e2a5 | ||
|
|
2d7ba91c0e | ||
|
|
872e034312 | ||
|
|
a63a7b0262 | ||
|
|
991a020917 | ||
|
|
f03f395225 | ||
|
|
174f6a48a6 | ||
|
|
c2f0a95802 | ||
|
|
4dc4073452 | ||
|
|
d9b70087c4 | ||
|
|
07fd9c3a4a | ||
|
|
377b84e18c | ||
|
|
223ecda80e | ||
|
|
7dde01e74b | ||
|
|
b768ca845e | ||
|
|
86ed32ea10 | ||
|
|
0e838940f1 | ||
|
|
7cc9a23f99 | ||
|
|
c42d2a32f3 | ||
|
|
4da355d269 | ||
|
|
2175fd1106 | ||
|
|
10692b5e5a | ||
|
|
62298bf094 | ||
|
|
5f1518ffd9 | ||
|
|
cae0e85826 | ||
|
|
fa9c97816b | ||
|
|
4bc37db547 | ||
|
|
15138629cb | ||
|
|
ace83ebcae | ||
|
|
b33ae5bff9 | ||
|
|
dc6052578d | ||
|
|
4adbae03e7 | ||
|
|
3509ce8ce4 | ||
|
|
7aae108b87 | ||
|
|
980a6d8347 |
@@ -1,69 +0,0 @@
|
||||
# Sim Development Environment Bashrc
|
||||
# This gets sourced by post-create.sh
|
||||
|
||||
# Enhanced prompt with git branch info
|
||||
parse_git_branch() {
|
||||
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
|
||||
}
|
||||
|
||||
export PS1="\[\033[01;32m\]\u@simstudio\[\033[00m\]:\[\033[01;34m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\]\$ "
|
||||
|
||||
# Helpful aliases
|
||||
alias ll="ls -la"
|
||||
alias ..="cd .."
|
||||
alias ...="cd ../.."
|
||||
|
||||
# Database aliases
|
||||
alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio"
|
||||
alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'"
|
||||
|
||||
# Sim specific aliases
|
||||
alias logs="cd /workspace/apps/sim && tail -f logs/*.log 2>/dev/null || echo 'No log files found'"
|
||||
alias sim-start="cd /workspace && bun run dev"
|
||||
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
|
||||
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||
alias sim-rebuild="cd /workspace && bun run build && bun run start"
|
||||
alias docs-dev="cd /workspace/apps/docs && bun run dev"
|
||||
|
||||
# Turbo related commands
|
||||
alias turbo-build="cd /workspace && bunx turbo run build"
|
||||
alias turbo-dev="cd /workspace && bunx turbo run dev"
|
||||
alias turbo-test="cd /workspace && bunx turbo run test"
|
||||
|
||||
# Bun specific commands
|
||||
alias bun-update="cd /workspace && bun update"
|
||||
alias bun-add="cd /workspace && bun add"
|
||||
alias bun-pm="cd /workspace && bun pm"
|
||||
alias bun-canary="bun upgrade --canary"
|
||||
|
||||
# Default to workspace directory
|
||||
cd /workspace 2>/dev/null || true
|
||||
|
||||
# Welcome message - only show once per session
|
||||
if [ -z "$SIM_WELCOME_SHOWN" ]; then
|
||||
export SIM_WELCOME_SHOWN=1
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 Welcome to Sim development environment!"
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo " sim-start - Start all apps in development mode"
|
||||
echo " sim-migrate - Push schema changes to the database for sim app"
|
||||
echo " sim-generate - Generate new migrations for sim app"
|
||||
echo " sim-rebuild - Build and start all apps"
|
||||
echo " docs-dev - Start only the docs app in development mode"
|
||||
echo ""
|
||||
echo "Turbo commands:"
|
||||
echo " turbo-build - Build all apps using Turborepo"
|
||||
echo " turbo-dev - Start development mode for all apps"
|
||||
echo " turbo-test - Run tests for all packages"
|
||||
echo ""
|
||||
echo "Bun commands:"
|
||||
echo " bun-update - Update dependencies"
|
||||
echo " bun-add - Add a new dependency"
|
||||
echo " bun-pm - Manage dependencies"
|
||||
echo " bun-canary - Upgrade to the latest canary version of Bun"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
fi
|
||||
@@ -1,38 +1,43 @@
|
||||
# Use the latest Bun canary image for development
|
||||
FROM oven/bun:canary
|
||||
|
||||
# Avoid warnings by switching to noninteractive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
FROM oven/bun:1.2.22-alpine
|
||||
|
||||
# Install necessary packages for development
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install --no-install-recommends \
|
||||
git curl wget jq sudo postgresql-client vim nano \
|
||||
bash-completion ca-certificates lsb-release gnupg \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
jq \
|
||||
sudo \
|
||||
postgresql-client \
|
||||
vim \
|
||||
nano \
|
||||
bash \
|
||||
bash-completion \
|
||||
zsh \
|
||||
zsh-vcs \
|
||||
ca-certificates \
|
||||
shadow
|
||||
|
||||
# Create a non-root user
|
||||
# Create a non-root user with matching UID/GID
|
||||
ARG USERNAME=bun
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
# Create user group if it doesn't exist
|
||||
RUN if ! getent group $USER_GID >/dev/null; then \
|
||||
addgroup -g $USER_GID $USERNAME; \
|
||||
fi
|
||||
|
||||
# Create user if it doesn't exist
|
||||
RUN if ! getent passwd $USER_UID >/dev/null; then \
|
||||
adduser -D -u $USER_UID -G $(getent group $USER_GID | cut -d: -f1) $USERNAME; \
|
||||
fi
|
||||
|
||||
# Add sudo support
|
||||
RUN echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME \
|
||||
&& chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
|
||||
# Install global packages for development
|
||||
RUN bun install -g turbo drizzle-kit typescript @types/node
|
||||
|
||||
# Install bun completions
|
||||
RUN bun completions > /etc/bash_completion.d/bun
|
||||
|
||||
# Set up shell environment
|
||||
RUN echo "export PATH=$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile
|
||||
RUN echo "source /etc/profile" >> /etc/bash.bashrc
|
||||
|
||||
# Switch back to dialog for any ad-hoc use of apt-get
|
||||
ENV DEBIAN_FRONTEND=dialog
|
||||
RUN echo "export PATH=\$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
|
||||
@@ -1,78 +1,75 @@
|
||||
# Sim Development Container
|
||||
|
||||
This directory contains configuration files for Visual Studio Code Dev Containers / GitHub Codespaces. Dev containers provide a consistent, isolated development environment for this project.
|
||||
Development container configuration for VS Code Dev Containers and GitHub Codespaces.
|
||||
|
||||
## Contents
|
||||
|
||||
- `devcontainer.json` - The main configuration file that defines the development container settings
|
||||
- `Dockerfile` - Defines the container image and development environment
|
||||
- `docker-compose.yml` - Sets up the application and database containers
|
||||
- `post-create.sh` - Script that runs when the container is created
|
||||
- `.bashrc` - Custom shell configuration with helpful aliases
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
## Prerequisites
|
||||
|
||||
- Visual Studio Code
|
||||
- Docker installation:
|
||||
- Docker Desktop (Windows/macOS)
|
||||
- Docker Engine (Linux)
|
||||
- VS Code Remote - Containers extension
|
||||
- Docker Desktop or Podman Desktop
|
||||
- VS Code Dev Containers extension
|
||||
|
||||
### Getting Started
|
||||
## Getting Started
|
||||
|
||||
1. Open this project in Visual Studio Code
|
||||
2. When prompted, click "Reopen in Container"
|
||||
- Alternatively, press `F1` and select "Remote-Containers: Reopen in Container"
|
||||
1. Open this project in VS Code
|
||||
2. Click "Reopen in Container" when prompted (or press `F1` → "Dev Containers: Reopen in Container")
|
||||
3. Wait for the container to build and initialize
|
||||
4. The post-creation script will automatically:
|
||||
4. Start developing with `sim-start`
|
||||
|
||||
- Install dependencies
|
||||
- Set up environment variables
|
||||
- Run database migrations
|
||||
- Configure helpful aliases
|
||||
The setup script will automatically install dependencies and run migrations.
|
||||
|
||||
5. Start the application with `sim-start` (alias for `bun run dev`)
|
||||
## Development Commands
|
||||
|
||||
### Development Commands
|
||||
### Running Services
|
||||
|
||||
The development environment includes these helpful aliases:
|
||||
You have two options for running the development environment:
|
||||
|
||||
**Option 1: Run everything together (recommended for most development)**
|
||||
```bash
|
||||
sim-start # Runs both app and socket server using concurrently
|
||||
```
|
||||
|
||||
**Option 2: Run services separately (useful for debugging individual services)**
|
||||
- In the **app** container terminal: `sim-app` (starts Next.js app on port 3000)
|
||||
- In the **realtime** container terminal: `sim-sockets` (starts socket server on port 3002)
|
||||
|
||||
### Other Commands
|
||||
|
||||
- `sim-start` - Start the development server
|
||||
- `sim-migrate` - Push schema changes to the database
|
||||
- `sim-generate` - Generate new migrations
|
||||
- `sim-rebuild` - Build and start the production version
|
||||
- `pgc` - Connect to the PostgreSQL database
|
||||
- `check-db` - List all databases
|
||||
|
||||
### Using GitHub Codespaces
|
||||
|
||||
This project is also configured for GitHub Codespaces. To use it:
|
||||
|
||||
1. Go to the GitHub repository
|
||||
2. Click the "Code" button
|
||||
3. Select the "Codespaces" tab
|
||||
4. Click "Create codespace on main"
|
||||
|
||||
This will start a new Codespace with the development environment already set up.
|
||||
|
||||
## Customization
|
||||
|
||||
You can customize the development environment by:
|
||||
|
||||
- Modifying `devcontainer.json` to add VS Code extensions or settings
|
||||
- Updating the `Dockerfile` to install additional packages
|
||||
- Editing `docker-compose.yml` to add services or change configuration
|
||||
- Modifying `.bashrc` to add custom aliases or configurations
|
||||
- `build` - Build the application
|
||||
- `pgc` - Connect to PostgreSQL database
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
**Build errors**: Rebuild the container with `F1` → "Dev Containers: Rebuild Container"
|
||||
|
||||
1. Rebuild the container: `F1` → "Remote-Containers: Rebuild Container"
|
||||
2. Check Docker logs for build errors
|
||||
3. Verify Docker Desktop is running
|
||||
4. Ensure all prerequisites are installed
|
||||
**Port conflicts**: Ensure ports 3000, 3002, and 5432 are available
|
||||
|
||||
For more information, see the [VS Code Remote Development documentation](https://code.visualstudio.com/docs/remote/containers).
|
||||
**Container runtime issues**: Verify Docker Desktop or Podman Desktop is running
|
||||
|
||||
## Technical Details
|
||||
|
||||
Services:
|
||||
- **App container** (8GB memory limit) - Main Next.js application
|
||||
- **Realtime container** (4GB memory limit) - Socket.io server for real-time features
|
||||
- **Database** - PostgreSQL with pgvector extension
|
||||
- **Migrations** - Runs automatically on container creation
|
||||
|
||||
You can develop with services running together or independently.
|
||||
|
||||
### Personalization
|
||||
|
||||
**Project commands** (`sim-start`, `sim-app`, etc.) are automatically available via `/workspace/.devcontainer/sim-commands.sh`.
|
||||
|
||||
**Personal shell customization** (aliases, prompts, etc.) should use VS Code's dotfiles feature:
|
||||
1. Create a dotfiles repository (e.g., `github.com/youruser/dotfiles`)
|
||||
2. Add your `.bashrc`, `.zshrc`, or other configs
|
||||
3. Configure in VS Code Settings:
|
||||
```json
|
||||
{
|
||||
"dotfiles.repository": "youruser/dotfiles",
|
||||
"dotfiles.installCommand": "install.sh"
|
||||
}
|
||||
```
|
||||
|
||||
This separates project-specific commands from personal preferences, following VS Code best practices.
|
||||
|
||||
@@ -13,13 +13,6 @@
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"bash": {
|
||||
"path": "/bin/bash",
|
||||
"args": ["--login"]
|
||||
}
|
||||
},
|
||||
"terminal.integrated.shellIntegration.enabled": true
|
||||
},
|
||||
"extensions": [
|
||||
@@ -36,18 +29,9 @@
|
||||
}
|
||||
},
|
||||
|
||||
"forwardPorts": [3000, 5432],
|
||||
"forwardPorts": [3000, 3002, 5432],
|
||||
|
||||
"postCreateCommand": "bash -c 'bash .devcontainer/post-create.sh || true'",
|
||||
|
||||
"postStartCommand": "bash -c 'if [ ! -f ~/.bashrc ] || ! grep -q \"sim-start\" ~/.bashrc; then cp .devcontainer/.bashrc ~/.bashrc; fi'",
|
||||
|
||||
"remoteUser": "bun",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"ghcr.io/prulloac/devcontainer-features/bun:1": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
"remoteUser": "bun"
|
||||
}
|
||||
|
||||
@@ -7,52 +7,56 @@ services:
|
||||
- ..:/workspace:cached
|
||||
- bun-cache:/home/bun/.bun/cache:delegated
|
||||
command: sleep infinity
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 8G
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||
- BETTER_AUTH_URL=http://localhost:3000
|
||||
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-your_encryption_key_here}
|
||||
- COPILOT_API_KEY=${COPILOT_API_KEY}
|
||||
- SIM_AGENT_API_URL=${SIM_AGENT_API_URL}
|
||||
- OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434}
|
||||
- NEXT_PUBLIC_SOCKET_URL=${NEXT_PUBLIC_SOCKET_URL:-http://localhost:3002}
|
||||
- BUN_INSTALL_CACHE_DIR=/home/bun/.bun/cache
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
realtime:
|
||||
condition: service_healthy
|
||||
migrations:
|
||||
condition: service_completed_successfully
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3001:3001"
|
||||
working_dir: /workspace
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000']
|
||||
interval: 90s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
realtime:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
- bun-cache:/home/bun/.bun/cache:delegated
|
||||
command: sleep infinity
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||
- BETTER_AUTH_URL=http://localhost:3000
|
||||
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3002:3002"
|
||||
working_dir: /workspace
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002']
|
||||
interval: 90s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
migrations:
|
||||
build:
|
||||
|
||||
@@ -8,11 +8,43 @@ echo "🔧 Setting up Sim development environment..."
|
||||
# Change to the workspace root directory
|
||||
cd /workspace
|
||||
|
||||
# Setup .bashrc
|
||||
echo "📄 Setting up .bashrc with aliases..."
|
||||
cp /workspace/.devcontainer/.bashrc ~/.bashrc
|
||||
# Add to .profile to ensure .bashrc is sourced in non-interactive shells
|
||||
echo 'if [ -f ~/.bashrc ]; then . ~/.bashrc; fi' >> ~/.profile
|
||||
# Install global packages for development (done at runtime, not build time)
|
||||
echo "📦 Installing global development tools..."
|
||||
bun install -g turbo drizzle-kit typescript @types/node 2>/dev/null || {
|
||||
echo "⚠️ Some global packages may already be installed, continuing..."
|
||||
}
|
||||
|
||||
# Set up bun completions (with proper shell detection)
|
||||
echo "🔧 Setting up shell completions..."
|
||||
if [ -n "$SHELL" ] && [ -f "$SHELL" ]; then
|
||||
SHELL=/bin/bash bun completions 2>/dev/null | sudo tee /etc/bash_completion.d/bun > /dev/null || {
|
||||
echo "⚠️ Could not install bun completions, but continuing..."
|
||||
}
|
||||
fi
|
||||
|
||||
# Add project commands to shell profile
|
||||
echo "📄 Setting up project commands..."
|
||||
# Add sourcing of sim-commands.sh to user's shell config files if they exist
|
||||
for rcfile in ~/.bashrc ~/.zshrc; do
|
||||
if [ -f "$rcfile" ]; then
|
||||
# Check if already added
|
||||
if ! grep -q "sim-commands.sh" "$rcfile"; then
|
||||
echo "" >> "$rcfile"
|
||||
echo "# Sim project commands" >> "$rcfile"
|
||||
echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> "$rcfile"
|
||||
echo " source /workspace/.devcontainer/sim-commands.sh" >> "$rcfile"
|
||||
echo "fi" >> "$rcfile"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# If no rc files exist yet, create a minimal one
|
||||
if [ ! -f ~/.bashrc ] && [ ! -f ~/.zshrc ]; then
|
||||
echo "# Source Sim project commands" > ~/.bashrc
|
||||
echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> ~/.bashrc
|
||||
echo " source /workspace/.devcontainer/sim-commands.sh" >> ~/.bashrc
|
||||
echo "fi" >> ~/.bashrc
|
||||
fi
|
||||
|
||||
# Clean and reinstall dependencies to ensure platform compatibility
|
||||
echo "📦 Cleaning and reinstalling dependencies..."
|
||||
@@ -29,18 +61,12 @@ chmod 700 ~/.bun ~/.bun/cache
|
||||
|
||||
# Install dependencies with platform-specific binaries
|
||||
echo "Installing dependencies with Bun..."
|
||||
bun install || {
|
||||
echo "⚠️ bun install had issues but continuing setup..."
|
||||
}
|
||||
bun install
|
||||
|
||||
# Check for native dependencies
|
||||
echo "Checking for native dependencies compatibility..."
|
||||
NATIVE_DEPS=$(grep '"trustedDependencies"' apps/sim/package.json || echo "")
|
||||
if [ ! -z "$NATIVE_DEPS" ]; then
|
||||
echo "⚠️ Native dependencies detected. Ensuring compatibility with Bun..."
|
||||
for pkg in $(echo $NATIVE_DEPS | grep -oP '"[^"]*"' | tr -d '"' | grep -v "trustedDependencies"); do
|
||||
echo "Checking compatibility for $pkg..."
|
||||
done
|
||||
if grep -q '"trustedDependencies"' apps/sim/package.json 2>/dev/null; then
|
||||
echo "⚠️ Native dependencies detected. Bun will handle compatibility during install."
|
||||
fi
|
||||
|
||||
# Set up environment variables if .env doesn't exist for the sim app
|
||||
@@ -82,23 +108,6 @@ echo "Waiting for database to be ready..."
|
||||
fi
|
||||
) || echo "⚠️ Database setup had issues but continuing..."
|
||||
|
||||
# Add additional helpful aliases to .bashrc
|
||||
cat << EOF >> ~/.bashrc
|
||||
|
||||
# Additional Sim Development Aliases
|
||||
alias migrate="cd /workspace/apps/sim && DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio bunx drizzle-kit push"
|
||||
alias generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||
alias dev="cd /workspace && bun run dev"
|
||||
alias build="cd /workspace && bun run build"
|
||||
alias start="cd /workspace && bun run dev"
|
||||
alias lint="cd /workspace/apps/sim && bun run lint"
|
||||
alias test="cd /workspace && bun run test"
|
||||
alias bun-update="cd /workspace && bun update"
|
||||
EOF
|
||||
|
||||
# Source the .bashrc to make aliases available immediately
|
||||
. ~/.bashrc
|
||||
|
||||
# Clear the welcome message flag to ensure it shows after setup
|
||||
unset SIM_WELCOME_SHOWN
|
||||
|
||||
|
||||
42
.devcontainer/sim-commands.sh
Executable file
42
.devcontainer/sim-commands.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# Sim Project Commands
|
||||
# Source this file to add project-specific commands to your shell
|
||||
# Add to your ~/.bashrc or ~/.zshrc: source /workspace/.devcontainer/sim-commands.sh
|
||||
|
||||
# Project-specific aliases for Sim development
|
||||
alias sim-start="cd /workspace && bun run dev:full"
|
||||
alias sim-app="cd /workspace && bun run dev"
|
||||
alias sim-sockets="cd /workspace && bun run dev:sockets"
|
||||
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
|
||||
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||
alias sim-rebuild="cd /workspace && bun run build && bun run start"
|
||||
alias docs-dev="cd /workspace/apps/docs && bun run dev"
|
||||
|
||||
# Database connection helpers
|
||||
alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio"
|
||||
alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'"
|
||||
|
||||
# Default to workspace directory
|
||||
cd /workspace 2>/dev/null || true
|
||||
|
||||
# Welcome message - show once per session
|
||||
if [ -z "$SIM_WELCOME_SHOWN" ]; then
|
||||
export SIM_WELCOME_SHOWN=1
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 Sim Development Environment"
|
||||
echo ""
|
||||
echo "Project commands:"
|
||||
echo " sim-start - Start app + socket server"
|
||||
echo " sim-app - Start only main app"
|
||||
echo " sim-sockets - Start only socket server"
|
||||
echo " sim-migrate - Push schema changes"
|
||||
echo " sim-generate - Generate migrations"
|
||||
echo ""
|
||||
echo "Database:"
|
||||
echo " pgc - Connect to PostgreSQL"
|
||||
echo " check-db - List databases"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
fi
|
||||
4
.github/workflows/docs-embeddings.yml
vendored
4
.github/workflows/docs-embeddings.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -32,4 +32,4 @@ jobs:
|
||||
env:
|
||||
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: bun run scripts/process-docs-embeddings.ts --clear
|
||||
run: bun run scripts/process-docs.ts --clear
|
||||
|
||||
4
.github/workflows/i18n.yml
vendored
4
.github/workflows/i18n.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Run Lingo.dev translations
|
||||
env:
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
2
.github/workflows/migrations.yml
vendored
2
.github/workflows/migrations.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
2
.github/workflows/publish-cli.yml
vendored
2
.github/workflows/publish-cli.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Setup Node.js for npm publishing
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
2
.github/workflows/publish-python-sdk.yml
vendored
2
.github/workflows/publish-python-sdk.yml
vendored
@@ -84,6 +84,6 @@ jobs:
|
||||
```
|
||||
|
||||
### Documentation
|
||||
See the [README](https://github.com/simstudio/sim/tree/main/packages/python-sdk) for usage instructions.
|
||||
See the [README](https://github.com/simstudioai/sim/tree/main/packages/python-sdk) or the [docs](https://docs.sim.ai/sdks/python) for more information.
|
||||
draft: false
|
||||
prerelease: false
|
||||
7
.github/workflows/publish-ts-sdk.yml
vendored
7
.github/workflows/publish-ts-sdk.yml
vendored
@@ -16,16 +16,15 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Setup Node.js for npm publishing
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: packages/ts-sdk
|
||||
run: bun install
|
||||
|
||||
- name: Run tests
|
||||
@@ -80,6 +79,6 @@ jobs:
|
||||
```
|
||||
|
||||
### Documentation
|
||||
See the [README](https://github.com/simstudio/sim/tree/main/packages/ts-sdk) for usage instructions.
|
||||
See the [README](https://github.com/simstudioai/sim/tree/main/packages/ts-sdk) or the [docs](https://docs.sim.ai/sdks/typescript) for more information.
|
||||
draft: false
|
||||
prerelease: false
|
||||
2
.github/workflows/test-build.yml
vendored
2
.github/workflows/test-build.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
2
.github/workflows/trigger-deploy.yml
vendored
2
.github/workflows/trigger-deploy.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
bun-version: 1.2.22
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { defineI18nUI } from 'fumadocs-ui/i18n'
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
||||
import { RootProvider } from 'fumadocs-ui/provider'
|
||||
import { RootProvider } from 'fumadocs-ui/provider/next'
|
||||
import { ExternalLink, GithubIcon } from 'lucide-react'
|
||||
import { Inter } from 'next/font/google'
|
||||
import Image from 'next/image'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { getVideoUrl } from '@/lib/utils'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
|
||||
interface LightboxProps {
|
||||
isOpen: boolean
|
||||
@@ -60,7 +60,7 @@ export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={getVideoUrl(src)}
|
||||
src={getAssetUrl(src)}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { getVideoUrl } from '@/lib/utils'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { Lightbox } from './lightbox'
|
||||
|
||||
interface VideoProps {
|
||||
@@ -39,7 +39,7 @@ export function Video({
|
||||
muted={muted}
|
||||
playsInline={playsInline}
|
||||
className={`${className} ${enableLightbox ? 'cursor-pointer transition-opacity hover:opacity-90' : ''}`}
|
||||
src={getVideoUrl(src)}
|
||||
src={getAssetUrl(src)}
|
||||
onClick={handleVideoClick}
|
||||
/>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
Das offizielle Python SDK für Sim ermöglicht es Ihnen, Workflows programmatisch aus Ihren Python-Anwendungen mithilfe des offiziellen Python SDKs auszuführen.
|
||||
|
||||
<Callout type="info">
|
||||
Das Python SDK unterstützt Python 3.8+ und bietet synchrone Workflow-Ausführung. Alle Workflow-Ausführungen sind derzeit synchron.
|
||||
Das Python SDK unterstützt Python 3.8+ mit asynchroner Ausführungsunterstützung, automatischer Ratenbegrenzung mit exponentiellem Backoff und Nutzungsverfolgung.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
@@ -74,12 +74,17 @@ result = client.execute_workflow(
|
||||
- `workflow_id` (str): Die ID des auszuführenden Workflows
|
||||
- `input_data` (dict, optional): Eingabedaten, die an den Workflow übergeben werden
|
||||
- `timeout` (float, optional): Timeout in Sekunden (Standard: 30.0)
|
||||
- `stream` (bool, optional): Streaming-Antworten aktivieren (Standard: False)
|
||||
- `selected_outputs` (list[str], optional): Block-Ausgaben, die im `blockName.attribute`Format gestreamt werden sollen (z.B. `["agent1.content"]`)
|
||||
- `async_execution` (bool, optional): Asynchron ausführen (Standard: False)
|
||||
|
||||
**Rückgabewert:** `WorkflowExecutionResult`
|
||||
**Rückgabe:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
Wenn `async_execution=True`, wird sofort mit einer Task-ID zum Abfragen zurückgegeben. Andernfalls wird auf den Abschluss gewartet.
|
||||
|
||||
##### get_workflow_status()
|
||||
|
||||
Ruft den Status eines Workflows ab (Deployment-Status usw.).
|
||||
Den Status eines Workflows abrufen (Bereitstellungsstatus usw.).
|
||||
|
||||
```python
|
||||
status = client.get_workflow_status("workflow-id")
|
||||
@@ -93,7 +98,7 @@ print("Is deployed:", status.is_deployed)
|
||||
|
||||
##### validate_workflow()
|
||||
|
||||
Überprüft, ob ein Workflow für die Ausführung bereit ist.
|
||||
Überprüfen, ob ein Workflow für die Ausführung bereit ist.
|
||||
|
||||
```python
|
||||
is_ready = client.validate_workflow("workflow-id")
|
||||
@@ -107,28 +112,118 @@ if is_ready:
|
||||
|
||||
**Rückgabe:** `bool`
|
||||
|
||||
##### execute_workflow_sync()
|
||||
##### get_job_status()
|
||||
|
||||
<Callout type="info">
|
||||
Derzeit ist diese Methode identisch mit `execute_workflow()`, da alle Ausführungen synchron sind. Diese Methode wird für zukünftige Kompatibilität bereitgestellt, wenn asynchrone Ausführung hinzugefügt wird.
|
||||
</Callout>
|
||||
|
||||
Führt einen Workflow aus (derzeit synchron, identisch mit `execute_workflow()`).
|
||||
Den Status einer asynchronen Job-Ausführung abrufen.
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
status = client.get_job_status("task-id-from-async-execution")
|
||||
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
|
||||
if status["status"] == "completed":
|
||||
print("Output:", status["output"])
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `task_id` (str): Die Task-ID, die von der asynchronen Ausführung zurückgegeben wurde
|
||||
|
||||
**Rückgabe:** `Dict[str, Any]`
|
||||
|
||||
**Antwortfelder:**
|
||||
- `success` (bool): Ob die Anfrage erfolgreich war
|
||||
- `taskId` (str): Die Task-ID
|
||||
- `status` (str): Einer der Werte `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (dict): Enthält `startedAt`, `completedAt` und `duration`
|
||||
- `output` (any, optional): Die Workflow-Ausgabe (wenn abgeschlossen)
|
||||
- `error` (any, optional): Fehlerdetails (wenn fehlgeschlagen)
|
||||
- `estimatedDuration` (int, optional): Geschätzte Dauer in Millisekunden (wenn in Bearbeitung/in Warteschlange)
|
||||
|
||||
##### execute_with_retry()
|
||||
|
||||
Einen Workflow mit automatischer Wiederholung bei Ratenbegrenzungsfehlern unter Verwendung von exponentiellem Backoff ausführen.
|
||||
|
||||
```python
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
input_data={"message": "Hello"},
|
||||
timeout=30.0,
|
||||
max_retries=3, # Maximum number of retries
|
||||
initial_delay=1.0, # Initial delay in seconds
|
||||
max_delay=30.0, # Maximum delay in seconds
|
||||
backoff_multiplier=2.0 # Exponential backoff multiplier
|
||||
)
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `workflow_id` (str): Die ID des auszuführenden Workflows
|
||||
- `input_data` (dict, optional): Eingabedaten, die an den Workflow übergeben werden
|
||||
- `timeout` (float): Timeout für die initiale Anfrage in Sekunden
|
||||
- `timeout` (float, optional): Timeout in Sekunden
|
||||
- `stream` (bool, optional): Streaming-Antworten aktivieren
|
||||
- `selected_outputs` (list, optional): Block-Ausgaben zum Streamen
|
||||
- `async_execution` (bool, optional): Asynchron ausführen
|
||||
- `max_retries` (int, optional): Maximale Anzahl von Wiederholungen (Standard: 3)
|
||||
- `initial_delay` (float, optional): Anfängliche Verzögerung in Sekunden (Standard: 1.0)
|
||||
- `max_delay` (float, optional): Maximale Verzögerung in Sekunden (Standard: 30.0)
|
||||
- `backoff_multiplier` (float, optional): Backoff-Multiplikator (Standard: 2.0)
|
||||
|
||||
**Rückgabe:** `WorkflowExecutionResult`
|
||||
**Rückgabewert:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
Die Wiederholungslogik verwendet exponentielles Backoff (1s → 2s → 4s → 8s...) mit ±25% Jitter, um den Thundering-Herd-Effekt zu vermeiden. Wenn die API einen `retry-after` Header bereitstellt, wird dieser stattdessen verwendet.
|
||||
|
||||
##### get_rate_limit_info()
|
||||
|
||||
Ruft die aktuellen Rate-Limit-Informationen aus der letzten API-Antwort ab.
|
||||
|
||||
```python
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
print("Limit:", rate_limit_info.limit)
|
||||
print("Remaining:", rate_limit_info.remaining)
|
||||
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
|
||||
```
|
||||
|
||||
**Rückgabewert:** `RateLimitInfo | None`
|
||||
|
||||
##### get_usage_limits()
|
||||
|
||||
Ruft aktuelle Nutzungslimits und Kontingentinformationen für dein Konto ab.
|
||||
|
||||
```python
|
||||
limits = client.get_usage_limits()
|
||||
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
|
||||
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
|
||||
print("Current period cost:", limits.usage["currentPeriodCost"])
|
||||
print("Plan:", limits.usage["plan"])
|
||||
```
|
||||
|
||||
**Rückgabewert:** `UsageLimits`
|
||||
|
||||
**Antwortstruktur:**
|
||||
|
||||
```python
|
||||
{
|
||||
"success": bool,
|
||||
"rateLimit": {
|
||||
"sync": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"async": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"authType": str # 'api' or 'manual'
|
||||
},
|
||||
"usage": {
|
||||
"currentPeriodCost": float,
|
||||
"limit": float,
|
||||
"plan": str # e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### set_api_key()
|
||||
|
||||
@@ -170,6 +265,18 @@ class WorkflowExecutionResult:
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class AsyncExecutionResult:
|
||||
success: bool
|
||||
task_id: str
|
||||
status: str # 'queued'
|
||||
created_at: str
|
||||
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@@ -181,6 +288,27 @@ class WorkflowStatus:
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class RateLimitInfo:
|
||||
limit: int
|
||||
remaining: int
|
||||
reset: int
|
||||
retry_after: Optional[int] = None
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UsageLimits:
|
||||
success: bool
|
||||
rate_limit: Dict[str, Any]
|
||||
usage: Dict[str, Any]
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
@@ -191,6 +319,13 @@ class SimStudioError(Exception):
|
||||
self.status = status
|
||||
```
|
||||
|
||||
**Häufige Fehlercodes:**
|
||||
- `UNAUTHORIZED`: Ungültiger API-Schlüssel
|
||||
- `TIMEOUT`: Zeitüberschreitung bei der Anfrage
|
||||
- `RATE_LIMIT_EXCEEDED`: Ratengrenze überschritten
|
||||
- `USAGE_LIMIT_EXCEEDED`: Nutzungsgrenze überschritten
|
||||
- `EXECUTION_ERROR`: Workflow-Ausführung fehlgeschlagen
|
||||
|
||||
## Beispiele
|
||||
|
||||
### Grundlegende Workflow-Ausführung
|
||||
@@ -214,7 +349,7 @@ class SimStudioError(Exception):
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
@@ -252,7 +387,7 @@ Behandeln Sie verschiedene Fehlertypen, die während der Workflow-Ausführung au
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
@@ -279,16 +414,7 @@ def execute_with_error_handling():
|
||||
|
||||
Verwenden Sie den Client als Kontextmanager, um die Ressourcenbereinigung automatisch zu handhaben:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
```
|
||||
---CODE-PLACEHOLDER-ef99d3dd509e04865d5b6b0e0e03d3f8---
|
||||
|
||||
### Batch-Workflow-Ausführung
|
||||
|
||||
@@ -298,7 +424,7 @@ Führen Sie mehrere Workflows effizient aus:
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
@@ -339,9 +465,233 @@ for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
### Asynchrone Workflow-Ausführung
|
||||
|
||||
Führen Sie Workflows asynchron für lang laufende Aufgaben aus:
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_async():
|
||||
try:
|
||||
# Start async execution
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"data": "large dataset"},
|
||||
async_execution=True # Execute asynchronously
|
||||
)
|
||||
|
||||
# Check if result is an async execution
|
||||
if hasattr(result, 'task_id'):
|
||||
print(f"Task ID: {result.task_id}")
|
||||
print(f"Status endpoint: {result.links['status']}")
|
||||
|
||||
# Poll for completion
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
while status["status"] in ["queued", "processing"]:
|
||||
print(f"Current status: {status['status']}")
|
||||
time.sleep(2) # Wait 2 seconds
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
if status["status"] == "completed":
|
||||
print("Workflow completed!")
|
||||
print(f"Output: {status['output']}")
|
||||
print(f"Duration: {status['metadata']['duration']}")
|
||||
else:
|
||||
print(f"Workflow failed: {status['error']}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
execute_async()
|
||||
```
|
||||
|
||||
### Rate-Limiting und Wiederholungsversuche
|
||||
|
||||
Behandle Rate-Limits automatisch mit exponentiellem Backoff:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_retry_handling():
|
||||
try:
|
||||
# Automatically retries on rate limit
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"message": "Process this"},
|
||||
max_retries=5,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
backoff_multiplier=2.0
|
||||
)
|
||||
|
||||
print(f"Success: {result}")
|
||||
except SimStudioError as error:
|
||||
if error.code == "RATE_LIMIT_EXCEEDED":
|
||||
print("Rate limit exceeded after all retries")
|
||||
|
||||
# Check rate limit info
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
from datetime import datetime
|
||||
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
|
||||
print(f"Rate limit resets at: {reset_time}")
|
||||
|
||||
execute_with_retry_handling()
|
||||
```
|
||||
|
||||
### Nutzungsüberwachung
|
||||
|
||||
Überwache deine Kontonutzung und -limits:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def check_usage():
|
||||
try:
|
||||
limits = client.get_usage_limits()
|
||||
|
||||
print("=== Rate Limits ===")
|
||||
print("Sync requests:")
|
||||
print(f" Limit: {limits.rate_limit['sync']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
|
||||
|
||||
print("\nAsync requests:")
|
||||
print(f" Limit: {limits.rate_limit['async']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
|
||||
print(f"Limit: ${limits.usage['limit']:.2f}")
|
||||
print(f"Plan: {limits.usage['plan']}")
|
||||
|
||||
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
|
||||
print(f"Usage: {percent_used:.1f}%")
|
||||
|
||||
if percent_used > 80:
|
||||
print("⚠️ Warning: You are approaching your usage limit!")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error checking usage: {error}")
|
||||
|
||||
check_usage()
|
||||
```
|
||||
|
||||
### Streaming-Workflow-Ausführung
|
||||
|
||||
Führe Workflows mit Echtzeit-Streaming-Antworten aus:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_streaming():
|
||||
"""Execute workflow with streaming enabled."""
|
||||
try:
|
||||
# Enable streaming for specific block outputs
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Count to five"},
|
||||
stream=True,
|
||||
selected_outputs=["agent1.content"] # Use blockName.attribute format
|
||||
)
|
||||
|
||||
print("Workflow result:", result)
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
execute_with_streaming()
|
||||
```
|
||||
|
||||
Die Streaming-Antwort folgt dem Server-Sent Events (SSE) Format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Flask-Streaming-Beispiel:**
|
||||
|
||||
```python
|
||||
from flask import Flask, Response, stream_with_context
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/stream-workflow')
|
||||
def stream_workflow():
|
||||
"""Stream workflow execution to the client."""
|
||||
|
||||
def generate():
|
||||
response = requests.post(
|
||||
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': os.getenv('SIM_API_KEY')
|
||||
},
|
||||
json={
|
||||
'message': 'Generate a story',
|
||||
'stream': True,
|
||||
'selectedOutputs': ['agent1.content']
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
data = decoded_line[6:] # Remove 'data: ' prefix
|
||||
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
if 'chunk' in parsed:
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
elif parsed.get('event') == 'done':
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
print("Execution complete:", parsed.get('metadata'))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
### Umgebungskonfiguration
|
||||
|
||||
Konfigurieren Sie den Client mit Umgebungsvariablen:
|
||||
Konfiguriere den Client mit Umgebungsvariablen:
|
||||
|
||||
<Tabs items={['Development', 'Production']}>
|
||||
<Tab value="Development">
|
||||
@@ -352,8 +702,8 @@ Konfigurieren Sie den Client mit Umgebungsvariablen:
|
||||
|
||||
# Development configuration
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
api_key=os.getenv("SIM_API_KEY")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -365,13 +715,13 @@ Konfigurieren Sie den Client mit Umgebungsvariablen:
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Production configuration with error handling
|
||||
api_key = os.getenv("SIMSTUDIO_API_KEY")
|
||||
api_key = os.getenv("SIM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("SIMSTUDIO_API_KEY environment variable is required")
|
||||
raise ValueError("SIM_API_KEY environment variable is required")
|
||||
|
||||
client = SimStudioClient(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -382,19 +732,19 @@ Konfigurieren Sie den Client mit Umgebungsvariablen:
|
||||
|
||||
<Steps>
|
||||
<Step title="Bei Sim anmelden">
|
||||
Navigieren Sie zu [Sim](https://sim.ai) und melden Sie sich bei Ihrem Konto an.
|
||||
Navigiere zu [Sim](https://sim.ai) und melde dich bei deinem Konto an.
|
||||
</Step>
|
||||
<Step title="Ihren Workflow öffnen">
|
||||
Navigieren Sie zu dem Workflow, den Sie programmatisch ausführen möchten.
|
||||
<Step title="Öffne deinen Workflow">
|
||||
Navigiere zu dem Workflow, den du programmatisch ausführen möchtest.
|
||||
</Step>
|
||||
<Step title="Ihren Workflow bereitstellen">
|
||||
Klicken Sie auf "Deploy", um Ihren Workflow bereitzustellen, falls dies noch nicht geschehen ist.
|
||||
<Step title="Deploye deinen Workflow">
|
||||
Klicke auf "Deploy", um deinen Workflow zu deployen, falls dies noch nicht geschehen ist.
|
||||
</Step>
|
||||
<Step title="API-Schlüssel erstellen oder auswählen">
|
||||
Wählen Sie während des Bereitstellungsprozesses einen API-Schlüssel aus oder erstellen Sie einen neuen.
|
||||
<Step title="Erstelle oder wähle einen API-Schlüssel">
|
||||
Wähle während des Deployment-Prozesses einen API-Schlüssel aus oder erstelle einen neuen.
|
||||
</Step>
|
||||
<Step title="API-Schlüssel kopieren">
|
||||
Kopieren Sie den API-Schlüssel zur Verwendung in Ihrer Python-Anwendung.
|
||||
<Step title="Kopiere den API-Schlüssel">
|
||||
Kopiere den API-Schlüssel zur Verwendung in deiner Python-Anwendung.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
Das offizielle TypeScript/JavaScript SDK für Sim bietet vollständige Typsicherheit und unterstützt sowohl Node.js- als auch Browser-Umgebungen, sodass Sie Workflows programmatisch aus Ihren Node.js-Anwendungen, Webanwendungen und anderen JavaScript-Umgebungen ausführen können. Alle Workflow-Ausführungen sind derzeit synchron.
|
||||
Das offizielle TypeScript/JavaScript SDK für Sim bietet vollständige Typsicherheit und unterstützt sowohl Node.js- als auch Browser-Umgebungen, sodass Sie Workflows programmatisch aus Ihren Node.js-Anwendungen, Webanwendungen und anderen JavaScript-Umgebungen ausführen können.
|
||||
|
||||
<Callout type="info">
|
||||
Das TypeScript SDK bietet vollständige Typsicherheit und unterstützt sowohl Node.js- als auch Browser-Umgebungen. Alle Workflow-Ausführungen sind derzeit synchron.
|
||||
Das TypeScript SDK bietet vollständige Typsicherheit, Unterstützung für asynchrone Ausführung, automatische Ratenbegrenzung mit exponentiellem Backoff und Nutzungsverfolgung.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
@@ -95,8 +95,13 @@ const result = await client.executeWorkflow('workflow-id', {
|
||||
- `options` (ExecutionOptions, optional):
|
||||
- `input` (any): Eingabedaten, die an den Workflow übergeben werden
|
||||
- `timeout` (number): Timeout in Millisekunden (Standard: 30000)
|
||||
- `stream` (boolean): Streaming-Antworten aktivieren (Standard: false)
|
||||
- `selectedOutputs` (string[]): Block-Ausgaben, die im `blockName.attribute`Format gestreamt werden sollen (z.B. `["agent1.content"]`)
|
||||
- `async` (boolean): Asynchron ausführen (Standard: false)
|
||||
|
||||
**Rückgabewert:** `Promise<WorkflowExecutionResult>`
|
||||
**Rückgabe:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
Wenn `async: true`, wird sofort mit einer Task-ID zum Abfragen zurückgegeben. Andernfalls wird auf den Abschluss gewartet.
|
||||
|
||||
##### getWorkflowStatus()
|
||||
|
||||
@@ -110,7 +115,7 @@ console.log('Is deployed:', status.isDeployed);
|
||||
**Parameter:**
|
||||
- `workflowId` (string): Die ID des Workflows
|
||||
|
||||
**Rückgabewert:** `Promise<WorkflowStatus>`
|
||||
**Rückgabe:** `Promise<WorkflowStatus>`
|
||||
|
||||
##### validateWorkflow()
|
||||
|
||||
@@ -126,34 +131,123 @@ if (isReady) {
|
||||
**Parameter:**
|
||||
- `workflowId` (string): Die ID des Workflows
|
||||
|
||||
**Rückgabewert:** `Promise<boolean>`
|
||||
**Rückgabe:** `Promise<boolean>`
|
||||
|
||||
##### executeWorkflowSync()
|
||||
##### getJobStatus()
|
||||
|
||||
<Callout type="info">
|
||||
Derzeit ist diese Methode identisch mit `executeWorkflow()`, da alle Ausführungen synchron sind. Diese Methode wird für zukünftige Kompatibilität bereitgestellt, wenn asynchrone Ausführung hinzugefügt wird.
|
||||
</Callout>
|
||||
|
||||
Einen Workflow ausführen (derzeit synchron, identisch mit `executeWorkflow()`).
|
||||
Den Status einer asynchronen Job-Ausführung abrufen.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWorkflowSync('workflow-id', {
|
||||
input: { data: 'some input' },
|
||||
timeout: 60000
|
||||
const status = await client.getJobStatus('task-id-from-async-execution');
|
||||
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
|
||||
if (status.status === 'completed') {
|
||||
console.log('Output:', status.output);
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `taskId` (string): Die Task-ID, die von der asynchronen Ausführung zurückgegeben wurde
|
||||
|
||||
**Rückgabe:** `Promise<JobStatus>`
|
||||
|
||||
**Antwortfelder:**
|
||||
- `success` (boolean): Ob die Anfrage erfolgreich war
|
||||
- `taskId` (string): Die Task-ID
|
||||
- `status` (string): Einer der Werte `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (object): Enthält `startedAt`, `completedAt` und `duration`
|
||||
- `output` (any, optional): Die Workflow-Ausgabe (wenn abgeschlossen)
|
||||
- `error` (any, optional): Fehlerdetails (wenn fehlgeschlagen)
|
||||
- `estimatedDuration` (number, optional): Geschätzte Dauer in Millisekunden (wenn in Bearbeitung/in der Warteschlange)
|
||||
|
||||
##### executeWithRetry()
|
||||
|
||||
Führt einen Workflow mit automatischer Wiederholung bei Ratenlimitfehlern unter Verwendung von exponentiellem Backoff aus.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Hello' },
|
||||
timeout: 30000
|
||||
}, {
|
||||
maxRetries: 3, // Maximum number of retries
|
||||
initialDelay: 1000, // Initial delay in ms (1 second)
|
||||
maxDelay: 30000, // Maximum delay in ms (30 seconds)
|
||||
backoffMultiplier: 2 // Exponential backoff multiplier
|
||||
});
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `workflowId` (string): Die ID des auszuführenden Workflows
|
||||
- `options` (ExecutionOptions, optional):
|
||||
- `input` (any): Eingabedaten, die an den Workflow übergeben werden
|
||||
- `timeout` (number): Timeout für die initiale Anfrage in Millisekunden
|
||||
- `options` (ExecutionOptions, optional): Gleich wie `executeWorkflow()`
|
||||
- `retryOptions` (RetryOptions, optional):
|
||||
- `maxRetries` (number): Maximale Anzahl von Wiederholungen (Standard: 3)
|
||||
- `initialDelay` (number): Anfängliche Verzögerung in ms (Standard: 1000)
|
||||
- `maxDelay` (number): Maximale Verzögerung in ms (Standard: 30000)
|
||||
- `backoffMultiplier` (number): Backoff-Multiplikator (Standard: 2)
|
||||
|
||||
**Rückgabewert:** `Promise<WorkflowExecutionResult>`
|
||||
**Rückgabewert:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
Die Wiederholungslogik verwendet exponentiellen Backoff (1s → 2s → 4s → 8s...) mit ±25% Jitter, um den Thundering-Herd-Effekt zu vermeiden. Wenn die API einen `retry-after`Header bereitstellt, wird dieser stattdessen verwendet.
|
||||
|
||||
##### getRateLimitInfo()
|
||||
|
||||
Ruft die aktuellen Ratenlimit-Informationen aus der letzten API-Antwort ab.
|
||||
|
||||
```typescript
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Limit:', rateLimitInfo.limit);
|
||||
console.log('Remaining:', rateLimitInfo.remaining);
|
||||
console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
```
|
||||
|
||||
**Rückgabewert:** `RateLimitInfo | null`
|
||||
|
||||
##### getUsageLimits()
|
||||
|
||||
Ruft aktuelle Nutzungslimits und Kontingentinformationen für Ihr Konto ab.
|
||||
|
||||
```typescript
|
||||
const limits = await client.getUsageLimits();
|
||||
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
|
||||
console.log('Current period cost:', limits.usage.currentPeriodCost);
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
```
|
||||
|
||||
**Rückgabewert:** `Promise<UsageLimits>`
|
||||
|
||||
**Antwortstruktur:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
success: boolean
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
authType: string // 'api' or 'manual'
|
||||
}
|
||||
usage: {
|
||||
currentPeriodCost: number
|
||||
limit: number
|
||||
plan: string // e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### setApiKey()
|
||||
|
||||
Den API-Schlüssel aktualisieren.
|
||||
Aktualisiert den API-Schlüssel.
|
||||
|
||||
```typescript
|
||||
client.setApiKey('new-api-key');
|
||||
@@ -161,7 +255,7 @@ client.setApiKey('new-api-key');
|
||||
|
||||
##### setBaseUrl()
|
||||
|
||||
Die Basis-URL aktualisieren.
|
||||
Aktualisiert die Basis-URL.
|
||||
|
||||
```typescript
|
||||
client.setBaseUrl('https://my-custom-domain.com');
|
||||
@@ -187,6 +281,20 @@ interface WorkflowExecutionResult {
|
||||
}
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```typescript
|
||||
interface AsyncExecutionResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
status: 'queued';
|
||||
createdAt: string;
|
||||
links: {
|
||||
status: string; // e.g., "/api/jobs/{taskId}"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```typescript
|
||||
@@ -198,6 +306,45 @@ interface WorkflowStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```typescript
|
||||
interface RateLimitInfo {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```typescript
|
||||
interface UsageLimits {
|
||||
success: boolean;
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
async: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
authType: string;
|
||||
};
|
||||
usage: {
|
||||
currentPeriodCost: number;
|
||||
limit: number;
|
||||
plan: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```typescript
|
||||
@@ -207,6 +354,13 @@ class SimStudioError extends Error {
|
||||
}
|
||||
```
|
||||
|
||||
**Häufige Fehlercodes:**
|
||||
- `UNAUTHORIZED`: Ungültiger API-Schlüssel
|
||||
- `TIMEOUT`: Zeitüberschreitung der Anfrage
|
||||
- `RATE_LIMIT_EXCEEDED`: Ratengrenze überschritten
|
||||
- `USAGE_LIMIT_EXCEEDED`: Nutzungsgrenze überschritten
|
||||
- `EXECUTION_ERROR`: Workflow-Ausführung fehlgeschlagen
|
||||
|
||||
## Beispiele
|
||||
|
||||
### Grundlegende Workflow-Ausführung
|
||||
@@ -230,7 +384,7 @@ class SimStudioError extends Error {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function runWorkflow() {
|
||||
@@ -271,7 +425,7 @@ Behandeln Sie verschiedene Fehlertypen, die während der Workflow-Ausführung au
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithErrorHandling() {
|
||||
@@ -315,14 +469,14 @@ Konfigurieren Sie den Client mit Umgebungsvariablen:
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Development configuration
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL // optional
|
||||
baseUrl: process.env.SIM_BASE_URL // optional
|
||||
});
|
||||
```
|
||||
|
||||
@@ -333,14 +487,14 @@ Konfigurieren Sie den Client mit Umgebungsvariablen:
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Production configuration with validation
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://sim.ai'
|
||||
baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
|
||||
});
|
||||
```
|
||||
|
||||
@@ -357,7 +511,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const app = express();
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
@@ -399,7 +553,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -467,16 +621,16 @@ document.getElementById('executeBtn')?.addEventListener('click', executeClientSi
|
||||
Bei der Verwendung des SDK im Browser sollten Sie darauf achten, keine sensiblen API-Schlüssel offenzulegen. Erwägen Sie die Verwendung eines Backend-Proxys oder öffentlicher API-Schlüssel mit eingeschränkten Berechtigungen.
|
||||
</Callout>
|
||||
|
||||
### React Hook Beispiel
|
||||
### React Hook-Beispiel
|
||||
|
||||
Erstellen Sie einen benutzerdefinierten React Hook für die Workflow-Ausführung:
|
||||
Erstellen eines benutzerdefinierten React-Hooks für die Workflow-Ausführung:
|
||||
|
||||
```typescript
|
||||
import { useState, useCallback } from 'react';
|
||||
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
interface UseWorkflowResult {
|
||||
@@ -532,7 +686,7 @@ function WorkflowComponent() {
|
||||
<button onClick={handleExecute} disabled={loading}>
|
||||
{loading ? 'Executing...' : 'Execute Workflow'}
|
||||
</button>
|
||||
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{result && (
|
||||
<div>
|
||||
@@ -545,38 +699,267 @@ function WorkflowComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
## Ihren API-Schlüssel erhalten
|
||||
### Asynchrone Workflow-Ausführung
|
||||
|
||||
Führen Sie Workflows asynchron für lang laufende Aufgaben aus:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeAsync() {
|
||||
try {
|
||||
// Start async execution
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { data: 'large dataset' },
|
||||
async: true // Execute asynchronously
|
||||
});
|
||||
|
||||
// Check if result is an async execution
|
||||
if ('taskId' in result) {
|
||||
console.log('Task ID:', result.taskId);
|
||||
console.log('Status endpoint:', result.links.status);
|
||||
|
||||
// Poll for completion
|
||||
let status = await client.getJobStatus(result.taskId);
|
||||
|
||||
while (status.status === 'queued' || status.status === 'processing') {
|
||||
console.log('Current status:', status.status);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||
status = await client.getJobStatus(result.taskId);
|
||||
}
|
||||
|
||||
if (status.status === 'completed') {
|
||||
console.log('Workflow completed!');
|
||||
console.log('Output:', status.output);
|
||||
console.log('Duration:', status.metadata.duration);
|
||||
} else {
|
||||
console.error('Workflow failed:', status.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
executeAsync();
|
||||
```
|
||||
|
||||
### Rate-Limiting und Wiederholungsversuche
|
||||
|
||||
Automatische Behandlung von Rate-Limits mit exponentiellem Backoff:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithRetryHandling() {
|
||||
try {
|
||||
// Automatically retries on rate limit
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Process this' }
|
||||
}, {
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 60000,
|
||||
backoffMultiplier: 2
|
||||
});
|
||||
|
||||
console.log('Success:', result);
|
||||
} catch (error) {
|
||||
if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
console.error('Rate limit exceeded after all retries');
|
||||
|
||||
// Check rate limit info
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Nutzungsüberwachung
|
||||
|
||||
Überwachen Sie Ihre Kontonutzung und -limits:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function checkUsage() {
|
||||
try {
|
||||
const limits = await client.getUsageLimits();
|
||||
|
||||
console.log('=== Rate Limits ===');
|
||||
console.log('Sync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.sync.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.sync.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.sync.isLimited);
|
||||
|
||||
console.log('\nAsync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.async.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.async.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.async.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.async.isLimited);
|
||||
|
||||
console.log('\n=== Usage ===');
|
||||
console.log('Current period cost:
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithStreaming() {
|
||||
try {
|
||||
// Streaming für bestimmte Block-Ausgaben aktivieren
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { message: 'Count to five' },
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content'] // Format blockName.attribute verwenden
|
||||
});
|
||||
|
||||
console.log('Workflow-Ergebnis:', result);
|
||||
} catch (error) {
|
||||
console.error('Fehler:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", zwei"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**React Streaming Example:**
|
||||
|
||||
```typescript
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function StreamingWorkflow() {
|
||||
const [output, setOutput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const executeStreaming = async () => {
|
||||
setLoading(true);
|
||||
setOutput('');
|
||||
|
||||
// WICHTIG: Führen Sie diesen API-Aufruf von Ihrem Backend-Server aus, nicht vom Browser
|
||||
// Setzen Sie niemals Ihren API-Schlüssel im Client-seitigen Code frei
|
||||
const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.SIM_API_KEY! // Nur serverseitige Umgebungsvariable
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: 'Generate a story',
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content']
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.chunk) {
|
||||
setOutput(prev => prev + parsed.chunk);
|
||||
} else if (parsed.event === 'done') {
|
||||
console.log('Ausführung abgeschlossen:', parsed.metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ungültiges JSON überspringen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={executeStreaming} disabled={loading}>
|
||||
{loading ? 'Generiere...' : 'Streaming starten'}
|
||||
</button>
|
||||
<div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
<Steps>
|
||||
<Step title="Bei Sim anmelden">
|
||||
Navigieren Sie zu [Sim](https://sim.ai) und melden Sie sich bei Ihrem Konto an.
|
||||
<Step title="Log in to Sim">
|
||||
Navigate to [Sim](https://sim.ai) and log in to your account.
|
||||
</Step>
|
||||
<Step title="Öffnen Sie Ihren Workflow">
|
||||
Navigieren Sie zu dem Workflow, den Sie programmatisch ausführen möchten.
|
||||
<Step title="Open your workflow">
|
||||
Navigate to the workflow you want to execute programmatically.
|
||||
</Step>
|
||||
<Step title="Deployen Sie Ihren Workflow">
|
||||
Klicken Sie auf "Deploy", um Ihren Workflow zu deployen, falls dies noch nicht geschehen ist.
|
||||
<Step title="Deploy your workflow">
|
||||
Click on "Deploy" to deploy your workflow if it hasn't been deployed yet.
|
||||
</Step>
|
||||
<Step title="Erstellen oder wählen Sie einen API-Schlüssel">
|
||||
Wählen Sie während des Deployment-Prozesses einen API-Schlüssel aus oder erstellen Sie einen neuen.
|
||||
<Step title="Create or select an API key">
|
||||
During the deployment process, select or create an API key.
|
||||
</Step>
|
||||
<Step title="Kopieren Sie den API-Schlüssel">
|
||||
Kopieren Sie den API-Schlüssel zur Verwendung in Ihrer TypeScript/JavaScript-Anwendung.
|
||||
<Step title="Copy the API key">
|
||||
Copy the API key to use in your TypeScript/JavaScript application.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Callout type="warning">
|
||||
Halten Sie Ihren API-Schlüssel sicher und committen Sie ihn niemals in die Versionskontrolle. Verwenden Sie Umgebungsvariablen oder sicheres Konfigurationsmanagement.
|
||||
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
|
||||
</Callout>
|
||||
|
||||
## Anforderungen
|
||||
## Requirements
|
||||
|
||||
- Node.js 16+
|
||||
- TypeScript 5.0+ (für TypeScript-Projekte)
|
||||
- TypeScript 5.0+ (for TypeScript projects)
|
||||
|
||||
## TypeScript-Unterstützung
|
||||
## TypeScript Support
|
||||
|
||||
Das SDK ist in TypeScript geschrieben und bietet vollständige Typsicherheit:
|
||||
The SDK is written in TypeScript and provides full type safety:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
@@ -586,22 +969,22 @@ import {
|
||||
SimStudioError
|
||||
} from 'simstudio-ts-sdk';
|
||||
|
||||
// Type-safe client initialization
|
||||
// Typsichere Client-Initialisierung
|
||||
const client: SimStudioClient = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Type-safe workflow execution
|
||||
// Typsichere Workflow-Ausführung
|
||||
const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-id', {
|
||||
input: {
|
||||
message: 'Hello, TypeScript!'
|
||||
}
|
||||
});
|
||||
|
||||
// Type-safe status checking
|
||||
// Typsichere Statusprüfung
|
||||
const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id');
|
||||
```
|
||||
|
||||
## Lizenz
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -38,14 +38,92 @@ curl -X POST \
|
||||
|
||||
Erfolgreiche Antworten geben das serialisierte Ausführungsergebnis vom Executor zurück. Fehler zeigen Validierungs-, Authentifizierungs- oder Workflow-Fehler an.
|
||||
|
||||
## Ausgabe-Referenz
|
||||
## Streaming-Antworten
|
||||
|
||||
Aktivieren Sie Echtzeit-Streaming, um Workflow-Ausgaben zu erhalten, während sie zeichen-für-zeichen generiert werden. Dies ist nützlich, um KI-Antworten progressiv für Benutzer anzuzeigen.
|
||||
|
||||
### Anfrageparameter
|
||||
|
||||
Fügen Sie diese Parameter hinzu, um Streaming zu aktivieren:
|
||||
|
||||
- `stream` - Auf `true` setzen, um Server-Sent Events (SSE) Streaming zu aktivieren
|
||||
- `selectedOutputs` - Array von Block-Ausgaben zum Streamen (z.B. `["agent1.content"]`)
|
||||
|
||||
### Block-Ausgabeformat
|
||||
|
||||
Verwenden Sie das `blockName.attribute` Format, um anzugeben, welche Block-Ausgaben gestreamt werden sollen:
|
||||
- Format: `"blockName.attribute"` (z.B. Wenn Sie den Inhalt des Agent 1-Blocks streamen möchten, würden Sie `"agent1.content"` verwenden)
|
||||
- Blocknamen sind nicht case-sensitive und Leerzeichen werden ignoriert
|
||||
|
||||
### Beispielanfrage
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Count to five",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
### Antwortformat
|
||||
|
||||
Streaming-Antworten verwenden das Server-Sent Events (SSE) Format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", three"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
Jedes Ereignis enthält:
|
||||
- **Streaming-Chunks**: `{"blockId": "...", "chunk": "text"}` - Echtzeit-Text während er generiert wird
|
||||
- **Abschlussereignis**: `{"event": "done", ...}` - Ausführungsmetadaten und vollständige Ergebnisse
|
||||
- **Terminator**: `[DONE]` - Signalisiert das Ende des Streams
|
||||
|
||||
### Streaming mehrerer Blöcke
|
||||
|
||||
Wenn `selectedOutputs` mehrere Blöcke enthält, zeigt jeder Chunk an, welcher Block ihn erzeugt hat:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Process this request",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content", "agent2.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
Das Feld `blockId` in jedem Chunk ermöglicht es Ihnen, die Ausgabe zum richtigen UI-Element zu leiten:
|
||||
|
||||
```
|
||||
data: {"blockId":"agent1-uuid","chunk":"Processing..."}
|
||||
|
||||
data: {"blockId":"agent2-uuid","chunk":"Analyzing..."}
|
||||
|
||||
data: {"blockId":"agent1-uuid","chunk":" complete"}
|
||||
```
|
||||
|
||||
## Ausgabereferenz
|
||||
|
||||
| Referenz | Beschreibung |
|
||||
|-----------|-------------|
|
||||
| `<api.field>` | Im Eingabeformat definiertes Feld |
|
||||
| `<api.input>` | Gesamter strukturierter Anfragekörper |
|
||||
|
||||
Wenn kein Eingabeformat definiert ist, stellt der Executor das rohe JSON nur unter `<api.input>` bereit.
|
||||
Wenn kein Eingabeformat definiert ist, stellt der Executor das rohe JSON nur unter `<api.input>` zur Verfügung.
|
||||
|
||||
<Callout type="warning">
|
||||
Ein Workflow kann nur einen API-Trigger enthalten. Veröffentlichen Sie nach Änderungen eine neue Bereitstellung, damit der Endpunkt aktuell bleibt.
|
||||
|
||||
251
apps/docs/content/docs/en/blocks/guardrails.mdx
Normal file
251
apps/docs/content/docs/en/blocks/guardrails.mdx
Normal file
@@ -0,0 +1,251 @@
|
||||
---
|
||||
title: Guardrails
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Image } from '@/components/ui/image'
|
||||
import { Video } from '@/components/ui/video'
|
||||
|
||||
The Guardrails block validates and protects your AI workflows by checking content against multiple validation types. Ensure data quality, prevent hallucinations, detect PII, and enforce format requirements before content moves through your workflow.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/guardrails.png"
|
||||
alt="Guardrails Block"
|
||||
width={500}
|
||||
height={350}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Overview
|
||||
|
||||
The Guardrails block enables you to:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Validate JSON Structure</strong>: Ensure LLM outputs are valid JSON before parsing
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Match Regex Patterns</strong>: Verify content matches specific formats (emails, phone numbers, URLs, etc.)
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Detect Hallucinations</strong>: Use RAG + LLM scoring to validate AI outputs against knowledge base content
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Detect PII</strong>: Identify and optionally mask personally identifiable information across 40+ entity types
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Validation Types
|
||||
|
||||
### JSON Validation
|
||||
|
||||
Validates that content is properly formatted JSON. Perfect for ensuring structured LLM outputs can be safely parsed.
|
||||
|
||||
**Use Cases:**
|
||||
- Validate JSON responses from Agent blocks before parsing
|
||||
- Ensure API payloads are properly formatted
|
||||
- Check structured data integrity
|
||||
|
||||
**Output:**
|
||||
- `passed`: `true` if valid JSON, `false` otherwise
|
||||
- `error`: Error message if validation fails (e.g., "Invalid JSON: Unexpected token...")
|
||||
|
||||
### Regex Validation
|
||||
|
||||
Checks if content matches a specified regular expression pattern.
|
||||
|
||||
**Use Cases:**
|
||||
- Validate email addresses
|
||||
- Check phone number formats
|
||||
- Verify URLs or custom identifiers
|
||||
- Enforce specific text patterns
|
||||
|
||||
**Configuration:**
|
||||
- **Regex Pattern**: The regular expression to match against (e.g., `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` for emails)
|
||||
|
||||
**Output:**
|
||||
- `passed`: `true` if content matches pattern, `false` otherwise
|
||||
- `error`: Error message if validation fails
|
||||
|
||||
### Hallucination Detection
|
||||
|
||||
Uses Retrieval-Augmented Generation (RAG) with LLM scoring to detect when AI-generated content contradicts or isn't grounded in your knowledge base.
|
||||
|
||||
**How It Works:**
|
||||
1. Queries your knowledge base for relevant context
|
||||
2. Sends both the AI output and retrieved context to an LLM
|
||||
3. LLM assigns a confidence score (0-10 scale)
|
||||
- **0** = Full hallucination (completely ungrounded)
|
||||
- **10** = Fully grounded (completely supported by knowledge base)
|
||||
4. Validation passes if score ≥ threshold (default: 3)
|
||||
|
||||
**Configuration:**
|
||||
- **Knowledge Base**: Select from your existing knowledge bases
|
||||
- **Model**: Choose LLM for scoring (requires strong reasoning - GPT-4o, Claude 3.7 Sonnet recommended)
|
||||
- **API Key**: Authentication for selected LLM provider (auto-hidden for hosted/Ollama models)
|
||||
- **Confidence Threshold**: Minimum score to pass (0-10, default: 3)
|
||||
- **Top K** (Advanced): Number of knowledge base chunks to retrieve (default: 10)
|
||||
|
||||
**Output:**
|
||||
- `passed`: `true` if confidence score ≥ threshold
|
||||
- `score`: Confidence score (0-10)
|
||||
- `reasoning`: LLM's explanation for the score
|
||||
- `error`: Error message if validation fails
|
||||
|
||||
**Use Cases:**
|
||||
- Validate Agent responses against documentation
|
||||
- Ensure customer support answers are factually accurate
|
||||
- Verify generated content matches source material
|
||||
- Quality control for RAG applications
|
||||
|
||||
### PII Detection
|
||||
|
||||
Detects personally identifiable information using Microsoft Presidio. Supports 40+ entity types across multiple countries and languages.
|
||||
|
||||
<div className="mx-auto w-3/5 overflow-hidden rounded-lg">
|
||||
<Video src="guardrails.mp4" width={500} height={350} />
|
||||
</div>
|
||||
|
||||
**How It Works:**
|
||||
1. Scans content for PII entities using pattern matching and NLP
|
||||
2. Returns detected entities with locations and confidence scores
|
||||
3. Optionally masks detected PII in the output
|
||||
|
||||
**Configuration:**
|
||||
- **PII Types to Detect**: Select from grouped categories via modal selector
|
||||
- **Common**: Person name, Email, Phone, Credit card, IP address, etc.
|
||||
- **USA**: SSN, Driver's license, Passport, etc.
|
||||
- **UK**: NHS number, National insurance number
|
||||
- **Spain**: NIF, NIE, CIF
|
||||
- **Italy**: Fiscal code, Driver's license, VAT code
|
||||
- **Poland**: PESEL, NIP, REGON
|
||||
- **Singapore**: NRIC/FIN, UEN
|
||||
- **Australia**: ABN, ACN, TFN, Medicare
|
||||
- **India**: Aadhaar, PAN, Passport, Voter number
|
||||
- **Mode**:
|
||||
- **Detect**: Only identify PII (default)
|
||||
- **Mask**: Replace detected PII with masked values
|
||||
- **Language**: Detection language (default: English)
|
||||
|
||||
**Output:**
|
||||
- `passed`: `false` if any selected PII types are detected
|
||||
- `detectedEntities`: Array of detected PII with type, location, and confidence
|
||||
- `maskedText`: Content with PII masked (only if mode = "Mask")
|
||||
- `error`: Error message if validation fails
|
||||
|
||||
**Use Cases:**
|
||||
- Block content containing sensitive personal information
|
||||
- Mask PII before logging or storing data
|
||||
- Compliance with GDPR, HIPAA, and other privacy regulations
|
||||
- Sanitize user inputs before processing
|
||||
|
||||
## Configuration
|
||||
|
||||
### Content to Validate
|
||||
|
||||
The input content to validate. This typically comes from:
|
||||
- Agent block outputs: `<agent.content>`
|
||||
- Function block results: `<function.output>`
|
||||
- API responses: `<api.output>`
|
||||
- Any other block output
|
||||
|
||||
### Validation Type
|
||||
|
||||
Choose from four validation types:
|
||||
- **Valid JSON**: Check if content is properly formatted JSON
|
||||
- **Regex Match**: Verify content matches a regex pattern
|
||||
- **Hallucination Check**: Validate against knowledge base with LLM scoring
|
||||
- **PII Detection**: Detect and optionally mask personally identifiable information
|
||||
|
||||
## Outputs
|
||||
|
||||
All validation types return:
|
||||
|
||||
- **`<guardrails.passed>`**: Boolean indicating if validation passed
|
||||
- **`<guardrails.validationType>`**: The type of validation performed
|
||||
- **`<guardrails.input>`**: The original input that was validated
|
||||
- **`<guardrails.error>`**: Error message if validation failed (optional)
|
||||
|
||||
Additional outputs by type:
|
||||
|
||||
**Hallucination Check:**
|
||||
- **`<guardrails.score>`**: Confidence score (0-10)
|
||||
- **`<guardrails.reasoning>`**: LLM's explanation
|
||||
|
||||
**PII Detection:**
|
||||
- **`<guardrails.detectedEntities>`**: Array of detected PII entities
|
||||
- **`<guardrails.maskedText>`**: Content with PII masked (if mode = "Mask")
|
||||
|
||||
## Example Use Cases
|
||||
|
||||
### Validate JSON Before Parsing
|
||||
|
||||
<div className="mb-4 rounded-md border p-4">
|
||||
<h4 className="font-medium">Scenario: Ensure Agent output is valid JSON</h4>
|
||||
<ol className="list-decimal pl-5 text-sm">
|
||||
<li>Agent generates structured JSON response</li>
|
||||
<li>Guardrails validates JSON format</li>
|
||||
<li>Condition block checks `<guardrails.passed>`</li>
|
||||
<li>If passed → Parse and use data, If failed → Retry or handle error</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
### Prevent Hallucinations
|
||||
|
||||
<div className="mb-4 rounded-md border p-4">
|
||||
<h4 className="font-medium">Scenario: Validate customer support responses</h4>
|
||||
<ol className="list-decimal pl-5 text-sm">
|
||||
<li>Agent generates response to customer question</li>
|
||||
<li>Guardrails checks against support documentation knowledge base</li>
|
||||
<li>If confidence score ≥ 3 → Send response</li>
|
||||
<li>If confidence score \< 3 → Flag for human review</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
### Block PII in User Inputs
|
||||
|
||||
<div className="mb-4 rounded-md border p-4">
|
||||
<h4 className="font-medium">Scenario: Sanitize user-submitted content</h4>
|
||||
<ol className="list-decimal pl-5 text-sm">
|
||||
<li>User submits form with text content</li>
|
||||
<li>Guardrails detects PII (emails, phone numbers, SSN, etc.)</li>
|
||||
<li>If PII detected → Reject submission or mask sensitive data</li>
|
||||
<li>If no PII → Process normally</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto w-3/5 overflow-hidden rounded-lg">
|
||||
<Video src="guardrails-example.mp4" width={500} height={350} />
|
||||
</div>
|
||||
|
||||
### Validate Email Format
|
||||
|
||||
<div className="mb-4 rounded-md border p-4">
|
||||
<h4 className="font-medium">Scenario: Check email address format</h4>
|
||||
<ol className="list-decimal pl-5 text-sm">
|
||||
<li>Agent extracts email from text</li>
|
||||
<li>Guardrails validates with regex pattern</li>
|
||||
<li>If valid → Use email for notification</li>
|
||||
<li>If invalid → Request correction</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Chain with Condition blocks**: Use `<guardrails.passed>` to branch workflow logic based on validation results
|
||||
- **Use JSON validation before parsing**: Always validate JSON structure before attempting to parse LLM outputs
|
||||
- **Choose appropriate PII types**: Only select the PII entity types relevant to your use case for better performance
|
||||
- **Set reasonable confidence thresholds**: For hallucination detection, adjust threshold based on your accuracy requirements (higher = stricter)
|
||||
- **Use strong models for hallucination detection**: GPT-4o or Claude 3.7 Sonnet provide more accurate confidence scoring
|
||||
- **Mask PII for logging**: Use "Mask" mode when you need to log or store content that may contain PII
|
||||
- **Test regex patterns**: Validate your regex patterns thoroughly before deploying to production
|
||||
- **Monitor validation failures**: Track `<guardrails.error>` messages to identify common validation issues
|
||||
|
||||
<Callout type="info">
|
||||
Guardrails validation happens synchronously in your workflow. For hallucination detection, choose faster models (like GPT-4o-mini) if latency is critical.
|
||||
</Callout>
|
||||
|
||||
@@ -166,6 +166,38 @@ Different subscription plans have different usage limits:
|
||||
| **Team** | $500 (pooled) | 50 sync, 100 async |
|
||||
| **Enterprise** | Custom | Custom |
|
||||
|
||||
## Billing Model
|
||||
|
||||
Sim uses a **base subscription + overage** billing model:
|
||||
|
||||
### How It Works
|
||||
|
||||
**Pro Plan ($20/month):**
|
||||
- Monthly subscription includes $20 of usage
|
||||
- Usage under $20 → No additional charges
|
||||
- Usage over $20 → Pay the overage at month end
|
||||
- Example: $35 usage = $20 (subscription) + $15 (overage)
|
||||
|
||||
**Team Plan ($40/seat/month):**
|
||||
- Pooled usage across all team members
|
||||
- Overage calculated from total team usage
|
||||
- Organization owner receives one bill
|
||||
|
||||
**Enterprise Plans:**
|
||||
- Fixed monthly price, no overages
|
||||
- Custom usage limits per agreement
|
||||
|
||||
### Threshold Billing
|
||||
|
||||
When unbilled overage reaches $50, Sim automatically bills the full unbilled amount.
|
||||
|
||||
**Example:**
|
||||
- Day 10: $70 overage → Bill $70 immediately
|
||||
- Day 15: Additional $35 usage ($105 total) → Already billed, no action
|
||||
- Day 20: Another $50 usage ($155 total, $85 unbilled) → Bill $85 immediately
|
||||
|
||||
This spreads large overage charges throughout the month instead of one large bill at period end.
|
||||
|
||||
## Cost Management Best Practices
|
||||
|
||||
1. **Monitor Regularly**: Check your usage dashboard frequently to avoid surprises
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
The official Python SDK for Sim allows you to execute workflows programmatically from your Python applications using the official Python SDK.
|
||||
|
||||
<Callout type="info">
|
||||
The Python SDK supports Python 3.8+ and provides synchronous workflow execution. All workflow executions are currently synchronous.
|
||||
The Python SDK supports Python 3.8+ with async execution support, automatic rate limiting with exponential backoff, and usage tracking.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
@@ -74,8 +74,13 @@ result = client.execute_workflow(
|
||||
- `workflow_id` (str): The ID of the workflow to execute
|
||||
- `input_data` (dict, optional): Input data to pass to the workflow
|
||||
- `timeout` (float, optional): Timeout in seconds (default: 30.0)
|
||||
- `stream` (bool, optional): Enable streaming responses (default: False)
|
||||
- `selected_outputs` (list[str], optional): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`)
|
||||
- `async_execution` (bool, optional): Execute asynchronously (default: False)
|
||||
|
||||
**Returns:** `WorkflowExecutionResult`
|
||||
**Returns:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
When `async_execution=True`, returns immediately with a task ID for polling. Otherwise, waits for completion.
|
||||
|
||||
##### get_workflow_status()
|
||||
|
||||
@@ -107,28 +112,117 @@ if is_ready:
|
||||
|
||||
**Returns:** `bool`
|
||||
|
||||
##### execute_workflow_sync()
|
||||
##### get_job_status()
|
||||
|
||||
<Callout type="info">
|
||||
Currently, this method is identical to `execute_workflow()` since all executions are synchronous. This method is provided for future compatibility when asynchronous execution is added.
|
||||
</Callout>
|
||||
|
||||
Execute a workflow (currently synchronous, same as `execute_workflow()`).
|
||||
Get the status of an async job execution.
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
status = client.get_job_status("task-id-from-async-execution")
|
||||
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
|
||||
if status["status"] == "completed":
|
||||
print("Output:", status["output"])
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `task_id` (str): The task ID returned from async execution
|
||||
|
||||
**Returns:** `Dict[str, Any]`
|
||||
|
||||
**Response fields:**
|
||||
- `success` (bool): Whether the request was successful
|
||||
- `taskId` (str): The task ID
|
||||
- `status` (str): One of `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (dict): Contains `startedAt`, `completedAt`, and `duration`
|
||||
- `output` (any, optional): The workflow output (when completed)
|
||||
- `error` (any, optional): Error details (when failed)
|
||||
- `estimatedDuration` (int, optional): Estimated duration in milliseconds (when processing/queued)
|
||||
|
||||
##### execute_with_retry()
|
||||
|
||||
Execute a workflow with automatic retry on rate limit errors using exponential backoff.
|
||||
|
||||
```python
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
input_data={"message": "Hello"},
|
||||
timeout=30.0,
|
||||
max_retries=3, # Maximum number of retries
|
||||
initial_delay=1.0, # Initial delay in seconds
|
||||
max_delay=30.0, # Maximum delay in seconds
|
||||
backoff_multiplier=2.0 # Exponential backoff multiplier
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `workflow_id` (str): The ID of the workflow to execute
|
||||
- `input_data` (dict, optional): Input data to pass to the workflow
|
||||
- `timeout` (float): Timeout for the initial request in seconds
|
||||
- `timeout` (float, optional): Timeout in seconds
|
||||
- `stream` (bool, optional): Enable streaming responses
|
||||
- `selected_outputs` (list, optional): Block outputs to stream
|
||||
- `async_execution` (bool, optional): Execute asynchronously
|
||||
- `max_retries` (int, optional): Maximum number of retries (default: 3)
|
||||
- `initial_delay` (float, optional): Initial delay in seconds (default: 1.0)
|
||||
- `max_delay` (float, optional): Maximum delay in seconds (default: 30.0)
|
||||
- `backoff_multiplier` (float, optional): Backoff multiplier (default: 2.0)
|
||||
|
||||
**Returns:** `WorkflowExecutionResult`
|
||||
**Returns:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
The retry logic uses exponential backoff (1s → 2s → 4s → 8s...) with ±25% jitter to prevent thundering herd. If the API provides a `retry-after` header, it will be used instead.
|
||||
|
||||
##### get_rate_limit_info()
|
||||
|
||||
Get the current rate limit information from the last API response.
|
||||
|
||||
```python
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
print("Limit:", rate_limit_info.limit)
|
||||
print("Remaining:", rate_limit_info.remaining)
|
||||
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
|
||||
```
|
||||
|
||||
**Returns:** `RateLimitInfo | None`
|
||||
|
||||
##### get_usage_limits()
|
||||
|
||||
Get current usage limits and quota information for your account.
|
||||
|
||||
```python
|
||||
limits = client.get_usage_limits()
|
||||
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
|
||||
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
|
||||
print("Current period cost:", limits.usage["currentPeriodCost"])
|
||||
print("Plan:", limits.usage["plan"])
|
||||
```
|
||||
|
||||
**Returns:** `UsageLimits`
|
||||
|
||||
**Response structure:**
|
||||
```python
|
||||
{
|
||||
"success": bool,
|
||||
"rateLimit": {
|
||||
"sync": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"async": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"authType": str # 'api' or 'manual'
|
||||
},
|
||||
"usage": {
|
||||
"currentPeriodCost": float,
|
||||
"limit": float,
|
||||
"plan": str # e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### set_api_key()
|
||||
|
||||
@@ -170,6 +264,18 @@ class WorkflowExecutionResult:
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class AsyncExecutionResult:
|
||||
success: bool
|
||||
task_id: str
|
||||
status: str # 'queued'
|
||||
created_at: str
|
||||
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@@ -181,6 +287,27 @@ class WorkflowStatus:
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class RateLimitInfo:
|
||||
limit: int
|
||||
remaining: int
|
||||
reset: int
|
||||
retry_after: Optional[int] = None
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UsageLimits:
|
||||
success: bool
|
||||
rate_limit: Dict[str, Any]
|
||||
usage: Dict[str, Any]
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
@@ -191,6 +318,13 @@ class SimStudioError(Exception):
|
||||
self.status = status
|
||||
```
|
||||
|
||||
**Common error codes:**
|
||||
- `UNAUTHORIZED`: Invalid API key
|
||||
- `TIMEOUT`: Request timed out
|
||||
- `RATE_LIMIT_EXCEEDED`: Rate limit exceeded
|
||||
- `USAGE_LIMIT_EXCEEDED`: Usage limit exceeded
|
||||
- `EXECUTION_ERROR`: Workflow execution failed
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Workflow Execution
|
||||
@@ -214,7 +348,7 @@ class SimStudioError(Exception):
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
@@ -252,7 +386,7 @@ Handle different types of errors that may occur during workflow execution:
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
@@ -284,7 +418,7 @@ from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
@@ -298,7 +432,7 @@ Execute multiple workflows efficiently:
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
@@ -339,6 +473,230 @@ for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
### Async Workflow Execution
|
||||
|
||||
Execute workflows asynchronously for long-running tasks:
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_async():
|
||||
try:
|
||||
# Start async execution
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"data": "large dataset"},
|
||||
async_execution=True # Execute asynchronously
|
||||
)
|
||||
|
||||
# Check if result is an async execution
|
||||
if hasattr(result, 'task_id'):
|
||||
print(f"Task ID: {result.task_id}")
|
||||
print(f"Status endpoint: {result.links['status']}")
|
||||
|
||||
# Poll for completion
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
while status["status"] in ["queued", "processing"]:
|
||||
print(f"Current status: {status['status']}")
|
||||
time.sleep(2) # Wait 2 seconds
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
if status["status"] == "completed":
|
||||
print("Workflow completed!")
|
||||
print(f"Output: {status['output']}")
|
||||
print(f"Duration: {status['metadata']['duration']}")
|
||||
else:
|
||||
print(f"Workflow failed: {status['error']}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
execute_async()
|
||||
```
|
||||
|
||||
### Rate Limiting and Retry
|
||||
|
||||
Handle rate limits automatically with exponential backoff:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_retry_handling():
|
||||
try:
|
||||
# Automatically retries on rate limit
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"message": "Process this"},
|
||||
max_retries=5,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
backoff_multiplier=2.0
|
||||
)
|
||||
|
||||
print(f"Success: {result}")
|
||||
except SimStudioError as error:
|
||||
if error.code == "RATE_LIMIT_EXCEEDED":
|
||||
print("Rate limit exceeded after all retries")
|
||||
|
||||
# Check rate limit info
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
from datetime import datetime
|
||||
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
|
||||
print(f"Rate limit resets at: {reset_time}")
|
||||
|
||||
execute_with_retry_handling()
|
||||
```
|
||||
|
||||
### Usage Monitoring
|
||||
|
||||
Monitor your account usage and limits:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def check_usage():
|
||||
try:
|
||||
limits = client.get_usage_limits()
|
||||
|
||||
print("=== Rate Limits ===")
|
||||
print("Sync requests:")
|
||||
print(f" Limit: {limits.rate_limit['sync']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
|
||||
|
||||
print("\nAsync requests:")
|
||||
print(f" Limit: {limits.rate_limit['async']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
|
||||
print(f"Limit: ${limits.usage['limit']:.2f}")
|
||||
print(f"Plan: {limits.usage['plan']}")
|
||||
|
||||
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
|
||||
print(f"Usage: {percent_used:.1f}%")
|
||||
|
||||
if percent_used > 80:
|
||||
print("⚠️ Warning: You are approaching your usage limit!")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error checking usage: {error}")
|
||||
|
||||
check_usage()
|
||||
```
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_streaming():
|
||||
"""Execute workflow with streaming enabled."""
|
||||
try:
|
||||
# Enable streaming for specific block outputs
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Count to five"},
|
||||
stream=True,
|
||||
selected_outputs=["agent1.content"] # Use blockName.attribute format
|
||||
)
|
||||
|
||||
print("Workflow result:", result)
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
execute_with_streaming()
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Flask Streaming Example:**
|
||||
|
||||
```python
|
||||
from flask import Flask, Response, stream_with_context
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/stream-workflow')
|
||||
def stream_workflow():
|
||||
"""Stream workflow execution to the client."""
|
||||
|
||||
def generate():
|
||||
response = requests.post(
|
||||
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': os.getenv('SIM_API_KEY')
|
||||
},
|
||||
json={
|
||||
'message': 'Generate a story',
|
||||
'stream': True,
|
||||
'selectedOutputs': ['agent1.content']
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
data = decoded_line[6:] # Remove 'data: ' prefix
|
||||
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
if 'chunk' in parsed:
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
elif parsed.get('event') == 'done':
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
print("Execution complete:", parsed.get('metadata'))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Configure the client using environment variables:
|
||||
@@ -351,8 +709,8 @@ Configure the client using environment variables:
|
||||
|
||||
# Development configuration
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
api_key=os.getenv("SIM_API_KEY")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
@@ -362,13 +720,13 @@ Configure the client using environment variables:
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Production configuration with error handling
|
||||
api_key = os.getenv("SIMSTUDIO_API_KEY")
|
||||
api_key = os.getenv("SIM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("SIMSTUDIO_API_KEY environment variable is required")
|
||||
raise ValueError("SIM_API_KEY environment variable is required")
|
||||
|
||||
client = SimStudioClient(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
The official TypeScript/JavaScript SDK for Sim provides full type safety and supports both Node.js and browser environments, allowing you to execute workflows programmatically from your Node.js applications, web applications, and other JavaScript environments. All workflow executions are currently synchronous.
|
||||
The official TypeScript/JavaScript SDK for Sim provides full type safety and supports both Node.js and browser environments, allowing you to execute workflows programmatically from your Node.js applications, web applications, and other JavaScript environments.
|
||||
|
||||
<Callout type="info">
|
||||
The TypeScript SDK provides full type safety and supports both Node.js and browser environments. All workflow executions are currently synchronous.
|
||||
The TypeScript SDK provides full type safety, async execution support, automatic rate limiting with exponential backoff, and usage tracking.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
@@ -89,8 +89,13 @@ const result = await client.executeWorkflow('workflow-id', {
|
||||
- `options` (ExecutionOptions, optional):
|
||||
- `input` (any): Input data to pass to the workflow
|
||||
- `timeout` (number): Timeout in milliseconds (default: 30000)
|
||||
- `stream` (boolean): Enable streaming responses (default: false)
|
||||
- `selectedOutputs` (string[]): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`)
|
||||
- `async` (boolean): Execute asynchronously (default: false)
|
||||
|
||||
**Returns:** `Promise<WorkflowExecutionResult>`
|
||||
**Returns:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
When `async: true`, returns immediately with a task ID for polling. Otherwise, waits for completion.
|
||||
|
||||
##### getWorkflowStatus()
|
||||
|
||||
@@ -122,28 +127,116 @@ if (isReady) {
|
||||
|
||||
**Returns:** `Promise<boolean>`
|
||||
|
||||
##### executeWorkflowSync()
|
||||
##### getJobStatus()
|
||||
|
||||
<Callout type="info">
|
||||
Currently, this method is identical to `executeWorkflow()` since all executions are synchronous. This method is provided for future compatibility when asynchronous execution is added.
|
||||
</Callout>
|
||||
|
||||
Execute a workflow (currently synchronous, same as `executeWorkflow()`).
|
||||
Get the status of an async job execution.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWorkflowSync('workflow-id', {
|
||||
input: { data: 'some input' },
|
||||
timeout: 60000
|
||||
const status = await client.getJobStatus('task-id-from-async-execution');
|
||||
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
|
||||
if (status.status === 'completed') {
|
||||
console.log('Output:', status.output);
|
||||
}
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `taskId` (string): The task ID returned from async execution
|
||||
|
||||
**Returns:** `Promise<JobStatus>`
|
||||
|
||||
**Response fields:**
|
||||
- `success` (boolean): Whether the request was successful
|
||||
- `taskId` (string): The task ID
|
||||
- `status` (string): One of `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (object): Contains `startedAt`, `completedAt`, and `duration`
|
||||
- `output` (any, optional): The workflow output (when completed)
|
||||
- `error` (any, optional): Error details (when failed)
|
||||
- `estimatedDuration` (number, optional): Estimated duration in milliseconds (when processing/queued)
|
||||
|
||||
##### executeWithRetry()
|
||||
|
||||
Execute a workflow with automatic retry on rate limit errors using exponential backoff.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Hello' },
|
||||
timeout: 30000
|
||||
}, {
|
||||
maxRetries: 3, // Maximum number of retries
|
||||
initialDelay: 1000, // Initial delay in ms (1 second)
|
||||
maxDelay: 30000, // Maximum delay in ms (30 seconds)
|
||||
backoffMultiplier: 2 // Exponential backoff multiplier
|
||||
});
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `workflowId` (string): The ID of the workflow to execute
|
||||
- `options` (ExecutionOptions, optional):
|
||||
- `input` (any): Input data to pass to the workflow
|
||||
- `timeout` (number): Timeout for the initial request in milliseconds
|
||||
- `options` (ExecutionOptions, optional): Same as `executeWorkflow()`
|
||||
- `retryOptions` (RetryOptions, optional):
|
||||
- `maxRetries` (number): Maximum number of retries (default: 3)
|
||||
- `initialDelay` (number): Initial delay in ms (default: 1000)
|
||||
- `maxDelay` (number): Maximum delay in ms (default: 30000)
|
||||
- `backoffMultiplier` (number): Backoff multiplier (default: 2)
|
||||
|
||||
**Returns:** `Promise<WorkflowExecutionResult>`
|
||||
**Returns:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
The retry logic uses exponential backoff (1s → 2s → 4s → 8s...) with ±25% jitter to prevent thundering herd. If the API provides a `retry-after` header, it will be used instead.
|
||||
|
||||
##### getRateLimitInfo()
|
||||
|
||||
Get the current rate limit information from the last API response.
|
||||
|
||||
```typescript
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Limit:', rateLimitInfo.limit);
|
||||
console.log('Remaining:', rateLimitInfo.remaining);
|
||||
console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:** `RateLimitInfo | null`
|
||||
|
||||
##### getUsageLimits()
|
||||
|
||||
Get current usage limits and quota information for your account.
|
||||
|
||||
```typescript
|
||||
const limits = await client.getUsageLimits();
|
||||
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
|
||||
console.log('Current period cost:', limits.usage.currentPeriodCost);
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
```
|
||||
|
||||
**Returns:** `Promise<UsageLimits>`
|
||||
|
||||
**Response structure:**
|
||||
```typescript
|
||||
{
|
||||
success: boolean
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
authType: string // 'api' or 'manual'
|
||||
}
|
||||
usage: {
|
||||
currentPeriodCost: number
|
||||
limit: number
|
||||
plan: string // e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### setApiKey()
|
||||
|
||||
@@ -181,6 +274,20 @@ interface WorkflowExecutionResult {
|
||||
}
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```typescript
|
||||
interface AsyncExecutionResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
status: 'queued';
|
||||
createdAt: string;
|
||||
links: {
|
||||
status: string; // e.g., "/api/jobs/{taskId}"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```typescript
|
||||
@@ -192,6 +299,45 @@ interface WorkflowStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```typescript
|
||||
interface RateLimitInfo {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```typescript
|
||||
interface UsageLimits {
|
||||
success: boolean;
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
async: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
authType: string;
|
||||
};
|
||||
usage: {
|
||||
currentPeriodCost: number;
|
||||
limit: number;
|
||||
plan: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```typescript
|
||||
@@ -201,6 +347,13 @@ class SimStudioError extends Error {
|
||||
}
|
||||
```
|
||||
|
||||
**Common error codes:**
|
||||
- `UNAUTHORIZED`: Invalid API key
|
||||
- `TIMEOUT`: Request timed out
|
||||
- `RATE_LIMIT_EXCEEDED`: Rate limit exceeded
|
||||
- `USAGE_LIMIT_EXCEEDED`: Usage limit exceeded
|
||||
- `EXECUTION_ERROR`: Workflow execution failed
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Workflow Execution
|
||||
@@ -224,7 +377,7 @@ class SimStudioError extends Error {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function runWorkflow() {
|
||||
@@ -265,7 +418,7 @@ Handle different types of errors that may occur during workflow execution:
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithErrorHandling() {
|
||||
@@ -308,14 +461,14 @@ Configure the client using environment variables:
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Development configuration
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL // optional
|
||||
baseUrl: process.env.SIM_BASE_URL // optional
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
@@ -324,14 +477,14 @@ Configure the client using environment variables:
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Production configuration with validation
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://sim.ai'
|
||||
baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
@@ -347,7 +500,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const app = express();
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
@@ -389,7 +542,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -440,14 +593,91 @@ async function executeClientSideWorkflow() {
|
||||
});
|
||||
|
||||
console.log('Workflow result:', result);
|
||||
|
||||
|
||||
// Update UI with result
|
||||
document.getElementById('result')!.textContent =
|
||||
document.getElementById('result')!.textContent =
|
||||
JSON.stringify(result.output, null, 2);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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:
|
||||
```typescript
|
||||
{
|
||||
type: 'file',
|
||||
data: 'data:mime/type;base64,base64data',
|
||||
name: 'filename',
|
||||
mime: 'mime/type'
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can manually provide files using the URL format:
|
||||
```typescript
|
||||
{
|
||||
type: 'url',
|
||||
data: 'https://example.com/file.pdf',
|
||||
name: 'file.pdf',
|
||||
mime: 'application/pdf'
|
||||
}
|
||||
```
|
||||
|
||||
<Tabs items={['Browser', 'Node.js']}>
|
||||
<Tab value="Browser">
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIM_API_KEY!
|
||||
});
|
||||
|
||||
// From file input
|
||||
async function handleFileUpload(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const files = Array.from(input.files || []);
|
||||
|
||||
// Include files under the field name from your API trigger's input format
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: {
|
||||
documents: files, // Must match your workflow's "files" field name
|
||||
instructions: 'Analyze these documents'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Result:', result);
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="Node.js">
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
import fs from 'fs';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Read file and create File object
|
||||
const fileBuffer = fs.readFileSync('./document.pdf');
|
||||
const file = new File([fileBuffer], 'document.pdf', {
|
||||
type: 'application/pdf'
|
||||
});
|
||||
|
||||
// Include files under the field name from your API trigger's input format
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: {
|
||||
documents: [file], // Must match your workflow's "files" field name
|
||||
query: 'Summarize this document'
|
||||
}
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
// Attach to button click
|
||||
document.getElementById('executeBtn')?.addEventListener('click', executeClientSideWorkflow);
|
||||
@@ -466,7 +696,7 @@ import { useState, useCallback } from 'react';
|
||||
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
interface UseWorkflowResult {
|
||||
@@ -522,7 +752,7 @@ function WorkflowComponent() {
|
||||
<button onClick={handleExecute} disabled={loading}>
|
||||
{loading ? 'Executing...' : 'Execute Workflow'}
|
||||
</button>
|
||||
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{result && (
|
||||
<div>
|
||||
@@ -535,6 +765,251 @@ function WorkflowComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
### Async Workflow Execution
|
||||
|
||||
Execute workflows asynchronously for long-running tasks:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeAsync() {
|
||||
try {
|
||||
// Start async execution
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { data: 'large dataset' },
|
||||
async: true // Execute asynchronously
|
||||
});
|
||||
|
||||
// Check if result is an async execution
|
||||
if ('taskId' in result) {
|
||||
console.log('Task ID:', result.taskId);
|
||||
console.log('Status endpoint:', result.links.status);
|
||||
|
||||
// Poll for completion
|
||||
let status = await client.getJobStatus(result.taskId);
|
||||
|
||||
while (status.status === 'queued' || status.status === 'processing') {
|
||||
console.log('Current status:', status.status);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||
status = await client.getJobStatus(result.taskId);
|
||||
}
|
||||
|
||||
if (status.status === 'completed') {
|
||||
console.log('Workflow completed!');
|
||||
console.log('Output:', status.output);
|
||||
console.log('Duration:', status.metadata.duration);
|
||||
} else {
|
||||
console.error('Workflow failed:', status.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
executeAsync();
|
||||
```
|
||||
|
||||
### Rate Limiting and Retry
|
||||
|
||||
Handle rate limits automatically with exponential backoff:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithRetryHandling() {
|
||||
try {
|
||||
// Automatically retries on rate limit
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Process this' }
|
||||
}, {
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 60000,
|
||||
backoffMultiplier: 2
|
||||
});
|
||||
|
||||
console.log('Success:', result);
|
||||
} catch (error) {
|
||||
if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
console.error('Rate limit exceeded after all retries');
|
||||
|
||||
// Check rate limit info
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Monitoring
|
||||
|
||||
Monitor your account usage and limits:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function checkUsage() {
|
||||
try {
|
||||
const limits = await client.getUsageLimits();
|
||||
|
||||
console.log('=== Rate Limits ===');
|
||||
console.log('Sync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.sync.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.sync.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.sync.isLimited);
|
||||
|
||||
console.log('\nAsync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.async.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.async.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.async.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.async.isLimited);
|
||||
|
||||
console.log('\n=== Usage ===');
|
||||
console.log('Current period cost: $' + limits.usage.currentPeriodCost.toFixed(2));
|
||||
console.log('Limit: $' + limits.usage.limit.toFixed(2));
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
|
||||
const percentUsed = (limits.usage.currentPeriodCost / limits.usage.limit) * 100;
|
||||
console.log('Usage: ' + percentUsed.toFixed(1) + '%');
|
||||
|
||||
if (percentUsed > 80) {
|
||||
console.warn('⚠️ Warning: You are approaching your usage limit!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking usage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
checkUsage();
|
||||
```
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithStreaming() {
|
||||
try {
|
||||
// Enable streaming for specific block outputs
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { message: 'Count to five' },
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content'] // Use blockName.attribute format
|
||||
});
|
||||
|
||||
console.log('Workflow result:', result);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**React Streaming Example:**
|
||||
|
||||
```typescript
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function StreamingWorkflow() {
|
||||
const [output, setOutput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const executeStreaming = async () => {
|
||||
setLoading(true);
|
||||
setOutput('');
|
||||
|
||||
// IMPORTANT: Make this API call from your backend server, not the browser
|
||||
// Never expose your API key in client-side code
|
||||
const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.SIM_API_KEY! // Server-side environment variable only
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: 'Generate a story',
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content']
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.chunk) {
|
||||
setOutput(prev => prev + parsed.chunk);
|
||||
} else if (parsed.event === 'done') {
|
||||
console.log('Execution complete:', parsed.metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={executeStreaming} disabled={loading}>
|
||||
{loading ? 'Generating...' : 'Start Streaming'}
|
||||
</button>
|
||||
<div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
<Steps>
|
||||
@@ -578,7 +1053,7 @@ import {
|
||||
|
||||
// Type-safe client initialization
|
||||
const client: SimStudioClient = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Type-safe workflow execution
|
||||
@@ -594,4 +1069,4 @@ const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id');
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
Apache-2.0
|
||||
|
||||
@@ -57,7 +57,7 @@ In Sim, the Airtable integration enables your agents to interact with your Airta
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrates Airtable into the workflow. Can create, get, list, or update Airtable records. Requires OAuth. Can be used in trigger mode to trigger a workflow when an update is made to an Airtable table.
|
||||
Integrates Airtable into the workflow. Can create, get, list, or update Airtable records. Can be used in trigger mode to trigger a workflow when an update is made to an Airtable table.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ In Sim, the BrowserUse integration allows your agents to interact with the web a
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Browser Use into the workflow. Can navigate the web and perform actions as if a real user was interacting with the browser. Requires API Key.
|
||||
Integrate Browser Use into the workflow. Can navigate the web and perform actions as if a real user was interacting with the browser.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ In Sim, the Clay integration allows your agents to push structured data into Cla
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Clay into the workflow. Can populate a table with data. Requires an API Key.
|
||||
Integrate Clay into the workflow. Can populate a table with data.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ In Sim, the Confluence integration enables your agents to access and leverage yo
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Confluence into the workflow. Can read and update a page. Requires OAuth.
|
||||
Integrate Confluence into the workflow. Can read and update a page.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ Discord components in Sim use efficient lazy loading, only fetching data when ne
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Discord into the workflow. Can send and get messages, get server information, and get a user’s information. Requires bot API key.
|
||||
Integrate Discord into the workflow. Can send and get messages, get server information, and get a user’s information.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ In Sim, the ElevenLabs integration enables your agents to convert text to lifeli
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate ElevenLabs into the workflow. Can convert text to speech. Requires API key.
|
||||
Integrate ElevenLabs into the workflow. Can convert text to speech.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ In Sim, the Exa integration allows your agents to search the web for information
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Exa into the workflow. Can search, get contents, find similar links, answer a question, and perform research. Requires API Key.
|
||||
Integrate Exa into the workflow. Can search, get contents, find similar links, answer a question, and perform research.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ This allows your agents to gather information from websites, extract structured
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Firecrawl into the workflow. Can search, scrape, or crawl websites. Requires API Key.
|
||||
Integrate Firecrawl into the workflow. Can search, scrape, or crawl websites.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ In Sim, the GitHub integration enables your agents to interact directly with Git
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Github into the workflow. Can get get PR details, create PR comment, get repository info, and get latest commit. Requires github token API Key. Can be used in trigger mode to trigger a workflow when a PR is created, commented on, or a commit is pushed.
|
||||
Integrate Github into the workflow. Can get get PR details, create PR comment, get repository info, and get latest commit. Can be used in trigger mode to trigger a workflow when a PR is created, commented on, or a commit is pushed.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ In Sim, the Gmail integration enables your agents to send, read, and search emai
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Gmail into the workflow. Can send, read, and search emails. Requires OAuth. Can be used in trigger mode to trigger a workflow when a new email is received.
|
||||
Integrate Gmail into the workflow. Can send, read, and search emails. Can be used in trigger mode to trigger a workflow when a new email is received.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ In Sim, the Google Calendar integration enables your agents to programmatically
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Calendar into the workflow. Can create, read, update, and list calendar events. Requires OAuth.
|
||||
Integrate Google Calendar into the workflow. Can create, read, update, and list calendar events.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ In Sim, the Google Docs integration enables your agents to interact directly wit
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Docs into the workflow. Can read, write, and create documents. Requires OAuth.
|
||||
Integrate Google Docs into the workflow. Can read, write, and create documents.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ In Sim, the Google Drive integration enables your agents to interact directly wi
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Drive into the workflow. Can create, upload, and list files. Requires OAuth.
|
||||
Integrate Google Drive into the workflow. Can create, upload, and list files.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,9 +69,6 @@ Integrate Google Forms into your workflow. Provide a Form ID to list responses,
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| formId | string | Yes | The ID of the Google Form |
|
||||
| responseId | string | No | If provided, returns this specific response |
|
||||
| pageSize | number | No | Max responses to return (service may return fewer). Defaults to 5000 |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ In Sim, the Google Search integration enables your agents to search the web prog
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Search into the workflow. Can search the web. Requires API Key.
|
||||
Integrate Google Search into the workflow. Can search the web.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ In Sim, the Google Sheets integration enables your agents to interact directly w
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Sheets into the workflow. Can read, write, append, and update data. Requires OAuth.
|
||||
Integrate Google Sheets into the workflow. Can read, write, append, and update data.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ In Sim, the HuggingFace integration enables your agents to programmatically gene
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Hugging Face into the workflow. Can generate completions using the Hugging Face Inference API. Requires API Key.
|
||||
Integrate Hugging Face into the workflow. Can generate completions using the Hugging Face Inference API.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ In Sim, the Hunter.io integration enables your agents to programmatically search
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Hunter into the workflow. Can search domains, find email addresses, verify email addresses, discover companies, find companies, and count email addresses. Requires API Key.
|
||||
Integrate Hunter into the workflow. Can search domains, find email addresses, verify email addresses, discover companies, find companies, and count email addresses.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ In Sim, the DALL-E integration enables your agents to generate images programmat
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Image Generator into the workflow. Can generate images using DALL-E 3 or GPT Image. Requires API Key.
|
||||
Integrate Image Generator into the workflow. Can generate images using DALL-E 3 or GPT Image.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ This integration is particularly valuable for building agents that need to gathe
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Jina into the workflow. Extracts content from websites. Requires API Key.
|
||||
Integrate Jina into the workflow. Extracts content from websites.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ In Sim, the Jira integration allows your agents to seamlessly interact with your
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Jira into the workflow. Can read, write, and update issues. Requires OAuth.
|
||||
Integrate Jira into the workflow. Can read, write, and update issues.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
color="#5E6AD2"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
fill='currentColor'
|
||||
|
||||
|
||||
viewBox='0 0 100 100'
|
||||
>
|
||||
<path
|
||||
@@ -39,7 +42,7 @@ In Sim, the Linear integration allows your agents to seamlessly interact with yo
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Linear into the workflow. Can read and create issues. Requires OAuth.
|
||||
Integrate Linear into the workflow. Can read and create issues.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ To implement Linkup in your agent, simply add the tool to your agent's configura
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Linkup into the workflow. Can search the web. Requires API Key.
|
||||
Integrate Linkup into the workflow. Can search the web.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ In Sim, the Mem0 integration enables your agents to maintain persistent memory a
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Mem0 into the workflow. Can add, search, and retrieve memories. Requires API Key.
|
||||
Integrate Mem0 into the workflow. Can add, search, and retrieve memories.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"google_forms",
|
||||
"google_search",
|
||||
"google_sheets",
|
||||
"google_vault",
|
||||
"huggingface",
|
||||
"hunter",
|
||||
"image_generator",
|
||||
|
||||
@@ -94,7 +94,7 @@ In Sim, the Microsoft Excel integration provides seamless access to spreadsheet
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Microsoft Excel into the workflow. Can read, write, update, and add to table. Requires OAuth.
|
||||
Integrate Microsoft Excel into the workflow. Can read, write, update, and add to table.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ In Sim, the Microsoft Planner integration allows your agents to programmatically
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Microsoft Planner into the workflow. Can read and create tasks. Requires OAuth.
|
||||
Integrate Microsoft Planner into the workflow. Can read and create tasks.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ In Sim, the Microsoft Teams integration enables your agents to interact directly
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Microsoft Teams into the workflow. Can read and write chat messages, and read and write channel messages. Requires OAuth. Can be used in trigger mode to trigger a workflow when a message is sent to a chat or channel.
|
||||
Integrate Microsoft Teams into the workflow. Can read and write chat messages, and read and write channel messages. Can be used in trigger mode to trigger a workflow when a message is sent to a chat or channel.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ The Mistral Parse tool is particularly useful for scenarios where your agents ne
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Mistral Parse into the workflow. Can extract text from uploaded PDF documents, or from a URL. Requires API Key.
|
||||
Integrate Mistral Parse into the workflow. Can extract text from uploaded PDF documents, or from a URL.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ This integration bridges the gap between your AI workflows and your knowledge ba
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate with Notion into the workflow. Can read page, read database, create page, create database, append content, query database, and search workspace. Requires OAuth.
|
||||
Integrate with Notion into the workflow. Can read page, read database, create page, create database, append content, query database, and search workspace.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ In Sim, the OneDrive integration enables your agents to directly interact with y
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate OneDrive into the workflow. Can create, upload, and list files. Requires OAuth.
|
||||
Integrate OneDrive into the workflow. Can create, upload, and list files.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ In Sim, the OpenAI integration enables your agents to leverage these powerful AI
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Embeddings into the workflow. Can generate embeddings from text. Requires API Key.
|
||||
Integrate Embeddings into the workflow. Can generate embeddings from text.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ In Sim, the Microsoft Outlook integration enables your agents to interact direct
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Outlook into the workflow. Can read, draft, and send email messages. Requires OAuth. Can be used in trigger mode to trigger a workflow when a new email is received.
|
||||
Integrate Outlook into the workflow. Can read, draft, and send email messages. Can be used in trigger mode to trigger a workflow when a new email is received.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ In Sim, the Parallel AI integration empowers your agents to perform web searches
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Parallel AI into the workflow. Can search the web. Requires API Key.
|
||||
Integrate Parallel AI into the workflow. Can search the web.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ In Sim, the Perplexity integration enables your agents to leverage these powerfu
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Perplexity into the workflow. Can generate completions using Perplexity AI chat models. Requires API Key.
|
||||
Integrate Perplexity into the workflow. Can generate completions using Perplexity AI chat models.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ In Sim, the Pinecone integration enables your agents to leverage vector search c
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Pinecone into the workflow. Can generate embeddings, upsert text, search with text, fetch vectors, and search with vectors. Requires API Key.
|
||||
Integrate Pinecone into the workflow. Can generate embeddings, upsert text, search with text, fetch vectors, and search with vectors.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ This integration allows your agents to leverage powerful vector search and manag
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Qdrant into the workflow. Can upsert, search, and fetch points. Requires API Key.
|
||||
Integrate Qdrant into the workflow. Can upsert, search, and fetch points.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ These operations let your agents access and analyze Reddit content as part of yo
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Reddit into the workflow. Can get posts and comments from a subreddit. Requires OAuth.
|
||||
Integrate Reddit into the workflow. Can get posts and comments from a subreddit.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="schedule"
|
||||
color="#7B68EE"
|
||||
color="#6366F1"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ In Sim, the Serper integration enables your agents to leverage the power of web
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Serper into the workflow. Can search the web. Requires API Key.
|
||||
Integrate Serper into the workflow. Can search the web.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ This allows for powerful automation scenarios such as sending notifications, ale
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Slack into the workflow. Can send messages, create canvases, and read messages. Requires OAuth. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.
|
||||
Integrate Slack into the workflow. Can send messages, create canvases, and read messages. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ In Sim, the Stagehand integration enables your agents to extract structured data
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Stagehand into the workflow. Can extract structured data from webpages. Requires API Key.
|
||||
Integrate Stagehand into the workflow. Can extract structured data from webpages.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ In Sim, the Stagehand integration enables your agents to seamlessly interact wit
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Stagehand Agent into the workflow. Can navigate the web and perform tasks. Requires API Key.
|
||||
Integrate Stagehand Agent into the workflow. Can navigate the web and perform tasks.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ In Sim, the Vision integration enables your agents to analyze images with vision
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Vision into the workflow. Can analyze images with vision models. Requires API Key.
|
||||
Integrate Vision into the workflow. Can analyze images with vision models.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ In Sim, the Wealthbox integration enables your agents to seamlessly interact wit
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Wealthbox into the workflow. Can read and write notes, read and write contacts, and read and write tasks. Requires OAuth.
|
||||
Integrate Wealthbox into the workflow. Can read and write notes, read and write contacts, and read and write tasks.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ In Sim, the X integration enables sophisticated social media automation scenario
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate X into the workflow. Can post a new tweet, get tweet details, search tweets, and get user profile. Requires OAuth.
|
||||
Integrate X into the workflow. Can post a new tweet, get tweet details, search tweets, and get user profile.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ In Sim, the YouTube integration enables your agents to programmatically search a
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate YouTube into the workflow. Can search for videos. Requires API Key.
|
||||
Integrate YouTube into the workflow. Can search for videos.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,9 +22,17 @@ The API trigger exposes your workflow as a secure HTTP endpoint. Send JSON data
|
||||
/>
|
||||
</div>
|
||||
|
||||
Add an **Input Format** field for each parameter. Runtime output keys mirror the schema and are also available under `<api.input>`.
|
||||
Add an **Input Format** field for each parameter. Supported types:
|
||||
|
||||
Manual runs in the editor use the `value` column so you can test without sending a request. During execution the resolver populates both `<api.userId>` and `<api.input.userId>`.
|
||||
- **string** - Text values
|
||||
- **number** - Numeric values
|
||||
- **boolean** - True/false values
|
||||
- **json** - JSON objects
|
||||
- **files** - File uploads (access via `<api.fieldName[0].url>`, `<api.fieldName[0].name>`, etc.)
|
||||
|
||||
Runtime output keys mirror the schema and are available under `<api.input>`.
|
||||
|
||||
Manual runs in the editor use the `value` column so you can test without sending a request. During execution the resolver populates both `<api.fieldName>` and `<api.input.fieldName>`.
|
||||
|
||||
## Request Example
|
||||
|
||||
@@ -38,6 +46,84 @@ curl -X POST \
|
||||
|
||||
Successful responses return the serialized execution result from the Executor. Errors surface validation, auth, or workflow failures.
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
Enable real-time streaming to receive workflow output as it's generated, character-by-character. This is useful for displaying AI responses progressively to users.
|
||||
|
||||
### Request Parameters
|
||||
|
||||
Add these parameters to enable streaming:
|
||||
|
||||
- `stream` - Set to `true` to enable Server-Sent Events (SSE) streaming
|
||||
- `selectedOutputs` - Array of block outputs to stream (e.g., `["agent1.content"]`)
|
||||
|
||||
### Block Output Format
|
||||
|
||||
Use the `blockName.attribute` format to specify which block outputs to stream:
|
||||
- Format: `"blockName.attribute"` (e.g., If you want to stream the content of the Agent 1 block, you would use `"agent1.content"`)
|
||||
- Block names are case-insensitive and spaces are ignored
|
||||
|
||||
### Example Request
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Count to five",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
### Response Format
|
||||
|
||||
Streaming responses use Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", three"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
Each event includes:
|
||||
- **Streaming chunks**: `{"blockId": "...", "chunk": "text"}` - Real-time text as it's generated
|
||||
- **Final event**: `{"event": "done", ...}` - Execution metadata and complete results
|
||||
- **Terminator**: `[DONE]` - Signals end of stream
|
||||
|
||||
### Multiple Block Streaming
|
||||
|
||||
When `selectedOutputs` includes multiple blocks, each chunk indicates which block produced it:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Process this request",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content", "agent2.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
The `blockId` field in each chunk lets you route output to the correct UI element:
|
||||
|
||||
```
|
||||
data: {"blockId":"agent1-uuid","chunk":"Processing..."}
|
||||
|
||||
data: {"blockId":"agent2-uuid","chunk":"Analyzing..."}
|
||||
|
||||
data: {"blockId":"agent1-uuid","chunk":" complete"}
|
||||
```
|
||||
|
||||
## Output Reference
|
||||
|
||||
| Reference | Description |
|
||||
@@ -45,6 +131,53 @@ Successful responses return the serialized execution result from the Executor. E
|
||||
| `<api.field>` | Field defined in the Input Format |
|
||||
| `<api.input>` | Entire structured request body |
|
||||
|
||||
### File Upload Format
|
||||
|
||||
The API accepts files in two formats:
|
||||
|
||||
**1. Base64-encoded files** (recommended for SDKs):
|
||||
```json
|
||||
{
|
||||
"documents": [{
|
||||
"type": "file",
|
||||
"data": "data:application/pdf;base64,JVBERi0xLjQK...",
|
||||
"name": "document.pdf",
|
||||
"mime": "application/pdf"
|
||||
}]
|
||||
}
|
||||
```
|
||||
- Maximum file size: 20MB per file
|
||||
- Files are uploaded to cloud storage and converted to UserFile objects with all properties
|
||||
|
||||
**2. Direct URL references**:
|
||||
```json
|
||||
{
|
||||
"documents": [{
|
||||
"type": "url",
|
||||
"data": "https://example.com/document.pdf",
|
||||
"name": "document.pdf",
|
||||
"mime": "application/pdf"
|
||||
}]
|
||||
}
|
||||
```
|
||||
- File is not uploaded, URL is passed through directly
|
||||
- Useful for referencing existing files
|
||||
|
||||
### File Properties
|
||||
|
||||
For files, access all properties:
|
||||
|
||||
| Property | Description | Type |
|
||||
|----------|-------------|------|
|
||||
| `<api.fieldName[0].url>` | Signed download URL | string |
|
||||
| `<api.fieldName[0].name>` | Original filename | string |
|
||||
| `<api.fieldName[0].size>` | File size in bytes | number |
|
||||
| `<api.fieldName[0].type>` | MIME type | string |
|
||||
| `<api.fieldName[0].uploadedAt>` | Upload timestamp (ISO 8601) | string |
|
||||
| `<api.fieldName[0].expiresAt>` | URL expiry timestamp (ISO 8601) | string |
|
||||
|
||||
For URL-referenced files, the same properties are available except `uploadedAt` and `expiresAt` since the file is not uploaded to our storage.
|
||||
|
||||
If no Input Format is defined, the executor exposes the raw JSON at `<api.input>` only.
|
||||
|
||||
<Callout type="warning">
|
||||
|
||||
@@ -24,13 +24,24 @@ The Chat trigger creates a conversational interface for your workflow. Deploy yo
|
||||
|
||||
The trigger writes three fields that downstream blocks can reference:
|
||||
|
||||
| Reference | Description |
|
||||
|-----------|-------------|
|
||||
| `<chat.input>` | Latest user message |
|
||||
| `<chat.conversationId>` | Conversation thread ID |
|
||||
| `<chat.files>` | Optional uploaded files |
|
||||
| Reference | Description | Type |
|
||||
|-----------|-------------|------|
|
||||
| `<chat.input>` | Latest user message | string |
|
||||
| `<chat.conversationId>` | Conversation thread ID | string |
|
||||
| `<chat.files>` | Optional uploaded files | files array |
|
||||
|
||||
Files include `name`, `mimeType`, and a signed download `url`.
|
||||
### File Properties
|
||||
|
||||
Access individual file properties using array indexing:
|
||||
|
||||
| Property | Description | Type |
|
||||
|----------|-------------|------|
|
||||
| `<chat.files[0].url>` | Signed download URL | string |
|
||||
| `<chat.files[0].name>` | Original filename | string |
|
||||
| `<chat.files[0].size>` | File size in bytes | number |
|
||||
| `<chat.files[0].type>` | MIME type | string |
|
||||
| `<chat.files[0].uploadedAt>` | Upload timestamp (ISO 8601) | string |
|
||||
| `<chat.files[0].expiresAt>` | URL expiry timestamp (ISO 8601) | string |
|
||||
|
||||
## Usage Notes
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
El SDK oficial de Python para Sim te permite ejecutar flujos de trabajo programáticamente desde tus aplicaciones Python utilizando el SDK oficial de Python.
|
||||
|
||||
<Callout type="info">
|
||||
El SDK de Python es compatible con Python 3.8+ y proporciona ejecución sincrónica de flujos de trabajo. Todas las ejecuciones de flujos de trabajo son actualmente sincrónicas.
|
||||
El SDK de Python es compatible con Python 3.8+ con soporte para ejecución asíncrona, limitación automática de velocidad con retroceso exponencial y seguimiento de uso.
|
||||
</Callout>
|
||||
|
||||
## Instalación
|
||||
@@ -74,12 +74,17 @@ result = client.execute_workflow(
|
||||
- `workflow_id` (str): El ID del flujo de trabajo a ejecutar
|
||||
- `input_data` (dict, opcional): Datos de entrada para pasar al flujo de trabajo
|
||||
- `timeout` (float, opcional): Tiempo de espera en segundos (predeterminado: 30.0)
|
||||
- `stream` (bool, opcional): Habilitar respuestas en streaming (predeterminado: False)
|
||||
- `selected_outputs` (list[str], opcional): Salidas de bloque para transmitir en formato `blockName.attribute` (p. ej., `["agent1.content"]`)
|
||||
- `async_execution` (bool, opcional): Ejecutar de forma asíncrona (predeterminado: False)
|
||||
|
||||
**Devuelve:** `WorkflowExecutionResult`
|
||||
**Devuelve:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
Cuando `async_execution=True`, devuelve inmediatamente un ID de tarea para sondeo. De lo contrario, espera a que se complete.
|
||||
|
||||
##### get_workflow_status()
|
||||
|
||||
Obtiene el estado de un flujo de trabajo (estado de implementación, etc.).
|
||||
Obtener el estado de un flujo de trabajo (estado de implementación, etc.).
|
||||
|
||||
```python
|
||||
status = client.get_workflow_status("workflow-id")
|
||||
@@ -93,7 +98,7 @@ print("Is deployed:", status.is_deployed)
|
||||
|
||||
##### validate_workflow()
|
||||
|
||||
Valida que un flujo de trabajo esté listo para su ejecución.
|
||||
Validar que un flujo de trabajo está listo para su ejecución.
|
||||
|
||||
```python
|
||||
is_ready = client.validate_workflow("workflow-id")
|
||||
@@ -107,28 +112,118 @@ if is_ready:
|
||||
|
||||
**Devuelve:** `bool`
|
||||
|
||||
##### execute_workflow_sync()
|
||||
##### get_job_status()
|
||||
|
||||
<Callout type="info">
|
||||
Actualmente, este método es idéntico a `execute_workflow()` ya que todas las ejecuciones son síncronas. Este método se proporciona para compatibilidad futura cuando se añada la ejecución asíncrona.
|
||||
</Callout>
|
||||
|
||||
Ejecuta un flujo de trabajo (actualmente síncrono, igual que `execute_workflow()`).
|
||||
Obtener el estado de una ejecución de trabajo asíncrono.
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
status = client.get_job_status("task-id-from-async-execution")
|
||||
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
|
||||
if status["status"] == "completed":
|
||||
print("Output:", status["output"])
|
||||
```
|
||||
|
||||
**Parámetros:**
|
||||
- `task_id` (str): El ID de tarea devuelto de la ejecución asíncrona
|
||||
|
||||
**Devuelve:** `Dict[str, Any]`
|
||||
|
||||
**Campos de respuesta:**
|
||||
- `success` (bool): Si la solicitud fue exitosa
|
||||
- `taskId` (str): El ID de la tarea
|
||||
- `status` (str): Uno de `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (dict): Contiene `startedAt`, `completedAt`, y `duration`
|
||||
- `output` (any, opcional): La salida del flujo de trabajo (cuando se completa)
|
||||
- `error` (any, opcional): Detalles del error (cuando falla)
|
||||
- `estimatedDuration` (int, opcional): Duración estimada en milisegundos (cuando está procesando/en cola)
|
||||
|
||||
##### execute_with_retry()
|
||||
|
||||
Ejecutar un flujo de trabajo con reintento automático en errores de límite de velocidad usando retroceso exponencial.
|
||||
|
||||
```python
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
input_data={"message": "Hello"},
|
||||
timeout=30.0,
|
||||
max_retries=3, # Maximum number of retries
|
||||
initial_delay=1.0, # Initial delay in seconds
|
||||
max_delay=30.0, # Maximum delay in seconds
|
||||
backoff_multiplier=2.0 # Exponential backoff multiplier
|
||||
)
|
||||
```
|
||||
|
||||
**Parámetros:**
|
||||
- `workflow_id` (str): El ID del flujo de trabajo a ejecutar
|
||||
- `input_data` (dict, opcional): Datos de entrada para pasar al flujo de trabajo
|
||||
- `timeout` (float): Tiempo de espera para la solicitud inicial en segundos
|
||||
- `timeout` (float, opcional): Tiempo de espera en segundos
|
||||
- `stream` (bool, opcional): Habilitar respuestas en streaming
|
||||
- `selected_outputs` (list, opcional): Salidas de bloque para transmitir
|
||||
- `async_execution` (bool, opcional): Ejecutar de forma asíncrona
|
||||
- `max_retries` (int, opcional): Número máximo de reintentos (predeterminado: 3)
|
||||
- `initial_delay` (float, opcional): Retraso inicial en segundos (predeterminado: 1.0)
|
||||
- `max_delay` (float, opcional): Retraso máximo en segundos (predeterminado: 30.0)
|
||||
- `backoff_multiplier` (float, opcional): Multiplicador de retroceso (predeterminado: 2.0)
|
||||
|
||||
**Devuelve:** `WorkflowExecutionResult`
|
||||
**Devuelve:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
La lógica de reintento utiliza retroceso exponencial (1s → 2s → 4s → 8s...) con fluctuación de ±25% para evitar el efecto de manada. Si la API proporciona un encabezado `retry-after`, se utilizará en su lugar.
|
||||
|
||||
##### get_rate_limit_info()
|
||||
|
||||
Obtiene la información actual del límite de tasa de la última respuesta de la API.
|
||||
|
||||
```python
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
print("Limit:", rate_limit_info.limit)
|
||||
print("Remaining:", rate_limit_info.remaining)
|
||||
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
|
||||
```
|
||||
|
||||
**Devuelve:** `RateLimitInfo | None`
|
||||
|
||||
##### get_usage_limits()
|
||||
|
||||
Obtiene los límites de uso actuales y la información de cuota para tu cuenta.
|
||||
|
||||
```python
|
||||
limits = client.get_usage_limits()
|
||||
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
|
||||
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
|
||||
print("Current period cost:", limits.usage["currentPeriodCost"])
|
||||
print("Plan:", limits.usage["plan"])
|
||||
```
|
||||
|
||||
**Devuelve:** `UsageLimits`
|
||||
|
||||
**Estructura de respuesta:**
|
||||
|
||||
```python
|
||||
{
|
||||
"success": bool,
|
||||
"rateLimit": {
|
||||
"sync": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"async": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"authType": str # 'api' or 'manual'
|
||||
},
|
||||
"usage": {
|
||||
"currentPeriodCost": float,
|
||||
"limit": float,
|
||||
"plan": str # e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### set_api_key()
|
||||
|
||||
@@ -170,6 +265,18 @@ class WorkflowExecutionResult:
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class AsyncExecutionResult:
|
||||
success: bool
|
||||
task_id: str
|
||||
status: str # 'queued'
|
||||
created_at: str
|
||||
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@@ -181,6 +288,27 @@ class WorkflowStatus:
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class RateLimitInfo:
|
||||
limit: int
|
||||
remaining: int
|
||||
reset: int
|
||||
retry_after: Optional[int] = None
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UsageLimits:
|
||||
success: bool
|
||||
rate_limit: Dict[str, Any]
|
||||
usage: Dict[str, Any]
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
@@ -191,6 +319,13 @@ class SimStudioError(Exception):
|
||||
self.status = status
|
||||
```
|
||||
|
||||
**Códigos de error comunes:**
|
||||
- `UNAUTHORIZED`: Clave API inválida
|
||||
- `TIMEOUT`: Tiempo de espera agotado
|
||||
- `RATE_LIMIT_EXCEEDED`: Límite de tasa excedido
|
||||
- `USAGE_LIMIT_EXCEEDED`: Límite de uso excedido
|
||||
- `EXECUTION_ERROR`: Ejecución del flujo de trabajo fallida
|
||||
|
||||
## Ejemplos
|
||||
|
||||
### Ejecución básica de flujo de trabajo
|
||||
@@ -205,8 +340,8 @@ class SimStudioError(Exception):
|
||||
<Step title="Ejecutar el flujo de trabajo">
|
||||
Ejecuta el flujo de trabajo con tus datos de entrada.
|
||||
</Step>
|
||||
<Step title="Gestionar el resultado">
|
||||
Procesa el resultado de la ejecución y maneja cualquier error.
|
||||
<Step title="Manejar el resultado">
|
||||
Procesa el resultado de la ejecución y gestiona cualquier error.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
@@ -214,7 +349,7 @@ class SimStudioError(Exception):
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
@@ -252,7 +387,7 @@ Maneja diferentes tipos de errores que pueden ocurrir durante la ejecución del
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
@@ -275,22 +410,22 @@ def execute_with_error_handling():
|
||||
raise
|
||||
```
|
||||
|
||||
### Uso del administrador de contexto
|
||||
### Uso del gestor de contexto
|
||||
|
||||
Usa el cliente como un administrador de contexto para manejar automáticamente la limpieza de recursos:
|
||||
Usa el cliente como un gestor de contexto para manejar automáticamente la limpieza de recursos:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
```
|
||||
|
||||
### Ejecución por lotes de flujos de trabajo
|
||||
### Ejecución de flujos de trabajo por lotes
|
||||
|
||||
Ejecuta múltiples flujos de trabajo de manera eficiente:
|
||||
|
||||
@@ -298,7 +433,7 @@ Ejecuta múltiples flujos de trabajo de manera eficiente:
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
@@ -339,6 +474,230 @@ for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
### Ejecución asíncrona de flujos de trabajo
|
||||
|
||||
Ejecuta flujos de trabajo de forma asíncrona para tareas de larga duración:
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_async():
|
||||
try:
|
||||
# Start async execution
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"data": "large dataset"},
|
||||
async_execution=True # Execute asynchronously
|
||||
)
|
||||
|
||||
# Check if result is an async execution
|
||||
if hasattr(result, 'task_id'):
|
||||
print(f"Task ID: {result.task_id}")
|
||||
print(f"Status endpoint: {result.links['status']}")
|
||||
|
||||
# Poll for completion
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
while status["status"] in ["queued", "processing"]:
|
||||
print(f"Current status: {status['status']}")
|
||||
time.sleep(2) # Wait 2 seconds
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
if status["status"] == "completed":
|
||||
print("Workflow completed!")
|
||||
print(f"Output: {status['output']}")
|
||||
print(f"Duration: {status['metadata']['duration']}")
|
||||
else:
|
||||
print(f"Workflow failed: {status['error']}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
execute_async()
|
||||
```
|
||||
|
||||
### Límite de tasa y reintentos
|
||||
|
||||
Maneja los límites de tasa automáticamente con retroceso exponencial:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_retry_handling():
|
||||
try:
|
||||
# Automatically retries on rate limit
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"message": "Process this"},
|
||||
max_retries=5,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
backoff_multiplier=2.0
|
||||
)
|
||||
|
||||
print(f"Success: {result}")
|
||||
except SimStudioError as error:
|
||||
if error.code == "RATE_LIMIT_EXCEEDED":
|
||||
print("Rate limit exceeded after all retries")
|
||||
|
||||
# Check rate limit info
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
from datetime import datetime
|
||||
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
|
||||
print(f"Rate limit resets at: {reset_time}")
|
||||
|
||||
execute_with_retry_handling()
|
||||
```
|
||||
|
||||
### Monitoreo de uso
|
||||
|
||||
Monitorea el uso de tu cuenta y sus límites:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def check_usage():
|
||||
try:
|
||||
limits = client.get_usage_limits()
|
||||
|
||||
print("=== Rate Limits ===")
|
||||
print("Sync requests:")
|
||||
print(f" Limit: {limits.rate_limit['sync']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
|
||||
|
||||
print("\nAsync requests:")
|
||||
print(f" Limit: {limits.rate_limit['async']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
|
||||
print(f"Limit: ${limits.usage['limit']:.2f}")
|
||||
print(f"Plan: {limits.usage['plan']}")
|
||||
|
||||
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
|
||||
print(f"Usage: {percent_used:.1f}%")
|
||||
|
||||
if percent_used > 80:
|
||||
print("⚠️ Warning: You are approaching your usage limit!")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error checking usage: {error}")
|
||||
|
||||
check_usage()
|
||||
```
|
||||
|
||||
### Ejecución de flujo de trabajo en streaming
|
||||
|
||||
Ejecuta flujos de trabajo con respuestas en tiempo real:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_streaming():
|
||||
"""Execute workflow with streaming enabled."""
|
||||
try:
|
||||
# Enable streaming for specific block outputs
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Count to five"},
|
||||
stream=True,
|
||||
selected_outputs=["agent1.content"] # Use blockName.attribute format
|
||||
)
|
||||
|
||||
print("Workflow result:", result)
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
execute_with_streaming()
|
||||
```
|
||||
|
||||
La respuesta en streaming sigue el formato de Server-Sent Events (SSE):
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Ejemplo de streaming con Flask:**
|
||||
|
||||
```python
|
||||
from flask import Flask, Response, stream_with_context
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/stream-workflow')
|
||||
def stream_workflow():
|
||||
"""Stream workflow execution to the client."""
|
||||
|
||||
def generate():
|
||||
response = requests.post(
|
||||
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': os.getenv('SIM_API_KEY')
|
||||
},
|
||||
json={
|
||||
'message': 'Generate a story',
|
||||
'stream': True,
|
||||
'selectedOutputs': ['agent1.content']
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
data = decoded_line[6:] # Remove 'data: ' prefix
|
||||
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
if 'chunk' in parsed:
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
elif parsed.get('event') == 'done':
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
print("Execution complete:", parsed.get('metadata'))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
### Configuración del entorno
|
||||
|
||||
Configura el cliente usando variables de entorno:
|
||||
@@ -352,8 +711,8 @@ Configura el cliente usando variables de entorno:
|
||||
|
||||
# Development configuration
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
api_key=os.getenv("SIM_API_KEY")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -365,13 +724,13 @@ Configura el cliente usando variables de entorno:
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Production configuration with error handling
|
||||
api_key = os.getenv("SIMSTUDIO_API_KEY")
|
||||
api_key = os.getenv("SIM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("SIMSTUDIO_API_KEY environment variable is required")
|
||||
raise ValueError("SIM_API_KEY environment variable is required")
|
||||
|
||||
client = SimStudioClient(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
El SDK oficial de TypeScript/JavaScript para Sim proporciona seguridad de tipos completa y es compatible tanto con entornos Node.js como con navegadores, lo que te permite ejecutar flujos de trabajo de forma programática desde tus aplicaciones Node.js, aplicaciones web y otros entornos JavaScript. Todas las ejecuciones de flujos de trabajo son actualmente síncronas.
|
||||
El SDK oficial de TypeScript/JavaScript para Sim proporciona seguridad de tipos completa y es compatible tanto con entornos Node.js como de navegador, lo que te permite ejecutar flujos de trabajo programáticamente desde tus aplicaciones Node.js, aplicaciones web y otros entornos JavaScript.
|
||||
|
||||
<Callout type="info">
|
||||
El SDK de TypeScript proporciona seguridad de tipos completa y es compatible tanto con entornos Node.js como con navegadores. Todas las ejecuciones de flujos de trabajo son actualmente síncronas.
|
||||
El SDK de TypeScript proporciona seguridad de tipos completa, soporte para ejecución asíncrona, limitación automática de velocidad con retroceso exponencial y seguimiento de uso.
|
||||
</Callout>
|
||||
|
||||
## Instalación
|
||||
@@ -95,8 +95,13 @@ const result = await client.executeWorkflow('workflow-id', {
|
||||
- `options` (ExecutionOptions, opcional):
|
||||
- `input` (any): Datos de entrada para pasar al flujo de trabajo
|
||||
- `timeout` (number): Tiempo de espera en milisegundos (predeterminado: 30000)
|
||||
- `stream` (boolean): Habilitar respuestas en streaming (predeterminado: false)
|
||||
- `selectedOutputs` (string[]): Bloquear salidas para transmitir en formato `blockName.attribute` (por ejemplo, `["agent1.content"]`)
|
||||
- `async` (boolean): Ejecutar de forma asíncrona (predeterminado: false)
|
||||
|
||||
**Devuelve:** `Promise<WorkflowExecutionResult>`
|
||||
**Devuelve:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
Cuando `async: true`, devuelve inmediatamente un ID de tarea para sondeo. De lo contrario, espera a que se complete.
|
||||
|
||||
##### getWorkflowStatus()
|
||||
|
||||
@@ -128,32 +133,121 @@ if (isReady) {
|
||||
|
||||
**Devuelve:** `Promise<boolean>`
|
||||
|
||||
##### executeWorkflowSync()
|
||||
##### getJobStatus()
|
||||
|
||||
<Callout type="info">
|
||||
Actualmente, este método es idéntico a `executeWorkflow()` ya que todas las ejecuciones son síncronas. Este método se proporciona para compatibilidad futura cuando se añada la ejecución asíncrona.
|
||||
</Callout>
|
||||
|
||||
Ejecutar un flujo de trabajo (actualmente síncrono, igual que `executeWorkflow()`).
|
||||
Obtener el estado de una ejecución de trabajo asíncrono.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWorkflowSync('workflow-id', {
|
||||
input: { data: 'some input' },
|
||||
timeout: 60000
|
||||
const status = await client.getJobStatus('task-id-from-async-execution');
|
||||
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
|
||||
if (status.status === 'completed') {
|
||||
console.log('Output:', status.output);
|
||||
}
|
||||
```
|
||||
|
||||
**Parámetros:**
|
||||
- `taskId` (string): El ID de tarea devuelto de la ejecución asíncrona
|
||||
|
||||
**Devuelve:** `Promise<JobStatus>`
|
||||
|
||||
**Campos de respuesta:**
|
||||
- `success` (boolean): Si la solicitud fue exitosa
|
||||
- `taskId` (string): El ID de la tarea
|
||||
- `status` (string): Uno de `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (object): Contiene `startedAt`, `completedAt`, y `duration`
|
||||
- `output` (any, opcional): La salida del flujo de trabajo (cuando se completa)
|
||||
- `error` (any, opcional): Detalles del error (cuando falla)
|
||||
- `estimatedDuration` (number, opcional): Duración estimada en milisegundos (cuando está procesando/en cola)
|
||||
|
||||
##### executeWithRetry()
|
||||
|
||||
Ejecuta un flujo de trabajo con reintento automático en errores de límite de tasa utilizando retroceso exponencial.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Hello' },
|
||||
timeout: 30000
|
||||
}, {
|
||||
maxRetries: 3, // Maximum number of retries
|
||||
initialDelay: 1000, // Initial delay in ms (1 second)
|
||||
maxDelay: 30000, // Maximum delay in ms (30 seconds)
|
||||
backoffMultiplier: 2 // Exponential backoff multiplier
|
||||
});
|
||||
```
|
||||
|
||||
**Parámetros:**
|
||||
- `workflowId` (string): El ID del flujo de trabajo a ejecutar
|
||||
- `options` (ExecutionOptions, opcional):
|
||||
- `input` (any): Datos de entrada para pasar al flujo de trabajo
|
||||
- `timeout` (number): Tiempo de espera para la solicitud inicial en milisegundos
|
||||
- `options` (ExecutionOptions, opcional): Igual que `executeWorkflow()`
|
||||
- `retryOptions` (RetryOptions, opcional):
|
||||
- `maxRetries` (number): Número máximo de reintentos (predeterminado: 3)
|
||||
- `initialDelay` (number): Retraso inicial en ms (predeterminado: 1000)
|
||||
- `maxDelay` (number): Retraso máximo en ms (predeterminado: 30000)
|
||||
- `backoffMultiplier` (number): Multiplicador de retroceso (predeterminado: 2)
|
||||
|
||||
**Devuelve:** `Promise<WorkflowExecutionResult>`
|
||||
**Devuelve:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
La lógica de reintento utiliza retroceso exponencial (1s → 2s → 4s → 8s...) con fluctuación de ±25% para evitar el efecto de manada. Si la API proporciona una cabecera `retry-after`, se utilizará en su lugar.
|
||||
|
||||
##### getRateLimitInfo()
|
||||
|
||||
Obtiene la información actual del límite de tasa de la última respuesta de la API.
|
||||
|
||||
```typescript
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Limit:', rateLimitInfo.limit);
|
||||
console.log('Remaining:', rateLimitInfo.remaining);
|
||||
console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
```
|
||||
|
||||
**Devuelve:** `RateLimitInfo | null`
|
||||
|
||||
##### getUsageLimits()
|
||||
|
||||
Obtiene los límites de uso actuales y la información de cuota para tu cuenta.
|
||||
|
||||
```typescript
|
||||
const limits = await client.getUsageLimits();
|
||||
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
|
||||
console.log('Current period cost:', limits.usage.currentPeriodCost);
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
```
|
||||
|
||||
**Devuelve:** `Promise<UsageLimits>`
|
||||
|
||||
**Estructura de respuesta:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
success: boolean
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
authType: string // 'api' or 'manual'
|
||||
}
|
||||
usage: {
|
||||
currentPeriodCost: number
|
||||
limit: number
|
||||
plan: string // e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### setApiKey()
|
||||
|
||||
Actualizar la clave API.
|
||||
Actualiza la clave API.
|
||||
|
||||
```typescript
|
||||
client.setApiKey('new-api-key');
|
||||
@@ -161,7 +255,7 @@ client.setApiKey('new-api-key');
|
||||
|
||||
##### setBaseUrl()
|
||||
|
||||
Actualizar la URL base.
|
||||
Actualiza la URL base.
|
||||
|
||||
```typescript
|
||||
client.setBaseUrl('https://my-custom-domain.com');
|
||||
@@ -187,6 +281,20 @@ interface WorkflowExecutionResult {
|
||||
}
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```typescript
|
||||
interface AsyncExecutionResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
status: 'queued';
|
||||
createdAt: string;
|
||||
links: {
|
||||
status: string; // e.g., "/api/jobs/{taskId}"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```typescript
|
||||
@@ -198,6 +306,45 @@ interface WorkflowStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```typescript
|
||||
interface RateLimitInfo {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```typescript
|
||||
interface UsageLimits {
|
||||
success: boolean;
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
async: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
authType: string;
|
||||
};
|
||||
usage: {
|
||||
currentPeriodCost: number;
|
||||
limit: number;
|
||||
plan: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```typescript
|
||||
@@ -207,6 +354,13 @@ class SimStudioError extends Error {
|
||||
}
|
||||
```
|
||||
|
||||
**Códigos de error comunes:**
|
||||
- `UNAUTHORIZED`: Clave API inválida
|
||||
- `TIMEOUT`: Tiempo de espera agotado
|
||||
- `RATE_LIMIT_EXCEEDED`: Límite de tasa excedido
|
||||
- `USAGE_LIMIT_EXCEEDED`: Límite de uso excedido
|
||||
- `EXECUTION_ERROR`: Ejecución del flujo de trabajo fallida
|
||||
|
||||
## Ejemplos
|
||||
|
||||
### Ejecución básica de flujo de trabajo
|
||||
@@ -216,13 +370,13 @@ class SimStudioError extends Error {
|
||||
Configura el SimStudioClient con tu clave API.
|
||||
</Step>
|
||||
<Step title="Validar el flujo de trabajo">
|
||||
Comprueba si el flujo de trabajo está implementado y listo para su ejecución.
|
||||
Comprueba si el flujo de trabajo está desplegado y listo para su ejecución.
|
||||
</Step>
|
||||
<Step title="Ejecutar el flujo de trabajo">
|
||||
Ejecuta el flujo de trabajo con tus datos de entrada.
|
||||
</Step>
|
||||
<Step title="Gestionar el resultado">
|
||||
Procesa el resultado de la ejecución y maneja cualquier error.
|
||||
<Step title="Manejar el resultado">
|
||||
Procesa el resultado de la ejecución y gestiona cualquier error.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
@@ -230,7 +384,7 @@ class SimStudioError extends Error {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function runWorkflow() {
|
||||
@@ -271,7 +425,7 @@ Maneja diferentes tipos de errores que pueden ocurrir durante la ejecución del
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithErrorHandling() {
|
||||
@@ -315,14 +469,14 @@ Configura el cliente usando variables de entorno:
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Development configuration
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL // optional
|
||||
baseUrl: process.env.SIM_BASE_URL // optional
|
||||
});
|
||||
```
|
||||
|
||||
@@ -333,14 +487,14 @@ Configura el cliente usando variables de entorno:
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Production configuration with validation
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://sim.ai'
|
||||
baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
|
||||
});
|
||||
```
|
||||
|
||||
@@ -349,7 +503,7 @@ Configura el cliente usando variables de entorno:
|
||||
|
||||
### Integración con Express de Node.js
|
||||
|
||||
Integración con un servidor Express.js:
|
||||
Integra con un servidor Express.js:
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
@@ -357,7 +511,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const app = express();
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
@@ -399,7 +553,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -430,7 +584,7 @@ export default async function handler(
|
||||
|
||||
### Uso del navegador
|
||||
|
||||
Uso en el navegador (con la configuración CORS adecuada):
|
||||
Uso en el navegador (con configuración CORS adecuada):
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
@@ -469,14 +623,14 @@ document.getElementById('executeBtn')?.addEventListener('click', executeClientSi
|
||||
|
||||
### Ejemplo de hook de React
|
||||
|
||||
Crea un hook personalizado de React para la ejecución del flujo de trabajo:
|
||||
Crea un hook personalizado de React para la ejecución de flujos de trabajo:
|
||||
|
||||
```typescript
|
||||
import { useState, useCallback } from 'react';
|
||||
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
interface UseWorkflowResult {
|
||||
@@ -532,7 +686,7 @@ function WorkflowComponent() {
|
||||
<button onClick={handleExecute} disabled={loading}>
|
||||
{loading ? 'Executing...' : 'Execute Workflow'}
|
||||
</button>
|
||||
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{result && (
|
||||
<div>
|
||||
@@ -545,38 +699,267 @@ function WorkflowComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
## Obtener tu clave API
|
||||
### Ejecución asíncrona de flujos de trabajo
|
||||
|
||||
Ejecuta flujos de trabajo de forma asíncrona para tareas de larga duración:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeAsync() {
|
||||
try {
|
||||
// Start async execution
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { data: 'large dataset' },
|
||||
async: true // Execute asynchronously
|
||||
});
|
||||
|
||||
// Check if result is an async execution
|
||||
if ('taskId' in result) {
|
||||
console.log('Task ID:', result.taskId);
|
||||
console.log('Status endpoint:', result.links.status);
|
||||
|
||||
// Poll for completion
|
||||
let status = await client.getJobStatus(result.taskId);
|
||||
|
||||
while (status.status === 'queued' || status.status === 'processing') {
|
||||
console.log('Current status:', status.status);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||
status = await client.getJobStatus(result.taskId);
|
||||
}
|
||||
|
||||
if (status.status === 'completed') {
|
||||
console.log('Workflow completed!');
|
||||
console.log('Output:', status.output);
|
||||
console.log('Duration:', status.metadata.duration);
|
||||
} else {
|
||||
console.error('Workflow failed:', status.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
executeAsync();
|
||||
```
|
||||
|
||||
### Límite de tasa y reintentos
|
||||
|
||||
Maneja límites de tasa automáticamente con retroceso exponencial:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithRetryHandling() {
|
||||
try {
|
||||
// Automatically retries on rate limit
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Process this' }
|
||||
}, {
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 60000,
|
||||
backoffMultiplier: 2
|
||||
});
|
||||
|
||||
console.log('Success:', result);
|
||||
} catch (error) {
|
||||
if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
console.error('Rate limit exceeded after all retries');
|
||||
|
||||
// Check rate limit info
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Monitoreo de uso
|
||||
|
||||
Monitorea el uso de tu cuenta y sus límites:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function checkUsage() {
|
||||
try {
|
||||
const limits = await client.getUsageLimits();
|
||||
|
||||
console.log('=== Rate Limits ===');
|
||||
console.log('Sync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.sync.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.sync.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.sync.isLimited);
|
||||
|
||||
console.log('\nAsync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.async.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.async.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.async.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.async.isLimited);
|
||||
|
||||
console.log('\n=== Usage ===');
|
||||
console.log('Current period cost:
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithStreaming() {
|
||||
try {
|
||||
// Habilita streaming para salidas de bloques específicos
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { message: 'Count to five' },
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content'] // Usa el formato blockName.attribute
|
||||
});
|
||||
|
||||
console.log('Resultado del flujo de trabajo:', result);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", dos"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**React Streaming Example:**
|
||||
|
||||
```typescript
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function StreamingWorkflow() {
|
||||
const [output, setOutput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const executeStreaming = async () => {
|
||||
setLoading(true);
|
||||
setOutput('');
|
||||
|
||||
// IMPORTANT: Make this API call from your backend server, not the browser
|
||||
// Never expose your API key in client-side code
|
||||
const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.SIM_API_KEY! // Server-side environment variable only
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: 'Generate a story',
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content']
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.chunk) {
|
||||
setOutput(prev => prev + parsed.chunk);
|
||||
} else if (parsed.event === 'done') {
|
||||
console.log('Execution complete:', parsed.metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={executeStreaming} disabled={loading}>
|
||||
{loading ? 'Generando...' : 'Iniciar streaming'}
|
||||
</button>
|
||||
<div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
<Steps>
|
||||
<Step title="Inicia sesión en Sim">
|
||||
Navega a [Sim](https://sim.ai) e inicia sesión en tu cuenta.
|
||||
<Step title="Log in to Sim">
|
||||
Navigate to [Sim](https://sim.ai) and log in to your account.
|
||||
</Step>
|
||||
<Step title="Abre tu flujo de trabajo">
|
||||
Navega al flujo de trabajo que quieres ejecutar programáticamente.
|
||||
<Step title="Open your workflow">
|
||||
Navigate to the workflow you want to execute programmatically.
|
||||
</Step>
|
||||
<Step title="Despliega tu flujo de trabajo">
|
||||
Haz clic en "Deploy" para desplegar tu flujo de trabajo si aún no ha sido desplegado.
|
||||
<Step title="Deploy your workflow">
|
||||
Click on "Deploy" to deploy your workflow if it hasn't been deployed yet.
|
||||
</Step>
|
||||
<Step title="Crea o selecciona una clave API">
|
||||
Durante el proceso de despliegue, selecciona o crea una clave API.
|
||||
<Step title="Create or select an API key">
|
||||
During the deployment process, select or create an API key.
|
||||
</Step>
|
||||
<Step title="Copia la clave API">
|
||||
Copia la clave API para usarla en tu aplicación TypeScript/JavaScript.
|
||||
<Step title="Copy the API key">
|
||||
Copy the API key to use in your TypeScript/JavaScript application.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Callout type="warning">
|
||||
Mantén tu clave API segura y nunca la incluyas en el control de versiones. Usa variables de entorno o gestión de configuración segura.
|
||||
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
|
||||
</Callout>
|
||||
|
||||
## Requisitos
|
||||
## Requirements
|
||||
|
||||
- Node.js 16+
|
||||
- TypeScript 5.0+ (para proyectos TypeScript)
|
||||
- TypeScript 5.0+ (for TypeScript projects)
|
||||
|
||||
## Soporte para TypeScript
|
||||
## TypeScript Support
|
||||
|
||||
El SDK está escrito en TypeScript y proporciona seguridad de tipos completa:
|
||||
The SDK is written in TypeScript and provides full type safety:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
@@ -588,13 +971,13 @@ import {
|
||||
|
||||
// Type-safe client initialization
|
||||
const client: SimStudioClient = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Type-safe workflow execution
|
||||
const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-id', {
|
||||
input: {
|
||||
message: 'Hello, TypeScript!'
|
||||
message: '¡Hola, TypeScript!'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -602,6 +985,7 @@ const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-i
|
||||
const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id');
|
||||
```
|
||||
|
||||
## Licencia
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -38,15 +38,93 @@ curl -X POST \
|
||||
|
||||
Las respuestas exitosas devuelven el resultado de ejecución serializado del Ejecutor. Los errores muestran fallos de validación, autenticación o flujo de trabajo.
|
||||
|
||||
## Respuestas en streaming
|
||||
|
||||
Habilita el streaming en tiempo real para recibir la salida del flujo de trabajo a medida que se genera, carácter por carácter. Esto es útil para mostrar las respuestas de IA progresivamente a los usuarios.
|
||||
|
||||
### Parámetros de solicitud
|
||||
|
||||
Añade estos parámetros para habilitar el streaming:
|
||||
|
||||
- `stream` - Establece a `true` para habilitar el streaming de eventos enviados por el servidor (SSE)
|
||||
- `selectedOutputs` - Array de salidas de bloques para transmitir (p. ej., `["agent1.content"]`)
|
||||
|
||||
### Formato de salida de bloque
|
||||
|
||||
Usa el formato `blockName.attribute` para especificar qué salidas de bloques transmitir:
|
||||
- Formato: `"blockName.attribute"` (p. ej., si quieres transmitir el contenido del bloque Agente 1, usarías `"agent1.content"`)
|
||||
- Los nombres de los bloques no distinguen entre mayúsculas y minúsculas y se ignoran los espacios
|
||||
|
||||
### Ejemplo de solicitud
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Count to five",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
### Formato de respuesta
|
||||
|
||||
Las respuestas en streaming utilizan el formato de eventos enviados por el servidor (SSE):
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", three"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
Cada evento incluye:
|
||||
- **Fragmentos de streaming**: `{"blockId": "...", "chunk": "text"}` - Texto en tiempo real a medida que se genera
|
||||
- **Evento final**: `{"event": "done", ...}` - Metadatos de ejecución y resultados completos
|
||||
- **Terminador**: `[DONE]` - Señala el fin del stream
|
||||
|
||||
### Streaming de múltiples bloques
|
||||
|
||||
Cuando `selectedOutputs` incluye múltiples bloques, cada fragmento indica qué bloque lo produjo:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Process this request",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content", "agent2.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
El campo `blockId` en cada fragmento te permite dirigir la salida al elemento de UI correcto:
|
||||
|
||||
```
|
||||
data: {"blockId":"agent1-uuid","chunk":"Processing..."}
|
||||
|
||||
data: {"blockId":"agent2-uuid","chunk":"Analyzing..."}
|
||||
|
||||
data: {"blockId":"agent1-uuid","chunk":" complete"}
|
||||
```
|
||||
|
||||
## Referencia de salida
|
||||
|
||||
| Referencia | Descripción |
|
||||
|-----------|-------------|
|
||||
| `<api.field>` | Campo definido en el Formato de Entrada |
|
||||
| `<api.input>` | Cuerpo completo estructurado de la solicitud |
|
||||
| `<api.field>` | Campo definido en el formato de entrada |
|
||||
| `<api.input>` | Cuerpo de solicitud estructurado completo |
|
||||
|
||||
Si no se define un Formato de Entrada, el ejecutor expone el JSON sin procesar solo en `<api.input>`.
|
||||
Si no se define un formato de entrada, el ejecutor expone el JSON sin procesar solo en `<api.input>`.
|
||||
|
||||
<Callout type="warning">
|
||||
Un flujo de trabajo puede contener solo un Disparador de API. Publica una nueva implementación después de realizar cambios para que el punto de conexión se mantenga actualizado.
|
||||
Un flujo de trabajo puede contener solo un disparador de API. Publica una nueva implementación después de los cambios para que el endpoint se mantenga actualizado.
|
||||
</Callout>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
Le SDK Python officiel pour Sim vous permet d'exécuter des workflows de manière programmatique à partir de vos applications Python en utilisant le SDK Python officiel.
|
||||
|
||||
<Callout type="info">
|
||||
Le SDK Python prend en charge Python 3.8+ et fournit une exécution synchrone des workflows. Toutes les exécutions de workflow sont actuellement synchrones.
|
||||
Le SDK Python prend en charge Python 3.8+ avec support d'exécution asynchrone, limitation automatique du débit avec backoff exponentiel, et suivi d'utilisation.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
@@ -71,11 +71,16 @@ result = client.execute_workflow(
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflow_id` (str) : L'ID du workflow à exécuter
|
||||
- `workflow_id` (str) : L'identifiant du workflow à exécuter
|
||||
- `input_data` (dict, facultatif) : Données d'entrée à transmettre au workflow
|
||||
- `timeout` (float, facultatif) : Délai d'attente en secondes (par défaut : 30.0)
|
||||
- `timeout` (float, facultatif) : Délai d'expiration en secondes (par défaut : 30.0)
|
||||
- `stream` (bool, facultatif) : Activer les réponses en streaming (par défaut : False)
|
||||
- `selected_outputs` (list[str], facultatif) : Sorties de blocs à diffuser au format `blockName.attribute` (par exemple, `["agent1.content"]`)
|
||||
- `async_execution` (bool, facultatif) : Exécuter de manière asynchrone (par défaut : False)
|
||||
|
||||
**Retourne :** `WorkflowExecutionResult`
|
||||
**Retourne :** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
Lorsque `async_execution=True`, retourne immédiatement un identifiant de tâche pour l'interrogation. Sinon, attend la fin de l'exécution.
|
||||
|
||||
##### get_workflow_status()
|
||||
|
||||
@@ -87,13 +92,13 @@ print("Is deployed:", status.is_deployed)
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflow_id` (str) : L'ID du workflow
|
||||
- `workflow_id` (str) : L'identifiant du workflow
|
||||
|
||||
**Retourne :** `WorkflowStatus`
|
||||
|
||||
##### validate_workflow()
|
||||
|
||||
Valide qu'un workflow est prêt pour l'exécution.
|
||||
Valider qu'un workflow est prêt pour l'exécution.
|
||||
|
||||
```python
|
||||
is_ready = client.validate_workflow("workflow-id")
|
||||
@@ -107,32 +112,122 @@ if is_ready:
|
||||
|
||||
**Retourne :** `bool`
|
||||
|
||||
##### execute_workflow_sync()
|
||||
##### get_job_status()
|
||||
|
||||
<Callout type="info">
|
||||
Actuellement, cette méthode est identique à `execute_workflow()` puisque toutes les exécutions sont synchrones. Cette méthode est fournie pour une compatibilité future lorsque l'exécution asynchrone sera ajoutée.
|
||||
</Callout>
|
||||
|
||||
Exécute un workflow (actuellement synchrone, identique à `execute_workflow()`).
|
||||
Obtenir le statut d'une exécution de tâche asynchrone.
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
status = client.get_job_status("task-id-from-async-execution")
|
||||
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
|
||||
if status["status"] == "completed":
|
||||
print("Output:", status["output"])
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `task_id` (str) : L'identifiant de tâche retourné par l'exécution asynchrone
|
||||
|
||||
**Retourne :** `Dict[str, Any]`
|
||||
|
||||
**Champs de réponse :**
|
||||
- `success` (bool) : Si la requête a réussi
|
||||
- `taskId` (str) : L'identifiant de la tâche
|
||||
- `status` (str) : L'un des états suivants : `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (dict) : Contient `startedAt`, `completedAt`, et `duration`
|
||||
- `output` (any, facultatif) : La sortie du workflow (une fois terminé)
|
||||
- `error` (any, facultatif) : Détails de l'erreur (en cas d'échec)
|
||||
- `estimatedDuration` (int, facultatif) : Durée estimée en millisecondes (lors du traitement/mise en file d'attente)
|
||||
|
||||
##### execute_with_retry()
|
||||
|
||||
Exécuter un workflow avec réessai automatique en cas d'erreurs de limitation de débit, en utilisant un backoff exponentiel.
|
||||
|
||||
```python
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
input_data={"message": "Hello"},
|
||||
timeout=30.0,
|
||||
max_retries=3, # Maximum number of retries
|
||||
initial_delay=1.0, # Initial delay in seconds
|
||||
max_delay=30.0, # Maximum delay in seconds
|
||||
backoff_multiplier=2.0 # Exponential backoff multiplier
|
||||
)
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflow_id` (str) : L'identifiant du workflow à exécuter
|
||||
- `input_data` (dict, facultatif) : Données d'entrée à transmettre au workflow
|
||||
- `timeout` (float) : Délai d'attente pour la requête initiale en secondes
|
||||
- `timeout` (float, facultatif) : Délai d'expiration en secondes
|
||||
- `stream` (bool, facultatif) : Activer les réponses en streaming
|
||||
- `selected_outputs` (list, facultatif) : Sorties de blocs à diffuser
|
||||
- `async_execution` (bool, facultatif) : Exécuter de manière asynchrone
|
||||
- `max_retries` (int, facultatif) : Nombre maximum de tentatives (par défaut : 3)
|
||||
- `initial_delay` (float, facultatif) : Délai initial en secondes (par défaut : 1.0)
|
||||
- `max_delay` (float, facultatif) : Délai maximum en secondes (par défaut : 30.0)
|
||||
- `backoff_multiplier` (float, facultatif) : Multiplicateur de backoff (par défaut : 2.0)
|
||||
|
||||
**Retourne :** `WorkflowExecutionResult`
|
||||
**Retourne :** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
La logique de nouvelle tentative utilise un backoff exponentiel (1s → 2s → 4s → 8s...) avec une variation aléatoire de ±25% pour éviter l'effet de horde. Si l'API fournit un en-tête `retry-after`, celui-ci sera utilisé à la place.
|
||||
|
||||
##### get_rate_limit_info()
|
||||
|
||||
Obtenir les informations actuelles sur les limites de débit à partir de la dernière réponse de l'API.
|
||||
|
||||
```python
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
print("Limit:", rate_limit_info.limit)
|
||||
print("Remaining:", rate_limit_info.remaining)
|
||||
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
|
||||
```
|
||||
|
||||
**Retourne :** `RateLimitInfo | None`
|
||||
|
||||
##### get_usage_limits()
|
||||
|
||||
Obtenir les limites d'utilisation actuelles et les informations de quota pour votre compte.
|
||||
|
||||
```python
|
||||
limits = client.get_usage_limits()
|
||||
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
|
||||
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
|
||||
print("Current period cost:", limits.usage["currentPeriodCost"])
|
||||
print("Plan:", limits.usage["plan"])
|
||||
```
|
||||
|
||||
**Retourne :** `UsageLimits`
|
||||
|
||||
**Structure de la réponse :**
|
||||
|
||||
```python
|
||||
{
|
||||
"success": bool,
|
||||
"rateLimit": {
|
||||
"sync": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"async": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"authType": str # 'api' or 'manual'
|
||||
},
|
||||
"usage": {
|
||||
"currentPeriodCost": float,
|
||||
"limit": float,
|
||||
"plan": str # e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### set_api_key()
|
||||
|
||||
Met à jour la clé API.
|
||||
Mettre à jour la clé API.
|
||||
|
||||
```python
|
||||
client.set_api_key("new-api-key")
|
||||
@@ -140,7 +235,7 @@ client.set_api_key("new-api-key")
|
||||
|
||||
##### set_base_url()
|
||||
|
||||
Met à jour l'URL de base.
|
||||
Mettre à jour l'URL de base.
|
||||
|
||||
```python
|
||||
client.set_base_url("https://my-custom-domain.com")
|
||||
@@ -148,7 +243,7 @@ client.set_base_url("https://my-custom-domain.com")
|
||||
|
||||
##### close()
|
||||
|
||||
Ferme la session HTTP sous-jacente.
|
||||
Fermer la session HTTP sous-jacente.
|
||||
|
||||
```python
|
||||
client.close()
|
||||
@@ -170,6 +265,18 @@ class WorkflowExecutionResult:
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class AsyncExecutionResult:
|
||||
success: bool
|
||||
task_id: str
|
||||
status: str # 'queued'
|
||||
created_at: str
|
||||
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@@ -181,6 +288,27 @@ class WorkflowStatus:
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class RateLimitInfo:
|
||||
limit: int
|
||||
remaining: int
|
||||
reset: int
|
||||
retry_after: Optional[int] = None
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UsageLimits:
|
||||
success: bool
|
||||
rate_limit: Dict[str, Any]
|
||||
usage: Dict[str, Any]
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
@@ -191,19 +319,26 @@ class SimStudioError(Exception):
|
||||
self.status = status
|
||||
```
|
||||
|
||||
**Codes d'erreur courants :**
|
||||
- `UNAUTHORIZED` : Clé API invalide
|
||||
- `TIMEOUT` : Délai d'attente de la requête dépassé
|
||||
- `RATE_LIMIT_EXCEEDED` : Limite de débit dépassée
|
||||
- `USAGE_LIMIT_EXCEEDED` : Limite d'utilisation dépassée
|
||||
- `EXECUTION_ERROR` : Échec de l'exécution du workflow
|
||||
|
||||
## Exemples
|
||||
|
||||
### Exécution de flux de travail basique
|
||||
### Exécution basique d'un workflow
|
||||
|
||||
<Steps>
|
||||
<Step title="Initialiser le client">
|
||||
Configurez le SimStudioClient avec votre clé API.
|
||||
</Step>
|
||||
<Step title="Valider le flux de travail">
|
||||
Vérifiez si le flux de travail est déployé et prêt pour l'exécution.
|
||||
<Step title="Valider le workflow">
|
||||
Vérifiez si le workflow est déployé et prêt pour l'exécution.
|
||||
</Step>
|
||||
<Step title="Exécuter le flux de travail">
|
||||
Lancez le flux de travail avec vos données d'entrée.
|
||||
<Step title="Exécuter le workflow">
|
||||
Lancez le workflow avec vos données d'entrée.
|
||||
</Step>
|
||||
<Step title="Gérer le résultat">
|
||||
Traitez le résultat de l'exécution et gérez les éventuelles erreurs.
|
||||
@@ -214,7 +349,7 @@ class SimStudioError(Exception):
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
@@ -246,13 +381,13 @@ run_workflow()
|
||||
|
||||
### Gestion des erreurs
|
||||
|
||||
Gérez différents types d'erreurs qui peuvent survenir pendant l'exécution du flux de travail :
|
||||
Gérez différents types d'erreurs qui peuvent survenir pendant l'exécution du workflow :
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
@@ -284,21 +419,21 @@ from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
```
|
||||
|
||||
### Exécution de flux de travail par lots
|
||||
### Exécution de workflows par lots
|
||||
|
||||
Exécutez plusieurs flux de travail efficacement :
|
||||
Exécutez plusieurs workflows efficacement :
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
@@ -339,6 +474,230 @@ for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
### Exécution asynchrone de workflow
|
||||
|
||||
Exécutez des workflows de manière asynchrone pour les tâches de longue durée :
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_async():
|
||||
try:
|
||||
# Start async execution
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"data": "large dataset"},
|
||||
async_execution=True # Execute asynchronously
|
||||
)
|
||||
|
||||
# Check if result is an async execution
|
||||
if hasattr(result, 'task_id'):
|
||||
print(f"Task ID: {result.task_id}")
|
||||
print(f"Status endpoint: {result.links['status']}")
|
||||
|
||||
# Poll for completion
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
while status["status"] in ["queued", "processing"]:
|
||||
print(f"Current status: {status['status']}")
|
||||
time.sleep(2) # Wait 2 seconds
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
if status["status"] == "completed":
|
||||
print("Workflow completed!")
|
||||
print(f"Output: {status['output']}")
|
||||
print(f"Duration: {status['metadata']['duration']}")
|
||||
else:
|
||||
print(f"Workflow failed: {status['error']}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
execute_async()
|
||||
```
|
||||
|
||||
### Limitation de débit et nouvelle tentative
|
||||
|
||||
Gérez les limites de débit automatiquement avec un retrait exponentiel :
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_retry_handling():
|
||||
try:
|
||||
# Automatically retries on rate limit
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"message": "Process this"},
|
||||
max_retries=5,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
backoff_multiplier=2.0
|
||||
)
|
||||
|
||||
print(f"Success: {result}")
|
||||
except SimStudioError as error:
|
||||
if error.code == "RATE_LIMIT_EXCEEDED":
|
||||
print("Rate limit exceeded after all retries")
|
||||
|
||||
# Check rate limit info
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
from datetime import datetime
|
||||
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
|
||||
print(f"Rate limit resets at: {reset_time}")
|
||||
|
||||
execute_with_retry_handling()
|
||||
```
|
||||
|
||||
### Surveillance de l'utilisation
|
||||
|
||||
Surveillez l'utilisation et les limites de votre compte :
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def check_usage():
|
||||
try:
|
||||
limits = client.get_usage_limits()
|
||||
|
||||
print("=== Rate Limits ===")
|
||||
print("Sync requests:")
|
||||
print(f" Limit: {limits.rate_limit['sync']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
|
||||
|
||||
print("\nAsync requests:")
|
||||
print(f" Limit: {limits.rate_limit['async']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
|
||||
print(f"Limit: ${limits.usage['limit']:.2f}")
|
||||
print(f"Plan: {limits.usage['plan']}")
|
||||
|
||||
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
|
||||
print(f"Usage: {percent_used:.1f}%")
|
||||
|
||||
if percent_used > 80:
|
||||
print("⚠️ Warning: You are approaching your usage limit!")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error checking usage: {error}")
|
||||
|
||||
check_usage()
|
||||
```
|
||||
|
||||
### Exécution de workflow en streaming
|
||||
|
||||
Exécutez des workflows avec des réponses en streaming en temps réel :
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_streaming():
|
||||
"""Execute workflow with streaming enabled."""
|
||||
try:
|
||||
# Enable streaming for specific block outputs
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Count to five"},
|
||||
stream=True,
|
||||
selected_outputs=["agent1.content"] # Use blockName.attribute format
|
||||
)
|
||||
|
||||
print("Workflow result:", result)
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
execute_with_streaming()
|
||||
```
|
||||
|
||||
La réponse en streaming suit le format Server-Sent Events (SSE) :
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Exemple de streaming avec Flask :**
|
||||
|
||||
```python
|
||||
from flask import Flask, Response, stream_with_context
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/stream-workflow')
|
||||
def stream_workflow():
|
||||
"""Stream workflow execution to the client."""
|
||||
|
||||
def generate():
|
||||
response = requests.post(
|
||||
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': os.getenv('SIM_API_KEY')
|
||||
},
|
||||
json={
|
||||
'message': 'Generate a story',
|
||||
'stream': True,
|
||||
'selectedOutputs': ['agent1.content']
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
data = decoded_line[6:] # Remove 'data: ' prefix
|
||||
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
if 'chunk' in parsed:
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
elif parsed.get('event') == 'done':
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
print("Execution complete:", parsed.get('metadata'))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
### Configuration de l'environnement
|
||||
|
||||
Configurez le client en utilisant des variables d'environnement :
|
||||
@@ -352,8 +711,8 @@ Configurez le client en utilisant des variables d'environnement :
|
||||
|
||||
# Development configuration
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
api_key=os.getenv("SIM_API_KEY")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -365,30 +724,30 @@ Configurez le client en utilisant des variables d'environnement :
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Production configuration with error handling
|
||||
api_key = os.getenv("SIMSTUDIO_API_KEY")
|
||||
api_key = os.getenv("SIM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("SIMSTUDIO_API_KEY environment variable is required")
|
||||
raise ValueError("SIM_API_KEY environment variable is required")
|
||||
|
||||
client = SimStudioClient(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Obtenir votre clé API
|
||||
## Obtention de votre clé API
|
||||
|
||||
<Steps>
|
||||
<Step title="Connectez-vous à Sim">
|
||||
Accédez à [Sim](https://sim.ai) et connectez-vous à votre compte.
|
||||
</Step>
|
||||
<Step title="Ouvrez votre flux de travail">
|
||||
Naviguez vers le flux de travail que vous souhaitez exécuter par programmation.
|
||||
<Step title="Ouvrez votre workflow">
|
||||
Accédez au workflow que vous souhaitez exécuter par programmation.
|
||||
</Step>
|
||||
<Step title="Déployez votre flux de travail">
|
||||
Cliquez sur "Déployer" pour déployer votre flux de travail s'il n'a pas encore été déployé.
|
||||
<Step title="Déployez votre workflow">
|
||||
Cliquez sur "Déployer" pour déployer votre workflow s'il n'a pas encore été déployé.
|
||||
</Step>
|
||||
<Step title="Créez ou sélectionnez une clé API">
|
||||
Pendant le processus de déploiement, sélectionnez ou créez une clé API.
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
Le SDK officiel TypeScript/JavaScript pour Sim offre une sécurité de type complète et prend en charge les environnements Node.js et navigateur, vous permettant d'exécuter des workflows de manière programmatique depuis vos applications Node.js, applications web et autres environnements JavaScript. Toutes les exécutions de workflow sont actuellement synchrones.
|
||||
Le SDK officiel TypeScript/JavaScript pour Sim offre une sécurité de type complète et prend en charge les environnements Node.js et navigateur, vous permettant d'exécuter des workflows par programmation depuis vos applications Node.js, applications web et autres environnements JavaScript.
|
||||
|
||||
<Callout type="info">
|
||||
Le SDK TypeScript offre une sécurité de type complète et prend en charge les environnements Node.js et navigateur. Toutes les exécutions de workflow sont actuellement synchrones.
|
||||
Le SDK TypeScript offre une sécurité de type complète, la prise en charge de l'exécution asynchrone, une limitation automatique du débit avec backoff exponentiel et le suivi d'utilisation.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
@@ -91,12 +91,17 @@ const result = await client.executeWorkflow('workflow-id', {
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflowId` (string) : L'identifiant du workflow à exécuter
|
||||
- `options` (ExecutionOptions, facultatif) :
|
||||
- `workflowId` (string) : L'ID du workflow à exécuter
|
||||
- `options` (ExecutionOptions, optionnel) :
|
||||
- `input` (any) : Données d'entrée à transmettre au workflow
|
||||
- `timeout` (number) : Délai d'expiration en millisecondes (par défaut : 30000)
|
||||
- `stream` (boolean) : Activer les réponses en streaming (par défaut : false)
|
||||
- `selectedOutputs` (string[]) : Bloquer les sorties à diffuser au format `blockName.attribute` (par exemple, `["agent1.content"]`)
|
||||
- `async` (boolean) : Exécuter de manière asynchrone (par défaut : false)
|
||||
|
||||
**Retourne :** `Promise<WorkflowExecutionResult>`
|
||||
**Retourne :** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
Lorsque `async: true`, retourne immédiatement avec un ID de tâche pour l'interrogation. Sinon, attend la fin de l'exécution.
|
||||
|
||||
##### getWorkflowStatus()
|
||||
|
||||
@@ -108,7 +113,7 @@ console.log('Is deployed:', status.isDeployed);
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflowId` (string) : L'identifiant du workflow
|
||||
- `workflowId` (string) : L'ID du workflow
|
||||
|
||||
**Retourne :** `Promise<WorkflowStatus>`
|
||||
|
||||
@@ -124,36 +129,125 @@ if (isReady) {
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflowId` (string) : L'identifiant du workflow
|
||||
- `workflowId` (string) : L'ID du workflow
|
||||
|
||||
**Retourne :** `Promise<boolean>`
|
||||
|
||||
##### executeWorkflowSync()
|
||||
##### getJobStatus()
|
||||
|
||||
<Callout type="info">
|
||||
Actuellement, cette méthode est identique à `executeWorkflow()` puisque toutes les exécutions sont synchrones. Cette méthode est fournie pour une compatibilité future lorsque l'exécution asynchrone sera ajoutée.
|
||||
</Callout>
|
||||
|
||||
Exécuter un workflow (actuellement synchrone, identique à `executeWorkflow()`).
|
||||
Obtenir le statut d'une exécution de tâche asynchrone.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWorkflowSync('workflow-id', {
|
||||
input: { data: 'some input' },
|
||||
timeout: 60000
|
||||
const status = await client.getJobStatus('task-id-from-async-execution');
|
||||
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
|
||||
if (status.status === 'completed') {
|
||||
console.log('Output:', status.output);
|
||||
}
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `taskId` (string) : L'ID de tâche retourné par l'exécution asynchrone
|
||||
|
||||
**Retourne :** `Promise<JobStatus>`
|
||||
|
||||
**Champs de réponse :**
|
||||
- `success` (boolean) : Indique si la requête a réussi
|
||||
- `taskId` (string) : L'ID de la tâche
|
||||
- `status` (string) : L'un des statuts suivants : `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (object) : Contient `startedAt`, `completedAt`, et `duration`
|
||||
- `output` (any, optionnel) : La sortie du workflow (une fois terminé)
|
||||
- `error` (any, optionnel) : Détails de l'erreur (en cas d'échec)
|
||||
- `estimatedDuration` (number, optionnel) : Durée estimée en millisecondes (lorsqu'en traitement/en file d'attente)
|
||||
|
||||
##### executeWithRetry()
|
||||
|
||||
Exécute un workflow avec une nouvelle tentative automatique en cas d'erreurs de limite de débit en utilisant un backoff exponentiel.
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Hello' },
|
||||
timeout: 30000
|
||||
}, {
|
||||
maxRetries: 3, // Maximum number of retries
|
||||
initialDelay: 1000, // Initial delay in ms (1 second)
|
||||
maxDelay: 30000, // Maximum delay in ms (30 seconds)
|
||||
backoffMultiplier: 2 // Exponential backoff multiplier
|
||||
});
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- `workflowId` (string) : L'identifiant du workflow à exécuter
|
||||
- `options` (ExecutionOptions, facultatif) :
|
||||
- `input` (any) : Données d'entrée à transmettre au workflow
|
||||
- `timeout` (number) : Délai d'expiration pour la requête initiale en millisecondes
|
||||
- `options` (ExecutionOptions, facultatif) : Identique à `executeWorkflow()`
|
||||
- `retryOptions` (RetryOptions, facultatif) :
|
||||
- `maxRetries` (number) : Nombre maximum de tentatives (par défaut : 3)
|
||||
- `initialDelay` (number) : Délai initial en ms (par défaut : 1000)
|
||||
- `maxDelay` (number) : Délai maximum en ms (par défaut : 30000)
|
||||
- `backoffMultiplier` (number) : Multiplicateur de backoff (par défaut : 2)
|
||||
|
||||
**Retourne :** `Promise<WorkflowExecutionResult>`
|
||||
**Retourne :** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
La logique de nouvelle tentative utilise un backoff exponentiel (1s → 2s → 4s → 8s...) avec une variation aléatoire de ±25 % pour éviter l'effet de rafale. Si l'API fournit un en-tête `retry-after`, celui-ci sera utilisé à la place.
|
||||
|
||||
##### getRateLimitInfo()
|
||||
|
||||
Obtient les informations actuelles sur les limites de débit à partir de la dernière réponse de l'API.
|
||||
|
||||
```typescript
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Limit:', rateLimitInfo.limit);
|
||||
console.log('Remaining:', rateLimitInfo.remaining);
|
||||
console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
```
|
||||
|
||||
**Retourne :** `RateLimitInfo | null`
|
||||
|
||||
##### getUsageLimits()
|
||||
|
||||
Obtient les limites d'utilisation actuelles et les informations de quota pour votre compte.
|
||||
|
||||
```typescript
|
||||
const limits = await client.getUsageLimits();
|
||||
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
|
||||
console.log('Current period cost:', limits.usage.currentPeriodCost);
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
```
|
||||
|
||||
**Retourne :** `Promise<UsageLimits>`
|
||||
|
||||
**Structure de la réponse :**
|
||||
|
||||
```typescript
|
||||
{
|
||||
success: boolean
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
authType: string // 'api' or 'manual'
|
||||
}
|
||||
usage: {
|
||||
currentPeriodCost: number
|
||||
limit: number
|
||||
plan: string // e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### setApiKey()
|
||||
|
||||
Mettre à jour la clé API.
|
||||
Met à jour la clé API.
|
||||
|
||||
```typescript
|
||||
client.setApiKey('new-api-key');
|
||||
@@ -161,7 +255,7 @@ client.setApiKey('new-api-key');
|
||||
|
||||
##### setBaseUrl()
|
||||
|
||||
Mettre à jour l'URL de base.
|
||||
Met à jour l'URL de base.
|
||||
|
||||
```typescript
|
||||
client.setBaseUrl('https://my-custom-domain.com');
|
||||
@@ -187,6 +281,20 @@ interface WorkflowExecutionResult {
|
||||
}
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```typescript
|
||||
interface AsyncExecutionResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
status: 'queued';
|
||||
createdAt: string;
|
||||
links: {
|
||||
status: string; // e.g., "/api/jobs/{taskId}"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```typescript
|
||||
@@ -198,6 +306,45 @@ interface WorkflowStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```typescript
|
||||
interface RateLimitInfo {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```typescript
|
||||
interface UsageLimits {
|
||||
success: boolean;
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
async: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
authType: string;
|
||||
};
|
||||
usage: {
|
||||
currentPeriodCost: number;
|
||||
limit: number;
|
||||
plan: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```typescript
|
||||
@@ -207,9 +354,16 @@ class SimStudioError extends Error {
|
||||
}
|
||||
```
|
||||
|
||||
**Codes d'erreur courants :**
|
||||
- `UNAUTHORIZED` : Clé API invalide
|
||||
- `TIMEOUT` : Délai d'attente de la requête dépassé
|
||||
- `RATE_LIMIT_EXCEEDED` : Limite de débit dépassée
|
||||
- `USAGE_LIMIT_EXCEEDED` : Limite d'utilisation dépassée
|
||||
- `EXECUTION_ERROR` : Échec de l'exécution du workflow
|
||||
|
||||
## Exemples
|
||||
|
||||
### Exécution de workflow basique
|
||||
### Exécution basique d'un workflow
|
||||
|
||||
<Steps>
|
||||
<Step title="Initialiser le client">
|
||||
@@ -230,7 +384,7 @@ class SimStudioError extends Error {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function runWorkflow() {
|
||||
@@ -271,7 +425,7 @@ Gérez différents types d'erreurs qui peuvent survenir pendant l'exécution du
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithErrorHandling() {
|
||||
@@ -315,14 +469,14 @@ Configurez le client en utilisant des variables d'environnement :
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Development configuration
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL // optional
|
||||
baseUrl: process.env.SIM_BASE_URL // optional
|
||||
});
|
||||
```
|
||||
|
||||
@@ -333,14 +487,14 @@ Configurez le client en utilisant des variables d'environnement :
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Production configuration with validation
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://sim.ai'
|
||||
baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
|
||||
});
|
||||
```
|
||||
|
||||
@@ -357,7 +511,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const app = express();
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
@@ -399,7 +553,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -469,14 +623,14 @@ document.getElementById('executeBtn')?.addEventListener('click', executeClientSi
|
||||
|
||||
### Exemple de hook React
|
||||
|
||||
Créez un hook React personnalisé pour l'exécution du workflow :
|
||||
Créer un hook React personnalisé pour l'exécution de workflow :
|
||||
|
||||
```typescript
|
||||
import { useState, useCallback } from 'react';
|
||||
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
interface UseWorkflowResult {
|
||||
@@ -532,7 +686,7 @@ function WorkflowComponent() {
|
||||
<button onClick={handleExecute} disabled={loading}>
|
||||
{loading ? 'Executing...' : 'Execute Workflow'}
|
||||
</button>
|
||||
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{result && (
|
||||
<div>
|
||||
@@ -545,38 +699,267 @@ function WorkflowComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
## Obtenir votre clé API
|
||||
### Exécution asynchrone de workflow
|
||||
|
||||
Exécuter des workflows de manière asynchrone pour les tâches de longue durée :
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeAsync() {
|
||||
try {
|
||||
// Start async execution
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { data: 'large dataset' },
|
||||
async: true // Execute asynchronously
|
||||
});
|
||||
|
||||
// Check if result is an async execution
|
||||
if ('taskId' in result) {
|
||||
console.log('Task ID:', result.taskId);
|
||||
console.log('Status endpoint:', result.links.status);
|
||||
|
||||
// Poll for completion
|
||||
let status = await client.getJobStatus(result.taskId);
|
||||
|
||||
while (status.status === 'queued' || status.status === 'processing') {
|
||||
console.log('Current status:', status.status);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||
status = await client.getJobStatus(result.taskId);
|
||||
}
|
||||
|
||||
if (status.status === 'completed') {
|
||||
console.log('Workflow completed!');
|
||||
console.log('Output:', status.output);
|
||||
console.log('Duration:', status.metadata.duration);
|
||||
} else {
|
||||
console.error('Workflow failed:', status.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
executeAsync();
|
||||
```
|
||||
|
||||
### Limitation de débit et nouvelle tentative
|
||||
|
||||
Gérer automatiquement les limites de débit avec backoff exponentiel :
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithRetryHandling() {
|
||||
try {
|
||||
// Automatically retries on rate limit
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Process this' }
|
||||
}, {
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 60000,
|
||||
backoffMultiplier: 2
|
||||
});
|
||||
|
||||
console.log('Success:', result);
|
||||
} catch (error) {
|
||||
if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
console.error('Rate limit exceeded after all retries');
|
||||
|
||||
// Check rate limit info
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Surveillance d'utilisation
|
||||
|
||||
Surveiller l'utilisation et les limites de votre compte :
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function checkUsage() {
|
||||
try {
|
||||
const limits = await client.getUsageLimits();
|
||||
|
||||
console.log('=== Rate Limits ===');
|
||||
console.log('Sync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.sync.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.sync.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.sync.isLimited);
|
||||
|
||||
console.log('\nAsync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.async.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.async.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.async.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.async.isLimited);
|
||||
|
||||
console.log('\n=== Usage ===');
|
||||
console.log('Current period cost:
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithStreaming() {
|
||||
try {
|
||||
// Activer le streaming pour des sorties de blocs spécifiques
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { message: 'Compter jusqu'à cinq' },
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content'] // Utiliser le format blockName.attribute
|
||||
});
|
||||
|
||||
console.log('Résultat du workflow :', result);
|
||||
} catch (error) {
|
||||
console.error('Erreur :', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", deux"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**React Streaming Example:**
|
||||
|
||||
```typescript
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function StreamingWorkflow() {
|
||||
const [output, setOutput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const executeStreaming = async () => {
|
||||
setLoading(true);
|
||||
setOutput('');
|
||||
|
||||
// IMPORTANT: Make this API call from your backend server, not the browser
|
||||
// Never expose your API key in client-side code
|
||||
const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.SIM_API_KEY! // Server-side environment variable only
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: 'Generate a story',
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content']
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.chunk) {
|
||||
setOutput(prev => prev + parsed.chunk);
|
||||
} else if (parsed.event === 'done') {
|
||||
console.log('Execution complete:', parsed.metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={executeStreaming} disabled={loading}>
|
||||
{loading ? 'Génération en cours...' : 'Démarrer le streaming'}
|
||||
</button>
|
||||
<div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
<Steps>
|
||||
<Step title="Connectez-vous à Sim">
|
||||
Accédez à [Sim](https://sim.ai) et connectez-vous à votre compte.
|
||||
<Step title="Log in to Sim">
|
||||
Navigate to [Sim](https://sim.ai) and log in to your account.
|
||||
</Step>
|
||||
<Step title="Ouvrez votre workflow">
|
||||
Accédez au workflow que vous souhaitez exécuter par programmation.
|
||||
<Step title="Open your workflow">
|
||||
Navigate to the workflow you want to execute programmatically.
|
||||
</Step>
|
||||
<Step title="Déployez votre workflow">
|
||||
Cliquez sur « Déployer » pour déployer votre workflow s'il n'a pas encore été déployé.
|
||||
<Step title="Deploy your workflow">
|
||||
Click on "Deploy" to deploy your workflow if it hasn't been deployed yet.
|
||||
</Step>
|
||||
<Step title="Créez ou sélectionnez une clé API">
|
||||
Pendant le processus de déploiement, sélectionnez ou créez une clé API.
|
||||
<Step title="Create or select an API key">
|
||||
During the deployment process, select or create an API key.
|
||||
</Step>
|
||||
<Step title="Copiez la clé API">
|
||||
Copiez la clé API à utiliser dans votre application TypeScript/JavaScript.
|
||||
<Step title="Copy the API key">
|
||||
Copy the API key to use in your TypeScript/JavaScript application.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Callout type="warning">
|
||||
Gardez votre clé API en sécurité et ne la soumettez jamais au contrôle de version. Utilisez des variables d'environnement ou une gestion de configuration sécurisée.
|
||||
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
|
||||
</Callout>
|
||||
|
||||
## Prérequis
|
||||
## Requirements
|
||||
|
||||
- Node.js 16+
|
||||
- TypeScript 5.0+ (pour les projets TypeScript)
|
||||
- TypeScript 5.0+ (for TypeScript projects)
|
||||
|
||||
## Support TypeScript
|
||||
## TypeScript Support
|
||||
|
||||
Le SDK est écrit en TypeScript et offre une sécurité de type complète :
|
||||
The SDK is written in TypeScript and provides full type safety:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
@@ -588,13 +971,13 @@ import {
|
||||
|
||||
// Type-safe client initialization
|
||||
const client: SimStudioClient = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Type-safe workflow execution
|
||||
const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-id', {
|
||||
input: {
|
||||
message: 'Hello, TypeScript!'
|
||||
message: 'Bonjour, TypeScript !'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -602,6 +985,7 @@ const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-i
|
||||
const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id');
|
||||
```
|
||||
|
||||
## Licence
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -38,6 +38,84 @@ curl -X POST \
|
||||
|
||||
Les réponses réussies renvoient le résultat d'exécution sérialisé de l'exécuteur. Les erreurs révèlent des problèmes de validation, d'authentification ou d'échec du workflow.
|
||||
|
||||
## Réponses en streaming
|
||||
|
||||
Activez le streaming en temps réel pour recevoir les résultats du workflow au fur et à mesure qu'ils sont générés, caractère par caractère. Cela est utile pour afficher progressivement les réponses de l'IA aux utilisateurs.
|
||||
|
||||
### Paramètres de requête
|
||||
|
||||
Ajoutez ces paramètres pour activer le streaming :
|
||||
|
||||
- `stream` - Définissez à `true` pour activer le streaming Server-Sent Events (SSE)
|
||||
- `selectedOutputs` - Tableau des sorties de blocs à diffuser en streaming (par exemple, `["agent1.content"]`)
|
||||
|
||||
### Format de sortie de bloc
|
||||
|
||||
Utilisez le format `blockName.attribute` pour spécifier quelles sorties de blocs diffuser en streaming :
|
||||
- Format : `"blockName.attribute"` (par exemple, si vous souhaitez diffuser en streaming le contenu du bloc Agent 1, vous utiliseriez `"agent1.content"`)
|
||||
- Les noms de blocs ne sont pas sensibles à la casse et les espaces sont ignorés
|
||||
|
||||
### Exemple de requête
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Count to five",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
### Format de réponse
|
||||
|
||||
Les réponses en streaming utilisent le format Server-Sent Events (SSE) :
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", three"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
Chaque événement comprend :
|
||||
- **Fragments en streaming** : `{"blockId": "...", "chunk": "text"}` - Texte en temps réel au fur et à mesure qu'il est généré
|
||||
- **Événement final** : `{"event": "done", ...}` - Métadonnées d'exécution et résultats complets
|
||||
- **Terminateur** : `[DONE]` - Signale la fin du flux
|
||||
|
||||
### Streaming de plusieurs blocs
|
||||
|
||||
Lorsque `selectedOutputs` inclut plusieurs blocs, chaque fragment indique quel bloc l'a produit :
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Process this request",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content", "agent2.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
Le champ `blockId` dans chaque fragment vous permet d'acheminer la sortie vers l'élément d'interface utilisateur approprié :
|
||||
|
||||
```
|
||||
data: {"blockId":"agent1-uuid","chunk":"Processing..."}
|
||||
|
||||
data: {"blockId":"agent2-uuid","chunk":"Analyzing..."}
|
||||
|
||||
data: {"blockId":"agent1-uuid","chunk":" complete"}
|
||||
```
|
||||
|
||||
## Référence des sorties
|
||||
|
||||
| Référence | Description |
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
Simの公式Python SDKを使用すると、公式Python SDKを使用してPythonアプリケーションからプログラムでワークフローを実行できます。
|
||||
|
||||
<Callout type="info">
|
||||
Python SDKはPython 3.8以上をサポートし、同期的なワークフロー実行を提供します。現在、すべてのワークフロー実行は同期的です。
|
||||
Python SDKはPython 3.8以上をサポートし、非同期実行、指数バックオフによる自動レート制限、使用状況追跡機能を提供します。
|
||||
</Callout>
|
||||
|
||||
## インストール
|
||||
@@ -70,12 +70,17 @@ result = client.execute_workflow(
|
||||
)
|
||||
```
|
||||
|
||||
**パラメータ:**
|
||||
**パラメータ:**
|
||||
- `workflow_id` (str): 実行するワークフローのID
|
||||
- `input_data` (dict, オプション): ワークフローに渡す入力データ
|
||||
- `timeout` (float, オプション): タイムアウト(秒)(デフォルト:30.0)
|
||||
- `timeout` (float, オプション): タイムアウト(秒)(デフォルト: 30.0)
|
||||
- `stream` (bool, オプション): ストリーミングレスポンスを有効にする(デフォルト: False)
|
||||
- `selected_outputs` (list[str], オプション): `blockName.attribute`形式でストリーミングするブロック出力(例: `["agent1.content"]`)
|
||||
- `async_execution` (bool, オプション): 非同期実行(デフォルト: False)
|
||||
|
||||
**戻り値:** `WorkflowExecutionResult`
|
||||
**戻り値:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
`async_execution=True`の場合、ポーリング用のタスクIDをすぐに返します。それ以外の場合は、完了を待ちます。
|
||||
|
||||
##### get_workflow_status()
|
||||
|
||||
@@ -86,7 +91,7 @@ status = client.get_workflow_status("workflow-id")
|
||||
print("Is deployed:", status.is_deployed)
|
||||
```
|
||||
|
||||
**パラメータ:**
|
||||
**パラメータ:**
|
||||
- `workflow_id` (str): ワークフローのID
|
||||
|
||||
**戻り値:** `WorkflowStatus`
|
||||
@@ -107,28 +112,118 @@ if is_ready:
|
||||
|
||||
**戻り値:** `bool`
|
||||
|
||||
##### execute_workflow_sync()
|
||||
##### get_job_status()
|
||||
|
||||
<Callout type="info">
|
||||
現在、このメソッドは `execute_workflow()` と同一です。すべての実行は同期的に行われるためです。このメソッドは、将来的に非同期実行が追加された際の互換性のために提供されています。
|
||||
</Callout>
|
||||
|
||||
ワークフローを実行します(現在は同期的、`execute_workflow()` と同じ)。
|
||||
非同期ジョブ実行のステータスを取得します。
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
status = client.get_job_status("task-id-from-async-execution")
|
||||
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
|
||||
if status["status"] == "completed":
|
||||
print("Output:", status["output"])
|
||||
```
|
||||
|
||||
**パラメータ:**
|
||||
- `task_id` (str): 非同期実行から返されたタスクID
|
||||
|
||||
**戻り値:** `Dict[str, Any]`
|
||||
|
||||
**レスポンスフィールド:**
|
||||
- `success` (bool): リクエストが成功したかどうか
|
||||
- `taskId` (str): タスクID
|
||||
- `status` (str): 次のいずれか: `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (dict): `startedAt`, `completedAt`, `duration`を含む
|
||||
- `output` (any, オプション): ワークフロー出力(完了時)
|
||||
- `error` (any, オプション): エラー詳細(失敗時)
|
||||
- `estimatedDuration` (int, オプション): 推定所要時間(ミリ秒)(処理中/キュー時)
|
||||
|
||||
##### execute_with_retry()
|
||||
|
||||
指数バックオフを使用してレート制限エラーで自動的に再試行するワークフロー実行。
|
||||
|
||||
```python
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
input_data={"message": "Hello"},
|
||||
timeout=30.0,
|
||||
max_retries=3, # Maximum number of retries
|
||||
initial_delay=1.0, # Initial delay in seconds
|
||||
max_delay=30.0, # Maximum delay in seconds
|
||||
backoff_multiplier=2.0 # Exponential backoff multiplier
|
||||
)
|
||||
```
|
||||
|
||||
**パラメータ:**
|
||||
- `workflow_id` (str): 実行するワークフローのID
|
||||
- `input_data` (dict, optional): ワークフローに渡す入力データ
|
||||
- `timeout` (float): 初期リクエストのタイムアウト(秒)
|
||||
- `input_data` (dict, オプション): ワークフローに渡す入力データ
|
||||
- `timeout` (float, オプション): タイムアウト(秒)
|
||||
- `stream` (bool, オプション): ストリーミングレスポンスを有効にする
|
||||
- `selected_outputs` (list, オプション): ストリーミングするブロック出力
|
||||
- `async_execution` (bool, オプション): 非同期実行
|
||||
- `max_retries` (int, オプション): 最大再試行回数(デフォルト: 3)
|
||||
- `initial_delay` (float, オプション): 初期遅延(秒)(デフォルト: 1.0)
|
||||
- `max_delay` (float, オプション): 最大遅延(秒)(デフォルト: 30.0)
|
||||
- `backoff_multiplier` (float, オプション): バックオフ乗数(デフォルト: 2.0)
|
||||
|
||||
**戻り値:** `WorkflowExecutionResult`
|
||||
**戻り値:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
リトライロジックは、サンダリングハード問題を防ぐために±25%のジッターを伴う指数バックオフ(1秒→2秒→4秒→8秒...)を使用します。APIが `retry-after` ヘッダーを提供する場合、代わりにそれが使用されます。
|
||||
|
||||
##### get_rate_limit_info()
|
||||
|
||||
最後のAPIレスポンスから現在のレート制限情報を取得します。
|
||||
|
||||
```python
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
print("Limit:", rate_limit_info.limit)
|
||||
print("Remaining:", rate_limit_info.remaining)
|
||||
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
|
||||
```
|
||||
|
||||
**戻り値:** `RateLimitInfo | None`
|
||||
|
||||
##### get_usage_limits()
|
||||
|
||||
アカウントの現在の使用制限とクォータ情報を取得します。
|
||||
|
||||
```python
|
||||
limits = client.get_usage_limits()
|
||||
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
|
||||
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
|
||||
print("Current period cost:", limits.usage["currentPeriodCost"])
|
||||
print("Plan:", limits.usage["plan"])
|
||||
```
|
||||
|
||||
**戻り値:** `UsageLimits`
|
||||
|
||||
**レスポンス構造:**
|
||||
|
||||
```python
|
||||
{
|
||||
"success": bool,
|
||||
"rateLimit": {
|
||||
"sync": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"async": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"authType": str # 'api' or 'manual'
|
||||
},
|
||||
"usage": {
|
||||
"currentPeriodCost": float,
|
||||
"limit": float,
|
||||
"plan": str # e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### set_api_key()
|
||||
|
||||
@@ -170,6 +265,18 @@ class WorkflowExecutionResult:
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class AsyncExecutionResult:
|
||||
success: bool
|
||||
task_id: str
|
||||
status: str # 'queued'
|
||||
created_at: str
|
||||
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@@ -181,6 +288,27 @@ class WorkflowStatus:
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class RateLimitInfo:
|
||||
limit: int
|
||||
remaining: int
|
||||
reset: int
|
||||
retry_after: Optional[int] = None
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UsageLimits:
|
||||
success: bool
|
||||
rate_limit: Dict[str, Any]
|
||||
usage: Dict[str, Any]
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
@@ -191,6 +319,13 @@ class SimStudioError(Exception):
|
||||
self.status = status
|
||||
```
|
||||
|
||||
**一般的なエラーコード:**
|
||||
- `UNAUTHORIZED`: 無効なAPIキー
|
||||
- `TIMEOUT`: リクエストがタイムアウトしました
|
||||
- `RATE_LIMIT_EXCEEDED`: レート制限を超えました
|
||||
- `USAGE_LIMIT_EXCEEDED`: 使用制限を超えました
|
||||
- `EXECUTION_ERROR`: ワークフローの実行に失敗しました
|
||||
|
||||
## 例
|
||||
|
||||
### 基本的なワークフロー実行
|
||||
@@ -214,7 +349,7 @@ class SimStudioError(Exception):
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
@@ -252,7 +387,7 @@ run_workflow()
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
@@ -277,14 +412,14 @@ def execute_with_error_handling():
|
||||
|
||||
### コンテキストマネージャーの使用
|
||||
|
||||
リソースのクリーンアップを自動的に処理するためにコンテキストマネージャーとしてクライアントを使用します:
|
||||
リソースのクリーンアップを自動的に処理するためにクライアントをコンテキストマネージャーとして使用します:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
@@ -298,7 +433,7 @@ with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
@@ -339,6 +474,230 @@ for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
### 非同期ワークフロー実行
|
||||
|
||||
長時間実行されるタスクのためにワークフローを非同期で実行します:
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_async():
|
||||
try:
|
||||
# Start async execution
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"data": "large dataset"},
|
||||
async_execution=True # Execute asynchronously
|
||||
)
|
||||
|
||||
# Check if result is an async execution
|
||||
if hasattr(result, 'task_id'):
|
||||
print(f"Task ID: {result.task_id}")
|
||||
print(f"Status endpoint: {result.links['status']}")
|
||||
|
||||
# Poll for completion
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
while status["status"] in ["queued", "processing"]:
|
||||
print(f"Current status: {status['status']}")
|
||||
time.sleep(2) # Wait 2 seconds
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
if status["status"] == "completed":
|
||||
print("Workflow completed!")
|
||||
print(f"Output: {status['output']}")
|
||||
print(f"Duration: {status['metadata']['duration']}")
|
||||
else:
|
||||
print(f"Workflow failed: {status['error']}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
execute_async()
|
||||
```
|
||||
|
||||
### レート制限とリトライ
|
||||
|
||||
指数バックオフを使用して自動的にレート制限を処理します:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_retry_handling():
|
||||
try:
|
||||
# Automatically retries on rate limit
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"message": "Process this"},
|
||||
max_retries=5,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
backoff_multiplier=2.0
|
||||
)
|
||||
|
||||
print(f"Success: {result}")
|
||||
except SimStudioError as error:
|
||||
if error.code == "RATE_LIMIT_EXCEEDED":
|
||||
print("Rate limit exceeded after all retries")
|
||||
|
||||
# Check rate limit info
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
from datetime import datetime
|
||||
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
|
||||
print(f"Rate limit resets at: {reset_time}")
|
||||
|
||||
execute_with_retry_handling()
|
||||
```
|
||||
|
||||
### 使用状況モニタリング
|
||||
|
||||
アカウントの使用状況と制限をモニタリングします:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def check_usage():
|
||||
try:
|
||||
limits = client.get_usage_limits()
|
||||
|
||||
print("=== Rate Limits ===")
|
||||
print("Sync requests:")
|
||||
print(f" Limit: {limits.rate_limit['sync']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
|
||||
|
||||
print("\nAsync requests:")
|
||||
print(f" Limit: {limits.rate_limit['async']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
|
||||
print(f"Limit: ${limits.usage['limit']:.2f}")
|
||||
print(f"Plan: {limits.usage['plan']}")
|
||||
|
||||
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
|
||||
print(f"Usage: {percent_used:.1f}%")
|
||||
|
||||
if percent_used > 80:
|
||||
print("⚠️ Warning: You are approaching your usage limit!")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error checking usage: {error}")
|
||||
|
||||
check_usage()
|
||||
```
|
||||
|
||||
### ワークフローの実行ストリーミング
|
||||
|
||||
リアルタイムのストリーミングレスポンスでワークフローを実行します:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_streaming():
|
||||
"""Execute workflow with streaming enabled."""
|
||||
try:
|
||||
# Enable streaming for specific block outputs
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Count to five"},
|
||||
stream=True,
|
||||
selected_outputs=["agent1.content"] # Use blockName.attribute format
|
||||
)
|
||||
|
||||
print("Workflow result:", result)
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
execute_with_streaming()
|
||||
```
|
||||
|
||||
ストリーミングレスポンスはServer-Sent Events(SSE)形式に従います:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Flaskストリーミングの例:**
|
||||
|
||||
```python
|
||||
from flask import Flask, Response, stream_with_context
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/stream-workflow')
|
||||
def stream_workflow():
|
||||
"""Stream workflow execution to the client."""
|
||||
|
||||
def generate():
|
||||
response = requests.post(
|
||||
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': os.getenv('SIM_API_KEY')
|
||||
},
|
||||
json={
|
||||
'message': 'Generate a story',
|
||||
'stream': True,
|
||||
'selectedOutputs': ['agent1.content']
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
data = decoded_line[6:] # Remove 'data: ' prefix
|
||||
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
if 'chunk' in parsed:
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
elif parsed.get('event') == 'done':
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
print("Execution complete:", parsed.get('metadata'))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
### 環境設定
|
||||
|
||||
環境変数を使用してクライアントを設定します:
|
||||
@@ -352,8 +711,8 @@ for result in results:
|
||||
|
||||
# Development configuration
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
api_key=os.getenv("SIM_API_KEY")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -365,13 +724,13 @@ for result in results:
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Production configuration with error handling
|
||||
api_key = os.getenv("SIMSTUDIO_API_KEY")
|
||||
api_key = os.getenv("SIM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("SIMSTUDIO_API_KEY environment variable is required")
|
||||
raise ValueError("SIM_API_KEY environment variable is required")
|
||||
|
||||
client = SimStudioClient(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
公式TypeScript/JavaScript SDKはSimのために完全な型安全性を提供し、Node.jsとブラウザ環境の両方をサポートしています。これにより、Node.jsアプリケーション、Webアプリケーション、その他のJavaScript環境からプログラムでワークフローを実行することができます。現在、すべてのワークフロー実行は同期的に行われます。
|
||||
Sim用の公式TypeScript/JavaScript SDKは、完全な型安全性を提供し、Node.jsとブラウザ環境の両方をサポートしています。これにより、Node.jsアプリケーション、Webアプリケーション、その他のJavaScript環境からプログラムによってワークフローを実行することができます。
|
||||
|
||||
<Callout type="info">
|
||||
TypeScript SDKは完全な型安全性を提供し、Node.jsとブラウザ環境の両方をサポートしています。現在、すべてのワークフロー実行は同期的に行われます。
|
||||
TypeScript SDKは、完全な型安全性、非同期実行サポート、指数バックオフによる自動レート制限、使用状況追跡を提供します。
|
||||
</Callout>
|
||||
|
||||
## インストール
|
||||
@@ -95,8 +95,13 @@ const result = await client.executeWorkflow('workflow-id', {
|
||||
- `options` (ExecutionOptions, オプション):
|
||||
- `input` (any): ワークフローに渡す入力データ
|
||||
- `timeout` (number): タイムアウト(ミリ秒)(デフォルト: 30000)
|
||||
- `stream` (boolean): ストリーミングレスポンスを有効にする(デフォルト: false)
|
||||
- `selectedOutputs` (string[]): `blockName.attribute`形式でストリーミングするブロック出力(例: `["agent1.content"]`)
|
||||
- `async` (boolean): 非同期実行(デフォルト: false)
|
||||
|
||||
**戻り値:** `Promise<WorkflowExecutionResult>`
|
||||
**戻り値:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
`async: true`の場合、ポーリング用のタスクIDをすぐに返します。それ以外の場合は、完了を待ちます。
|
||||
|
||||
##### getWorkflowStatus()
|
||||
|
||||
@@ -114,7 +119,7 @@ console.log('Is deployed:', status.isDeployed);
|
||||
|
||||
##### validateWorkflow()
|
||||
|
||||
ワークフローが実行準備ができているか検証します。
|
||||
ワークフローが実行準備ができているかを検証します。
|
||||
|
||||
```typescript
|
||||
const isReady = await client.validateWorkflow('workflow-id');
|
||||
@@ -128,28 +133,117 @@ if (isReady) {
|
||||
|
||||
**戻り値:** `Promise<boolean>`
|
||||
|
||||
##### executeWorkflowSync()
|
||||
##### getJobStatus()
|
||||
|
||||
<Callout type="info">
|
||||
現在、このメソッドは `executeWorkflow()` と同一です。すべての実行は同期的に行われるためです。このメソッドは、将来的に非同期実行が追加された際の互換性のために提供されています。
|
||||
</Callout>
|
||||
|
||||
ワークフローを実行します(現在は同期的、`executeWorkflow()` と同じ)。
|
||||
非同期ジョブ実行のステータスを取得します。
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWorkflowSync('workflow-id', {
|
||||
input: { data: 'some input' },
|
||||
timeout: 60000
|
||||
const status = await client.getJobStatus('task-id-from-async-execution');
|
||||
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
|
||||
if (status.status === 'completed') {
|
||||
console.log('Output:', status.output);
|
||||
}
|
||||
```
|
||||
|
||||
**パラメータ:**
|
||||
- `taskId` (string): 非同期実行から返されたタスクID
|
||||
|
||||
**戻り値:** `Promise<JobStatus>`
|
||||
|
||||
**レスポンスフィールド:**
|
||||
- `success` (boolean): リクエストが成功したかどうか
|
||||
- `taskId` (string): タスクID
|
||||
- `status` (string): 次のいずれか `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (object): `startedAt`, `completedAt`, および `duration` を含む
|
||||
- `output` (any, オプション): ワークフロー出力(完了時)
|
||||
- `error` (any, オプション): エラー詳細(失敗時)
|
||||
- `estimatedDuration` (number, オプション): 推定所要時間(ミリ秒)(処理中/キュー時)
|
||||
|
||||
##### executeWithRetry()
|
||||
|
||||
レート制限エラー時に指数バックオフを使用して自動的に再試行するワークフロー実行。
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Hello' },
|
||||
timeout: 30000
|
||||
}, {
|
||||
maxRetries: 3, // Maximum number of retries
|
||||
initialDelay: 1000, // Initial delay in ms (1 second)
|
||||
maxDelay: 30000, // Maximum delay in ms (30 seconds)
|
||||
backoffMultiplier: 2 // Exponential backoff multiplier
|
||||
});
|
||||
```
|
||||
|
||||
**パラメータ:**
|
||||
- `workflowId` (string): 実行するワークフローのID
|
||||
- `options` (ExecutionOptions, オプション):
|
||||
- `input` (any): ワークフローに渡す入力データ
|
||||
- `timeout` (number): 初期リクエストのタイムアウト(ミリ秒)
|
||||
- `options` (ExecutionOptions, オプション): `executeWorkflow()`と同じ
|
||||
- `retryOptions` (RetryOptions, オプション):
|
||||
- `maxRetries` (number): 最大再試行回数(デフォルト: 3)
|
||||
- `initialDelay` (number): 初期遅延(ミリ秒)(デフォルト: 1000)
|
||||
- `maxDelay` (number): 最大遅延(ミリ秒)(デフォルト: 30000)
|
||||
- `backoffMultiplier` (number): バックオフ乗数(デフォルト: 2)
|
||||
|
||||
**戻り値:** `Promise<WorkflowExecutionResult>`
|
||||
**戻り値:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
再試行ロジックは、サンダリングハード問題を防ぐために±25%のジッターを含む指数バックオフ(1秒→2秒→4秒→8秒...)を使用します。APIが`retry-after`ヘッダーを提供する場合、代わりにそれが使用されます。
|
||||
|
||||
##### getRateLimitInfo()
|
||||
|
||||
最後のAPIレスポンスから現在のレート制限情報を取得します。
|
||||
|
||||
```typescript
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Limit:', rateLimitInfo.limit);
|
||||
console.log('Remaining:', rateLimitInfo.remaining);
|
||||
console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
```
|
||||
|
||||
**戻り値:** `RateLimitInfo | null`
|
||||
|
||||
##### getUsageLimits()
|
||||
|
||||
アカウントの現在の使用制限とクォータ情報を取得します。
|
||||
|
||||
```typescript
|
||||
const limits = await client.getUsageLimits();
|
||||
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
|
||||
console.log('Current period cost:', limits.usage.currentPeriodCost);
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
```
|
||||
|
||||
**戻り値:** `Promise<UsageLimits>`
|
||||
|
||||
**レスポンス構造:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
success: boolean
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
authType: string // 'api' or 'manual'
|
||||
}
|
||||
usage: {
|
||||
currentPeriodCost: number
|
||||
limit: number
|
||||
plan: string // e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### setApiKey()
|
||||
|
||||
@@ -167,7 +261,7 @@ client.setApiKey('new-api-key');
|
||||
client.setBaseUrl('https://my-custom-domain.com');
|
||||
```
|
||||
|
||||
## 型
|
||||
## 型定義
|
||||
|
||||
### WorkflowExecutionResult
|
||||
|
||||
@@ -187,6 +281,20 @@ interface WorkflowExecutionResult {
|
||||
}
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```typescript
|
||||
interface AsyncExecutionResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
status: 'queued';
|
||||
createdAt: string;
|
||||
links: {
|
||||
status: string; // e.g., "/api/jobs/{taskId}"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```typescript
|
||||
@@ -198,6 +306,45 @@ interface WorkflowStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```typescript
|
||||
interface RateLimitInfo {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```typescript
|
||||
interface UsageLimits {
|
||||
success: boolean;
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
async: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
authType: string;
|
||||
};
|
||||
usage: {
|
||||
currentPeriodCost: number;
|
||||
limit: number;
|
||||
plan: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```typescript
|
||||
@@ -207,9 +354,16 @@ class SimStudioError extends Error {
|
||||
}
|
||||
```
|
||||
|
||||
**一般的なエラーコード:**
|
||||
- `UNAUTHORIZED`: 無効なAPIキー
|
||||
- `TIMEOUT`: リクエストがタイムアウトしました
|
||||
- `RATE_LIMIT_EXCEEDED`: レート制限を超えました
|
||||
- `USAGE_LIMIT_EXCEEDED`: 使用制限を超えました
|
||||
- `EXECUTION_ERROR`: ワークフローの実行に失敗しました
|
||||
|
||||
## 例
|
||||
|
||||
### 基本的なワークフローの実行
|
||||
### 基本的なワークフロー実行
|
||||
|
||||
<Steps>
|
||||
<Step title="クライアントの初期化">
|
||||
@@ -230,7 +384,7 @@ class SimStudioError extends Error {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function runWorkflow() {
|
||||
@@ -271,7 +425,7 @@ runWorkflow();
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithErrorHandling() {
|
||||
@@ -315,14 +469,14 @@ async function executeWithErrorHandling() {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Development configuration
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL // optional
|
||||
baseUrl: process.env.SIM_BASE_URL // optional
|
||||
});
|
||||
```
|
||||
|
||||
@@ -333,21 +487,21 @@ async function executeWithErrorHandling() {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Production configuration with validation
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://sim.ai'
|
||||
baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
|
||||
});
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Node.js Expressとの統合
|
||||
### Node.js Express統合
|
||||
|
||||
Express.jsサーバーとの統合:
|
||||
|
||||
@@ -357,7 +511,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const app = express();
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
@@ -399,7 +553,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -430,7 +584,7 @@ export default async function handler(
|
||||
|
||||
### ブラウザでの使用
|
||||
|
||||
ブラウザで使用する場合(適切なCORS設定が必要):
|
||||
ブラウザでの使用(適切なCORS設定が必要):
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
@@ -464,7 +618,7 @@ document.getElementById('executeBtn')?.addEventListener('click', executeClientSi
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
ブラウザでSDKを使用する際は、機密性の高いAPIキーを公開しないよう注意してください。バックエンドプロキシや権限が制限された公開APIキーの使用を検討してください。
|
||||
ブラウザでSDKを使用する場合、機密性の高いAPIキーを公開しないよう注意してください。バックエンドプロキシや権限が制限された公開APIキーの使用を検討してください。
|
||||
</Callout>
|
||||
|
||||
### Reactフックの例
|
||||
@@ -476,7 +630,7 @@ import { useState, useCallback } from 'react';
|
||||
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
interface UseWorkflowResult {
|
||||
@@ -532,7 +686,7 @@ function WorkflowComponent() {
|
||||
<button onClick={handleExecute} disabled={loading}>
|
||||
{loading ? 'Executing...' : 'Execute Workflow'}
|
||||
</button>
|
||||
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{result && (
|
||||
<div>
|
||||
@@ -545,38 +699,267 @@ function WorkflowComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
## APIキーの取得方法
|
||||
### 非同期ワークフロー実行
|
||||
|
||||
長時間実行タスク向けに非同期でワークフローを実行:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeAsync() {
|
||||
try {
|
||||
// Start async execution
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { data: 'large dataset' },
|
||||
async: true // Execute asynchronously
|
||||
});
|
||||
|
||||
// Check if result is an async execution
|
||||
if ('taskId' in result) {
|
||||
console.log('Task ID:', result.taskId);
|
||||
console.log('Status endpoint:', result.links.status);
|
||||
|
||||
// Poll for completion
|
||||
let status = await client.getJobStatus(result.taskId);
|
||||
|
||||
while (status.status === 'queued' || status.status === 'processing') {
|
||||
console.log('Current status:', status.status);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||
status = await client.getJobStatus(result.taskId);
|
||||
}
|
||||
|
||||
if (status.status === 'completed') {
|
||||
console.log('Workflow completed!');
|
||||
console.log('Output:', status.output);
|
||||
console.log('Duration:', status.metadata.duration);
|
||||
} else {
|
||||
console.error('Workflow failed:', status.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
executeAsync();
|
||||
```
|
||||
|
||||
### レート制限とリトライ
|
||||
|
||||
指数バックオフによるレート制限の自動処理:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithRetryHandling() {
|
||||
try {
|
||||
// Automatically retries on rate limit
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Process this' }
|
||||
}, {
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 60000,
|
||||
backoffMultiplier: 2
|
||||
});
|
||||
|
||||
console.log('Success:', result);
|
||||
} catch (error) {
|
||||
if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
console.error('Rate limit exceeded after all retries');
|
||||
|
||||
// Check rate limit info
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用状況モニタリング
|
||||
|
||||
アカウントの使用状況と制限のモニタリング:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function checkUsage() {
|
||||
try {
|
||||
const limits = await client.getUsageLimits();
|
||||
|
||||
console.log('=== Rate Limits ===');
|
||||
console.log('Sync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.sync.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.sync.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.sync.isLimited);
|
||||
|
||||
console.log('\nAsync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.async.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.async.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.async.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.async.isLimited);
|
||||
|
||||
console.log('\n=== Usage ===');
|
||||
console.log('Current period cost:
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithStreaming() {
|
||||
try {
|
||||
// 特定のブロック出力のストリーミングを有効化
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { message: 'Count to five' },
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content'] // blockName.attribute形式を使用
|
||||
});
|
||||
|
||||
console.log('ワークフロー結果:', result);
|
||||
} catch (error) {
|
||||
console.error('エラー:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**React Streaming Example:**
|
||||
|
||||
```typescript
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function StreamingWorkflow() {
|
||||
const [output, setOutput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const executeStreaming = async () => {
|
||||
setLoading(true);
|
||||
setOutput('');
|
||||
|
||||
// IMPORTANT: Make this API call from your backend server, not the browser
|
||||
// Never expose your API key in client-side code
|
||||
const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.SIM_API_KEY! // Server-side environment variable only
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: 'Generate a story',
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content']
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.chunk) {
|
||||
setOutput(prev => prev + parsed.chunk);
|
||||
} else if (parsed.event === 'done') {
|
||||
console.log('Execution complete:', parsed.metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={executeStreaming} disabled={loading}>
|
||||
{loading ? 'Generating...' : 'Start Streaming'}
|
||||
</button>
|
||||
<div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
<Steps>
|
||||
<Step title="Simにログイン">
|
||||
[Sim](https://sim.ai)に移動してアカウントにログインします。
|
||||
<Step title="Log in to Sim">
|
||||
Navigate to [Sim](https://sim.ai) and log in to your account.
|
||||
</Step>
|
||||
<Step title="ワークフローを開く">
|
||||
プログラムで実行したいワークフローに移動します。
|
||||
<Step title="Open your workflow">
|
||||
Navigate to the workflow you want to execute programmatically.
|
||||
</Step>
|
||||
<Step title="ワークフローをデプロイ">
|
||||
まだデプロイされていない場合は、「デプロイ」をクリックしてワークフローをデプロイします。
|
||||
<Step title="Deploy your workflow">
|
||||
Click on "Deploy" to deploy your workflow if it hasn't been deployed yet.
|
||||
</Step>
|
||||
<Step title="APIキーを作成または選択">
|
||||
デプロイ処理中に、APIキーを選択または作成します。
|
||||
<Step title="Create or select an API key">
|
||||
During the deployment process, select or create an API key.
|
||||
</Step>
|
||||
<Step title="APIキーをコピー">
|
||||
TypeScript/JavaScriptアプリケーションで使用するAPIキーをコピーします。
|
||||
<Step title="Copy the API key">
|
||||
Copy the API key to use in your TypeScript/JavaScript application.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Callout type="warning">
|
||||
APIキーは安全に保管し、バージョン管理システムにコミットしないでください。環境変数や安全な設定管理を使用してください。
|
||||
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
|
||||
</Callout>
|
||||
|
||||
## 要件
|
||||
## Requirements
|
||||
|
||||
- Node.js 16以上
|
||||
- TypeScript 5.0以上(TypeScriptプロジェクトの場合)
|
||||
- Node.js 16+
|
||||
- TypeScript 5.0+ (for TypeScript projects)
|
||||
|
||||
## TypeScriptサポート
|
||||
## TypeScript Support
|
||||
|
||||
このSDKはTypeScriptで書かれており、完全な型安全性を提供します:
|
||||
The SDK is written in TypeScript and provides full type safety:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
@@ -588,7 +971,7 @@ import {
|
||||
|
||||
// Type-safe client initialization
|
||||
const client: SimStudioClient = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Type-safe workflow execution
|
||||
@@ -602,6 +985,7 @@ const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-i
|
||||
const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id');
|
||||
```
|
||||
|
||||
## ライセンス
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
|
||||
Apache-2.0
|
||||
|
||||
@@ -38,6 +38,84 @@ curl -X POST \
|
||||
|
||||
成功したレスポンスはエグゼキュータからシリアル化された実行結果を返します。エラーは検証、認証、またはワークフローの失敗を表示します。
|
||||
|
||||
## ストリーミングレスポンス
|
||||
|
||||
リアルタイムストリーミングを有効にすると、ワークフローの出力が生成されるたびに文字単位で受信できます。これはAIの応答をユーザーに段階的に表示するのに役立ちます。
|
||||
|
||||
### リクエストパラメータ
|
||||
|
||||
ストリーミングを有効にするには、これらのパラメータを追加してください:
|
||||
|
||||
- `stream` - Server-Sent Events (SSE)ストリーミングを有効にするには `true` に設定します
|
||||
- `selectedOutputs` - ストリーミングするブロック出力の配列(例:`["agent1.content"]`)
|
||||
|
||||
### ブロック出力フォーマット
|
||||
|
||||
`blockName.attribute` フォーマットを使用して、ストリーミングするブロック出力を指定します:
|
||||
- フォーマット:`"blockName.attribute"`(例:Agent 1ブロックの内容をストリーミングしたい場合は、`"agent1.content"` を使用します)
|
||||
- ブロック名は大文字小文字を区別せず、スペースは無視されます
|
||||
|
||||
### リクエスト例
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Count to five",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
### レスポンスフォーマット
|
||||
|
||||
ストリーミングレスポンスはServer-Sent Events (SSE)フォーマットを使用します:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", three"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
各イベントには以下が含まれます:
|
||||
- **ストリーミングチャンク**:`{"blockId": "...", "chunk": "text"}` - 生成されるリアルタイムテキスト
|
||||
- **最終イベント**:`{"event": "done", ...}` - 実行メタデータと完全な結果
|
||||
- **ターミネーター**:`[DONE]` - ストリーム終了を示す信号
|
||||
|
||||
### 複数ブロックのストリーミング
|
||||
|
||||
`selectedOutputs` に複数のブロックが含まれる場合、各チャンクはどのブロックから生成されたかを示します:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Process this request",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content", "agent2.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
各チャンクの `blockId` フィールドを使用して、出力を正しいUI要素にルーティングできます:
|
||||
|
||||
```
|
||||
data: {"blockId":"agent1-uuid","chunk":"Processing..."}
|
||||
|
||||
data: {"blockId":"agent2-uuid","chunk":"Analyzing..."}
|
||||
|
||||
data: {"blockId":"agent1-uuid","chunk":" complete"}
|
||||
```
|
||||
|
||||
## 出力リファレンス
|
||||
|
||||
| リファレンス | 説明 |
|
||||
@@ -45,7 +123,7 @@ curl -X POST \
|
||||
| `<api.field>` | 入力フォーマットで定義されたフィールド |
|
||||
| `<api.input>` | 構造化されたリクエスト本文全体 |
|
||||
|
||||
入力フォーマットが定義されていない場合、エグゼキュータは生のJSONを `<api.input>` のみで公開します。
|
||||
入力フォーマットが定義されていない場合、エグゼキューターは `<api.input>` でのみ生のJSONを公開します。
|
||||
|
||||
<Callout type="warning">
|
||||
ワークフローには1つのAPIトリガーのみ含めることができます。変更後は新しいデプロイメントを公開して、エンドポイントを最新の状態に保ってください。
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
官方的 Python SDK 允许您通过 Python 应用程序以编程方式执行工作流。
|
||||
|
||||
<Callout type="info">
|
||||
Python SDK 支持 Python 3.8+,并提供同步工作流执行。目前所有工作流执行均为同步模式。
|
||||
Python SDK 支持 Python 3.8+,具备异步执行支持、自动速率限制(带指数退避)以及使用情况跟踪功能。
|
||||
</Callout>
|
||||
|
||||
## 安装
|
||||
@@ -72,10 +72,15 @@ result = client.execute_workflow(
|
||||
|
||||
**参数:**
|
||||
- `workflow_id` (str): 要执行的工作流 ID
|
||||
- `input_data` (dict, 可选): 传递给工作流的输入数据
|
||||
- `timeout` (float, 可选): 超时时间(以秒为单位,默认值:30.0)
|
||||
- `input_data` (dict, optional): 传递给工作流的输入数据
|
||||
- `timeout` (float, optional): 超时时间(以秒为单位,默认值:30.0)
|
||||
- `stream` (bool, optional): 启用流式响应(默认值:False)
|
||||
- `selected_outputs` (list[str], optional): 以 `blockName.attribute` 格式阻止输出流(例如,`["agent1.content"]`)
|
||||
- `async_execution` (bool, optional): 异步执行(默认值:False)
|
||||
|
||||
**返回值:** `WorkflowExecutionResult`
|
||||
**返回值:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
当 `async_execution=True` 时,立即返回任务 ID 以供轮询。否则,等待完成。
|
||||
|
||||
##### get_workflow_status()
|
||||
|
||||
@@ -103,32 +108,122 @@ if is_ready:
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `workflow_id` (str):工作流的 ID
|
||||
- `workflow_id` (str): 工作流的 ID
|
||||
|
||||
**返回值:** `bool`
|
||||
|
||||
##### execute_workflow_sync()
|
||||
##### get_job_status()
|
||||
|
||||
<Callout type="info">
|
||||
当前,此方法与 `execute_workflow()` 相同,因为所有执行都是同步的。提供此方法是为了在将来添加异步执行时保持兼容性。
|
||||
</Callout>
|
||||
|
||||
执行工作流(当前为同步,与 `execute_workflow()` 相同)。
|
||||
获取异步任务执行的状态。
|
||||
|
||||
```python
|
||||
result = client.execute_workflow_sync(
|
||||
status = client.get_job_status("task-id-from-async-execution")
|
||||
print("Status:", status["status"]) # 'queued', 'processing', 'completed', 'failed'
|
||||
if status["status"] == "completed":
|
||||
print("Output:", status["output"])
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `task_id` (str): 异步执行返回的任务 ID
|
||||
|
||||
**返回值:** `Dict[str, Any]`
|
||||
|
||||
**响应字段:**
|
||||
- `success` (bool): 请求是否成功
|
||||
- `taskId` (str): 任务 ID
|
||||
- `status` (str): 可能的值包括 `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (dict): 包含 `startedAt`, `completedAt` 和 `duration`
|
||||
- `output` (any, optional): 工作流输出(完成时)
|
||||
- `error` (any, optional): 错误详情(失败时)
|
||||
- `estimatedDuration` (int, optional): 估计持续时间(以毫秒为单位,处理中/排队时)
|
||||
|
||||
##### execute_with_retry()
|
||||
|
||||
使用指数退避在速率限制错误上自动重试执行工作流。
|
||||
|
||||
```python
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"data": "some input"},
|
||||
timeout=60.0
|
||||
input_data={"message": "Hello"},
|
||||
timeout=30.0,
|
||||
max_retries=3, # Maximum number of retries
|
||||
initial_delay=1.0, # Initial delay in seconds
|
||||
max_delay=30.0, # Maximum delay in seconds
|
||||
backoff_multiplier=2.0 # Exponential backoff multiplier
|
||||
)
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `workflow_id` (str):要执行的工作流 ID
|
||||
- `input_data` (dict, optional):传递给工作流的输入数据
|
||||
- `timeout` (float):初始请求的超时时间(以秒为单位)
|
||||
- `workflow_id` (str): 要执行的工作流 ID
|
||||
- `input_data` (dict, optional): 传递给工作流的输入数据
|
||||
- `timeout` (float, optional): 超时时间(以秒为单位)
|
||||
- `stream` (bool, optional): 启用流式响应
|
||||
- `selected_outputs` (list, optional): 阻止输出流
|
||||
- `async_execution` (bool, optional): 异步执行
|
||||
- `max_retries` (int, optional): 最大重试次数(默认值:3)
|
||||
- `initial_delay` (float, optional): 初始延迟时间(以秒为单位,默认值:1.0)
|
||||
- `max_delay` (float, optional): 最大延迟时间(以秒为单位,默认值:30.0)
|
||||
- `backoff_multiplier` (float, optional): 退避倍数(默认值:2.0)
|
||||
|
||||
**返回值:** `WorkflowExecutionResult`
|
||||
**返回值:** `WorkflowExecutionResult | AsyncExecutionResult`
|
||||
|
||||
重试逻辑使用指数退避(1 秒 → 2 秒 → 4 秒 → 8 秒...),并带有 ±25% 的抖动以防止惊群效应。如果 API 提供了 `retry-after` 标头,则会使用该标头。
|
||||
|
||||
##### get_rate_limit_info()
|
||||
|
||||
从上一次 API 响应中获取当前的速率限制信息。
|
||||
|
||||
```python
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
print("Limit:", rate_limit_info.limit)
|
||||
print("Remaining:", rate_limit_info.remaining)
|
||||
print("Reset:", datetime.fromtimestamp(rate_limit_info.reset))
|
||||
```
|
||||
|
||||
**返回值:** `RateLimitInfo | None`
|
||||
|
||||
##### get_usage_limits()
|
||||
|
||||
获取您的账户当前的使用限制和配额信息。
|
||||
|
||||
```python
|
||||
limits = client.get_usage_limits()
|
||||
print("Sync requests remaining:", limits.rate_limit["sync"]["remaining"])
|
||||
print("Async requests remaining:", limits.rate_limit["async"]["remaining"])
|
||||
print("Current period cost:", limits.usage["currentPeriodCost"])
|
||||
print("Plan:", limits.usage["plan"])
|
||||
```
|
||||
|
||||
**返回值:** `UsageLimits`
|
||||
|
||||
**响应结构:**
|
||||
|
||||
```python
|
||||
{
|
||||
"success": bool,
|
||||
"rateLimit": {
|
||||
"sync": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"async": {
|
||||
"isLimited": bool,
|
||||
"limit": int,
|
||||
"remaining": int,
|
||||
"resetAt": str
|
||||
},
|
||||
"authType": str # 'api' or 'manual'
|
||||
},
|
||||
"usage": {
|
||||
"currentPeriodCost": float,
|
||||
"limit": float,
|
||||
"plan": str # e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### set_api_key()
|
||||
|
||||
@@ -170,6 +265,18 @@ class WorkflowExecutionResult:
|
||||
total_duration: Optional[float] = None
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class AsyncExecutionResult:
|
||||
success: bool
|
||||
task_id: str
|
||||
status: str # 'queued'
|
||||
created_at: str
|
||||
links: Dict[str, str] # e.g., {"status": "/api/jobs/{taskId}"}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```python
|
||||
@@ -181,6 +288,27 @@ class WorkflowStatus:
|
||||
needs_redeployment: bool = False
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class RateLimitInfo:
|
||||
limit: int
|
||||
remaining: int
|
||||
reset: int
|
||||
retry_after: Optional[int] = None
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UsageLimits:
|
||||
success: bool
|
||||
rate_limit: Dict[str, Any]
|
||||
usage: Dict[str, Any]
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```python
|
||||
@@ -191,6 +319,13 @@ class SimStudioError(Exception):
|
||||
self.status = status
|
||||
```
|
||||
|
||||
**常见错误代码:**
|
||||
- `UNAUTHORIZED`: 无效的 API 密钥
|
||||
- `TIMEOUT`: 请求超时
|
||||
- `RATE_LIMIT_EXCEEDED`: 超出速率限制
|
||||
- `USAGE_LIMIT_EXCEEDED`: 超出使用限制
|
||||
- `EXECUTION_ERROR`: 工作流执行失败
|
||||
|
||||
## 示例
|
||||
|
||||
### 基本工作流执行
|
||||
@@ -214,7 +349,7 @@ class SimStudioError(Exception):
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def run_workflow():
|
||||
try:
|
||||
@@ -252,7 +387,7 @@ run_workflow()
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_error_handling():
|
||||
try:
|
||||
@@ -284,7 +419,7 @@ from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
# Using context manager to automatically close the session
|
||||
with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
with SimStudioClient(api_key=os.getenv("SIM_API_KEY")) as client:
|
||||
result = client.execute_workflow("workflow-id")
|
||||
print("Result:", result)
|
||||
# Session is automatically closed here
|
||||
@@ -298,7 +433,7 @@ with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client:
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY"))
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_workflows_batch(workflow_data_pairs):
|
||||
"""Execute multiple workflows with different input data."""
|
||||
@@ -339,6 +474,230 @@ for result in results:
|
||||
print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}")
|
||||
```
|
||||
|
||||
### 异步工作流执行
|
||||
|
||||
为长时间运行的任务异步执行工作流:
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_async():
|
||||
try:
|
||||
# Start async execution
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"data": "large dataset"},
|
||||
async_execution=True # Execute asynchronously
|
||||
)
|
||||
|
||||
# Check if result is an async execution
|
||||
if hasattr(result, 'task_id'):
|
||||
print(f"Task ID: {result.task_id}")
|
||||
print(f"Status endpoint: {result.links['status']}")
|
||||
|
||||
# Poll for completion
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
while status["status"] in ["queued", "processing"]:
|
||||
print(f"Current status: {status['status']}")
|
||||
time.sleep(2) # Wait 2 seconds
|
||||
status = client.get_job_status(result.task_id)
|
||||
|
||||
if status["status"] == "completed":
|
||||
print("Workflow completed!")
|
||||
print(f"Output: {status['output']}")
|
||||
print(f"Duration: {status['metadata']['duration']}")
|
||||
else:
|
||||
print(f"Workflow failed: {status['error']}")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error: {error}")
|
||||
|
||||
execute_async()
|
||||
```
|
||||
|
||||
### 速率限制与重试
|
||||
|
||||
通过指数退避自动处理速率限制:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient, SimStudioError
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_retry_handling():
|
||||
try:
|
||||
# Automatically retries on rate limit
|
||||
result = client.execute_with_retry(
|
||||
"workflow-id",
|
||||
input_data={"message": "Process this"},
|
||||
max_retries=5,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
backoff_multiplier=2.0
|
||||
)
|
||||
|
||||
print(f"Success: {result}")
|
||||
except SimStudioError as error:
|
||||
if error.code == "RATE_LIMIT_EXCEEDED":
|
||||
print("Rate limit exceeded after all retries")
|
||||
|
||||
# Check rate limit info
|
||||
rate_limit_info = client.get_rate_limit_info()
|
||||
if rate_limit_info:
|
||||
from datetime import datetime
|
||||
reset_time = datetime.fromtimestamp(rate_limit_info.reset)
|
||||
print(f"Rate limit resets at: {reset_time}")
|
||||
|
||||
execute_with_retry_handling()
|
||||
```
|
||||
|
||||
### 使用监控
|
||||
|
||||
监控您的账户使用情况和限制:
|
||||
|
||||
```python
|
||||
import os
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def check_usage():
|
||||
try:
|
||||
limits = client.get_usage_limits()
|
||||
|
||||
print("=== Rate Limits ===")
|
||||
print("Sync requests:")
|
||||
print(f" Limit: {limits.rate_limit['sync']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['sync']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['sync']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['sync']['isLimited']}")
|
||||
|
||||
print("\nAsync requests:")
|
||||
print(f" Limit: {limits.rate_limit['async']['limit']}")
|
||||
print(f" Remaining: {limits.rate_limit['async']['remaining']}")
|
||||
print(f" Resets at: {limits.rate_limit['async']['resetAt']}")
|
||||
print(f" Is limited: {limits.rate_limit['async']['isLimited']}")
|
||||
|
||||
print("\n=== Usage ===")
|
||||
print(f"Current period cost: ${limits.usage['currentPeriodCost']:.2f}")
|
||||
print(f"Limit: ${limits.usage['limit']:.2f}")
|
||||
print(f"Plan: {limits.usage['plan']}")
|
||||
|
||||
percent_used = (limits.usage['currentPeriodCost'] / limits.usage['limit']) * 100
|
||||
print(f"Usage: {percent_used:.1f}%")
|
||||
|
||||
if percent_used > 80:
|
||||
print("⚠️ Warning: You are approaching your usage limit!")
|
||||
|
||||
except Exception as error:
|
||||
print(f"Error checking usage: {error}")
|
||||
|
||||
check_usage()
|
||||
```
|
||||
|
||||
### 流式工作流执行
|
||||
|
||||
通过实时流式响应执行工作流:
|
||||
|
||||
```python
|
||||
from simstudio import SimStudioClient
|
||||
import os
|
||||
|
||||
client = SimStudioClient(api_key=os.getenv("SIM_API_KEY"))
|
||||
|
||||
def execute_with_streaming():
|
||||
"""Execute workflow with streaming enabled."""
|
||||
try:
|
||||
# Enable streaming for specific block outputs
|
||||
result = client.execute_workflow(
|
||||
"workflow-id",
|
||||
input_data={"message": "Count to five"},
|
||||
stream=True,
|
||||
selected_outputs=["agent1.content"] # Use blockName.attribute format
|
||||
)
|
||||
|
||||
print("Workflow result:", result)
|
||||
except Exception as error:
|
||||
print("Error:", error)
|
||||
|
||||
execute_with_streaming()
|
||||
```
|
||||
|
||||
流式响应遵循服务器发送事件 (SSE) 格式:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**Flask 流式示例:**
|
||||
|
||||
```python
|
||||
from flask import Flask, Response, stream_with_context
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/stream-workflow')
|
||||
def stream_workflow():
|
||||
"""Stream workflow execution to the client."""
|
||||
|
||||
def generate():
|
||||
response = requests.post(
|
||||
'https://sim.ai/api/workflows/WORKFLOW_ID/execute',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': os.getenv('SIM_API_KEY')
|
||||
},
|
||||
json={
|
||||
'message': 'Generate a story',
|
||||
'stream': True,
|
||||
'selectedOutputs': ['agent1.content']
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
data = decoded_line[6:] # Remove 'data: ' prefix
|
||||
|
||||
if data == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
parsed = json.loads(data)
|
||||
if 'chunk' in parsed:
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
elif parsed.get('event') == 'done':
|
||||
yield f"data: {json.dumps(parsed)}\n\n"
|
||||
print("Execution complete:", parsed.get('metadata'))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return Response(
|
||||
stream_with_context(generate()),
|
||||
mimetype='text/event-stream'
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
### 环境配置
|
||||
|
||||
使用环境变量配置客户端:
|
||||
@@ -352,8 +711,8 @@ for result in results:
|
||||
|
||||
# Development configuration
|
||||
client = SimStudioClient(
|
||||
api_key=os.getenv("SIMSTUDIO_API_KEY"),
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
api_key=os.getenv("SIM_API_KEY")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -365,13 +724,13 @@ for result in results:
|
||||
from simstudio import SimStudioClient
|
||||
|
||||
# Production configuration with error handling
|
||||
api_key = os.getenv("SIMSTUDIO_API_KEY")
|
||||
api_key = os.getenv("SIM_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("SIMSTUDIO_API_KEY environment variable is required")
|
||||
raise ValueError("SIM_API_KEY environment variable is required")
|
||||
|
||||
client = SimStudioClient(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://sim.ai")
|
||||
base_url=os.getenv("SIM_BASE_URL", "https://sim.ai")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -382,15 +741,15 @@ for result in results:
|
||||
|
||||
<Steps>
|
||||
<Step title="登录 Sim">
|
||||
访问 [Sim](https://sim.ai) 并登录您的账户。
|
||||
前往 [Sim](https://sim.ai) 并登录您的账户。
|
||||
</Step>
|
||||
<Step title="打开您的工作流">
|
||||
导航到您想要以编程方式执行的工作流。
|
||||
前往您想要以编程方式执行的工作流。
|
||||
</Step>
|
||||
<Step title="部署您的工作流">
|
||||
点击“部署”以部署您的工作流(如果尚未部署)。
|
||||
如果尚未部署,请点击“部署”以部署您的工作流。
|
||||
</Step>
|
||||
<Step title="创建或选择 API 密钥">
|
||||
<Step title="创建或选择一个 API 密钥">
|
||||
在部署过程中,选择或创建一个 API 密钥。
|
||||
</Step>
|
||||
<Step title="复制 API 密钥">
|
||||
|
||||
@@ -7,10 +7,10 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
Sim 的官方 TypeScript/JavaScript SDK 提供完整的类型安全,支持 Node.js 和浏览器环境,允许您从 Node.js 应用程序、Web 应用程序和其他 JavaScript 环境中以编程方式执行工作流。目前,所有工作流执行均为同步。
|
||||
Sim 的官方 TypeScript/JavaScript SDK 提供完整的类型安全,支持 Node.js 和浏览器环境,允许您从 Node.js 应用程序、Web 应用程序和其他 JavaScript 环境中以编程方式执行工作流。
|
||||
|
||||
<Callout type="info">
|
||||
TypeScript SDK 提供完整的类型安全,支持 Node.js 和浏览器环境。目前,所有工作流执行均为同步。
|
||||
TypeScript SDK 提供完整的类型安全、异步执行支持、带有指数回退的自动速率限制以及使用跟踪。
|
||||
</Callout>
|
||||
|
||||
## 安装
|
||||
@@ -91,12 +91,17 @@ const result = await client.executeWorkflow('workflow-id', {
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `workflowId`(字符串):要执行的工作流的 ID
|
||||
- `options`(ExecutionOptions,可选):
|
||||
- `input`(任意类型):传递给工作流的输入数据
|
||||
- `timeout`(数字):超时时间(以毫秒为单位,默认值:30000)
|
||||
- `workflowId` (字符串): 要执行的工作流的 ID
|
||||
- `options` (ExecutionOptions,可选):
|
||||
- `input` (任意类型): 传递给工作流的输入数据
|
||||
- `timeout` (数字): 超时时间(以毫秒为单位,默认值:30000)
|
||||
- `stream` (布尔值): 启用流式响应(默认值:false)
|
||||
- `selectedOutputs` (字符串数组): 以 `blockName.attribute` 格式阻止流中的输出(例如,`["agent1.content"]`)
|
||||
- `async` (布尔值): 异步执行(默认值:false)
|
||||
|
||||
**返回值:** `Promise<WorkflowExecutionResult>`
|
||||
**返回值:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
当 `async: true` 时,立即返回一个用于轮询的任务 ID。否则,等待完成。
|
||||
|
||||
##### getWorkflowStatus()
|
||||
|
||||
@@ -108,7 +113,7 @@ console.log('Is deployed:', status.isDeployed);
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `workflowId`(字符串):工作流的 ID
|
||||
- `workflowId` (字符串): 工作流的 ID
|
||||
|
||||
**返回值:** `Promise<WorkflowStatus>`
|
||||
|
||||
@@ -124,32 +129,121 @@ if (isReady) {
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `workflowId`(字符串):工作流的 ID
|
||||
- `workflowId` (字符串): 工作流的 ID
|
||||
|
||||
**返回值:** `Promise<boolean>`
|
||||
|
||||
##### executeWorkflowSync()
|
||||
##### getJobStatus()
|
||||
|
||||
<Callout type="info">
|
||||
当前,此方法与 `executeWorkflow()` 相同,因为所有执行都是同步的。提供此方法是为了在将来添加异步执行时保持兼容性。
|
||||
</Callout>
|
||||
|
||||
执行工作流(当前为同步,与 `executeWorkflow()` 相同)。
|
||||
获取异步任务执行的状态。
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWorkflowSync('workflow-id', {
|
||||
input: { data: 'some input' },
|
||||
timeout: 60000
|
||||
const status = await client.getJobStatus('task-id-from-async-execution');
|
||||
console.log('Status:', status.status); // 'queued', 'processing', 'completed', 'failed'
|
||||
if (status.status === 'completed') {
|
||||
console.log('Output:', status.output);
|
||||
}
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `taskId` (字符串): 异步执行返回的任务 ID
|
||||
|
||||
**返回值:** `Promise<JobStatus>`
|
||||
|
||||
**响应字段:**
|
||||
- `success` (布尔值): 请求是否成功
|
||||
- `taskId` (字符串): 任务 ID
|
||||
- `status` (字符串): 可能的值包括 `'queued'`, `'processing'`, `'completed'`, `'failed'`, `'cancelled'`
|
||||
- `metadata` (对象): 包含 `startedAt`, `completedAt` 和 `duration`
|
||||
- `output` (任意类型,可选): 工作流输出(完成时)
|
||||
- `error` (任意类型,可选): 错误详情(失败时)
|
||||
- `estimatedDuration` (数字,可选): 估计持续时间(以毫秒为单位,处理中/排队时)
|
||||
|
||||
##### executeWithRetry()
|
||||
|
||||
使用指数退避机制,在遇到速率限制错误时自动重试执行工作流。
|
||||
|
||||
```typescript
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Hello' },
|
||||
timeout: 30000
|
||||
}, {
|
||||
maxRetries: 3, // Maximum number of retries
|
||||
initialDelay: 1000, // Initial delay in ms (1 second)
|
||||
maxDelay: 30000, // Maximum delay in ms (30 seconds)
|
||||
backoffMultiplier: 2 // Exponential backoff multiplier
|
||||
});
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `workflowId`(字符串):要执行的工作流的 ID
|
||||
- `options`(ExecutionOptions,可选):
|
||||
- `input`(任意类型):传递给工作流的输入数据
|
||||
- `timeout`(数字):初始请求的超时时间(以毫秒为单位)
|
||||
- `options`(ExecutionOptions,可选):与 `executeWorkflow()` 相同
|
||||
- `retryOptions`(RetryOptions,可选):
|
||||
- `maxRetries`(数字):最大重试次数(默认值:3)
|
||||
- `initialDelay`(数字):初始延迟时间(以毫秒为单位,默认值:1000)
|
||||
- `maxDelay`(数字):最大延迟时间(以毫秒为单位,默认值:30000)
|
||||
- `backoffMultiplier`(数字):退避倍数(默认值:2)
|
||||
|
||||
**返回值:** `Promise<WorkflowExecutionResult>`
|
||||
**返回值:** `Promise<WorkflowExecutionResult | AsyncExecutionResult>`
|
||||
|
||||
重试逻辑使用指数退避(1秒 → 2秒 → 4秒 → 8秒...),并带有 ±25% 的抖动以防止蜂拥效应。如果 API 提供了 `retry-after` 头,则会使用该头。
|
||||
|
||||
##### getRateLimitInfo()
|
||||
|
||||
从上一次 API 响应中获取当前速率限制信息。
|
||||
|
||||
```typescript
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Limit:', rateLimitInfo.limit);
|
||||
console.log('Remaining:', rateLimitInfo.remaining);
|
||||
console.log('Reset:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
```
|
||||
|
||||
**返回值:** `RateLimitInfo | null`
|
||||
|
||||
##### getUsageLimits()
|
||||
|
||||
获取您的账户当前的使用限制和配额信息。
|
||||
|
||||
```typescript
|
||||
const limits = await client.getUsageLimits();
|
||||
console.log('Sync requests remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log('Async requests remaining:', limits.rateLimit.async.remaining);
|
||||
console.log('Current period cost:', limits.usage.currentPeriodCost);
|
||||
console.log('Plan:', limits.usage.plan);
|
||||
```
|
||||
|
||||
**返回值:** `Promise<UsageLimits>`
|
||||
|
||||
**响应结构:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
success: boolean
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
isLimited: boolean
|
||||
limit: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
authType: string // 'api' or 'manual'
|
||||
}
|
||||
usage: {
|
||||
currentPeriodCost: number
|
||||
limit: number
|
||||
plan: string // e.g., 'free', 'pro'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### setApiKey()
|
||||
|
||||
@@ -187,6 +281,20 @@ interface WorkflowExecutionResult {
|
||||
}
|
||||
```
|
||||
|
||||
### AsyncExecutionResult
|
||||
|
||||
```typescript
|
||||
interface AsyncExecutionResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
status: 'queued';
|
||||
createdAt: string;
|
||||
links: {
|
||||
status: string; // e.g., "/api/jobs/{taskId}"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkflowStatus
|
||||
|
||||
```typescript
|
||||
@@ -198,6 +306,45 @@ interface WorkflowStatus {
|
||||
}
|
||||
```
|
||||
|
||||
### RateLimitInfo
|
||||
|
||||
```typescript
|
||||
interface RateLimitInfo {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### UsageLimits
|
||||
|
||||
```typescript
|
||||
interface UsageLimits {
|
||||
success: boolean;
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
async: {
|
||||
isLimited: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetAt: string;
|
||||
};
|
||||
authType: string;
|
||||
};
|
||||
usage: {
|
||||
currentPeriodCost: number;
|
||||
limit: number;
|
||||
plan: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SimStudioError
|
||||
|
||||
```typescript
|
||||
@@ -207,6 +354,13 @@ class SimStudioError extends Error {
|
||||
}
|
||||
```
|
||||
|
||||
**常见错误代码:**
|
||||
- `UNAUTHORIZED`: 无效的 API 密钥
|
||||
- `TIMEOUT`: 请求超时
|
||||
- `RATE_LIMIT_EXCEEDED`: 超出速率限制
|
||||
- `USAGE_LIMIT_EXCEEDED`: 超出使用限制
|
||||
- `EXECUTION_ERROR`: 工作流执行失败
|
||||
|
||||
## 示例
|
||||
|
||||
### 基本工作流执行
|
||||
@@ -230,7 +384,7 @@ class SimStudioError extends Error {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function runWorkflow() {
|
||||
@@ -271,7 +425,7 @@ runWorkflow();
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithErrorHandling() {
|
||||
@@ -315,14 +469,14 @@ async function executeWithErrorHandling() {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Development configuration
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL // optional
|
||||
baseUrl: process.env.SIM_BASE_URL // optional
|
||||
});
|
||||
```
|
||||
|
||||
@@ -333,14 +487,14 @@ async function executeWithErrorHandling() {
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
// Production configuration with validation
|
||||
const apiKey = process.env.SIMSTUDIO_API_KEY;
|
||||
const apiKey = process.env.SIM_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('SIMSTUDIO_API_KEY environment variable is required');
|
||||
throw new Error('SIM_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey,
|
||||
baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://sim.ai'
|
||||
baseUrl: process.env.SIM_BASE_URL || 'https://sim.ai'
|
||||
});
|
||||
```
|
||||
|
||||
@@ -357,7 +511,7 @@ import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const app = express();
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
@@ -399,7 +553,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
export default async function handler(
|
||||
@@ -469,14 +623,14 @@ document.getElementById('executeBtn')?.addEventListener('click', executeClientSi
|
||||
|
||||
### React Hook 示例
|
||||
|
||||
为工作流执行创建一个自定义 React Hook:
|
||||
为工作流执行创建自定义 React hook:
|
||||
|
||||
```typescript
|
||||
import { useState, useCallback } from 'react';
|
||||
import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
interface UseWorkflowResult {
|
||||
@@ -532,7 +686,7 @@ function WorkflowComponent() {
|
||||
<button onClick={handleExecute} disabled={loading}>
|
||||
{loading ? 'Executing...' : 'Execute Workflow'}
|
||||
</button>
|
||||
|
||||
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{result && (
|
||||
<div>
|
||||
@@ -545,38 +699,267 @@ function WorkflowComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
## 获取您的 API 密钥
|
||||
### 异步工作流执行
|
||||
|
||||
为长时间运行的任务异步执行工作流:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, AsyncExecutionResult } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeAsync() {
|
||||
try {
|
||||
// Start async execution
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { data: 'large dataset' },
|
||||
async: true // Execute asynchronously
|
||||
});
|
||||
|
||||
// Check if result is an async execution
|
||||
if ('taskId' in result) {
|
||||
console.log('Task ID:', result.taskId);
|
||||
console.log('Status endpoint:', result.links.status);
|
||||
|
||||
// Poll for completion
|
||||
let status = await client.getJobStatus(result.taskId);
|
||||
|
||||
while (status.status === 'queued' || status.status === 'processing') {
|
||||
console.log('Current status:', status.status);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
|
||||
status = await client.getJobStatus(result.taskId);
|
||||
}
|
||||
|
||||
if (status.status === 'completed') {
|
||||
console.log('Workflow completed!');
|
||||
console.log('Output:', status.output);
|
||||
console.log('Duration:', status.metadata.duration);
|
||||
} else {
|
||||
console.error('Workflow failed:', status.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
executeAsync();
|
||||
```
|
||||
|
||||
### 速率限制和重试
|
||||
|
||||
通过指数退避自动处理速率限制:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithRetryHandling() {
|
||||
try {
|
||||
// Automatically retries on rate limit
|
||||
const result = await client.executeWithRetry('workflow-id', {
|
||||
input: { message: 'Process this' }
|
||||
}, {
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
maxDelay: 60000,
|
||||
backoffMultiplier: 2
|
||||
});
|
||||
|
||||
console.log('Success:', result);
|
||||
} catch (error) {
|
||||
if (error instanceof SimStudioError && error.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
console.error('Rate limit exceeded after all retries');
|
||||
|
||||
// Check rate limit info
|
||||
const rateLimitInfo = client.getRateLimitInfo();
|
||||
if (rateLimitInfo) {
|
||||
console.log('Rate limit resets at:', new Date(rateLimitInfo.reset * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用监控
|
||||
|
||||
监控您的账户使用情况和限制:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function checkUsage() {
|
||||
try {
|
||||
const limits = await client.getUsageLimits();
|
||||
|
||||
console.log('=== Rate Limits ===');
|
||||
console.log('Sync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.sync.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.sync.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.sync.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.sync.isLimited);
|
||||
|
||||
console.log('\nAsync requests:');
|
||||
console.log(' Limit:', limits.rateLimit.async.limit);
|
||||
console.log(' Remaining:', limits.rateLimit.async.remaining);
|
||||
console.log(' Resets at:', limits.rateLimit.async.resetAt);
|
||||
console.log(' Is limited:', limits.rateLimit.async.isLimited);
|
||||
|
||||
console.log('\n=== Usage ===');
|
||||
console.log('Current period cost:
|
||||
|
||||
### Streaming Workflow Execution
|
||||
|
||||
Execute workflows with real-time streaming responses:
|
||||
|
||||
```typescript
|
||||
import { SimStudioClient } from 'simstudio-ts-sdk';
|
||||
|
||||
const client = new SimStudioClient({
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
async function executeWithStreaming() {
|
||||
try {
|
||||
// 为特定的块输出启用流式传输
|
||||
const result = await client.executeWorkflow('workflow-id', {
|
||||
input: { message: 'Count to five' },
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content'] // 使用 blockName.attribute 格式
|
||||
});
|
||||
|
||||
console.log('工作流结果:', result);
|
||||
} catch (error) {
|
||||
console.error('错误:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The streaming response follows the Server-Sent Events (SSE) format:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
**React Streaming Example:**
|
||||
|
||||
```typescript
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function StreamingWorkflow() {
|
||||
const [output, setOutput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const executeStreaming = async () => {
|
||||
setLoading(true);
|
||||
setOutput('');
|
||||
|
||||
// 重要提示:请从您的后端服务器发起此 API 调用,而不是从浏览器发起
|
||||
// 切勿在客户端代码中暴露您的 API 密钥
|
||||
const response = await fetch('https://sim.ai/api/workflows/WORKFLOW_ID/execute', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': process.env.SIM_API_KEY! // 仅限服务器端环境变量
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: '生成一个故事',
|
||||
stream: true,
|
||||
selectedOutputs: ['agent1.content']
|
||||
})
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const lines = chunk.split('\n\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.slice(6);
|
||||
if (data === '[DONE]') {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.chunk) {
|
||||
setOutput(prev => prev + parsed.chunk);
|
||||
} else if (parsed.event === 'done') {
|
||||
console.log('执行完成:', parsed.metadata);
|
||||
}
|
||||
} catch (e) {
|
||||
// 跳过无效的 JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={executeStreaming} disabled={loading}>
|
||||
{loading ? '生成中...' : '开始流式处理'}
|
||||
</button>
|
||||
<div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
<Steps>
|
||||
<Step title="登录 Sim">
|
||||
访问 [Sim](https://sim.ai) 并登录您的账户。
|
||||
<Step title="Log in to Sim">
|
||||
Navigate to [Sim](https://sim.ai) and log in to your account.
|
||||
</Step>
|
||||
<Step title="打开您的工作流">
|
||||
导航到您想要以编程方式执行的工作流。
|
||||
<Step title="Open your workflow">
|
||||
Navigate to the workflow you want to execute programmatically.
|
||||
</Step>
|
||||
<Step title="部署您的工作流">
|
||||
如果尚未部署,请点击“部署”以部署您的工作流。
|
||||
<Step title="Deploy your workflow">
|
||||
Click on "Deploy" to deploy your workflow if it hasn't been deployed yet.
|
||||
</Step>
|
||||
<Step title="创建或选择一个 API 密钥">
|
||||
在部署过程中,选择或创建一个 API 密钥。
|
||||
<Step title="Create or select an API key">
|
||||
During the deployment process, select or create an API key.
|
||||
</Step>
|
||||
<Step title="复制 API 密钥">
|
||||
复制 API 密钥以在您的 TypeScript/JavaScript 应用程序中使用。
|
||||
<Step title="Copy the API key">
|
||||
Copy the API key to use in your TypeScript/JavaScript application.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Callout type="warning">
|
||||
请确保您的 API 密钥安全,切勿将其提交到版本控制中。使用环境变量或安全配置管理。
|
||||
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
|
||||
</Callout>
|
||||
|
||||
## 要求
|
||||
## Requirements
|
||||
|
||||
- Node.js 16+
|
||||
- TypeScript 5.0+(适用于 TypeScript 项目)
|
||||
- TypeScript 5.0+ (for TypeScript projects)
|
||||
|
||||
## TypeScript 支持
|
||||
## TypeScript Support
|
||||
|
||||
SDK 是用 TypeScript 编写的,并提供完整的类型安全:
|
||||
The SDK is written in TypeScript and provides full type safety:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
@@ -586,22 +969,22 @@ import {
|
||||
SimStudioError
|
||||
} from 'simstudio-ts-sdk';
|
||||
|
||||
// Type-safe client initialization
|
||||
// 类型安全的客户端初始化
|
||||
const client: SimStudioClient = new SimStudioClient({
|
||||
apiKey: process.env.SIMSTUDIO_API_KEY!
|
||||
apiKey: process.env.SIM_API_KEY!
|
||||
});
|
||||
|
||||
// Type-safe workflow execution
|
||||
// 类型安全的工作流执行
|
||||
const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-id', {
|
||||
input: {
|
||||
message: 'Hello, TypeScript!'
|
||||
message: '你好,TypeScript!'
|
||||
}
|
||||
});
|
||||
|
||||
// Type-safe status checking
|
||||
// 类型安全的状态检查
|
||||
const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id');
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
Apache-2.0
|
||||
Apache-2.0
|
||||
|
||||
@@ -38,6 +38,84 @@ curl -X POST \
|
||||
|
||||
成功的响应会返回来自执行器的序列化执行结果。错误会显示验证、认证或工作流失败的信息。
|
||||
|
||||
## 流式响应
|
||||
|
||||
启用实时流式传输以在生成时逐字符接收工作流输出。这对于向用户逐步显示 AI 响应非常有用。
|
||||
|
||||
### 请求参数
|
||||
|
||||
添加以下参数以启用流式传输:
|
||||
|
||||
- `stream` - 设置为 `true` 以启用服务器发送事件 (SSE) 流式传输
|
||||
- `selectedOutputs` - 要流式传输的块输出数组(例如,`["agent1.content"]`)
|
||||
|
||||
### 块输出格式
|
||||
|
||||
使用 `blockName.attribute` 格式指定要流式传输的块输出:
|
||||
- 格式:`"blockName.attribute"`(例如,如果您想流式传输 Agent 1 块的内容,可以使用 `"agent1.content"`)
|
||||
- 块名称不区分大小写,空格会被忽略
|
||||
|
||||
### 示例请求
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Count to five",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
### 响应格式
|
||||
|
||||
流式响应使用服务器发送事件 (SSE) 格式:
|
||||
|
||||
```
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":"One"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", two"}
|
||||
|
||||
data: {"blockId":"7b7735b9-19e5-4bd6-818b-46aae2596e9f","chunk":", three"}
|
||||
|
||||
data: {"event":"done","success":true,"output":{},"metadata":{"duration":610}}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
每个事件包括:
|
||||
- **流式块**:`{"blockId": "...", "chunk": "text"}` - 实时生成的文本
|
||||
- **最终事件**:`{"event": "done", ...}` - 执行元数据和完整结果
|
||||
- **终止符**:`[DONE]` - 表示流结束
|
||||
|
||||
### 多块流式传输
|
||||
|
||||
当 `selectedOutputs` 包含多个块时,每个块会指示其来源:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
https://sim.ai/api/workflows/WORKFLOW_ID/execute \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: YOUR_KEY' \
|
||||
-d '{
|
||||
"message": "Process this request",
|
||||
"stream": true,
|
||||
"selectedOutputs": ["agent1.content", "agent2.content"]
|
||||
}'
|
||||
```
|
||||
|
||||
每个块中的 `blockId` 字段可让您将输出路由到正确的 UI 元素:
|
||||
|
||||
```
|
||||
data: {"blockId":"agent1-uuid","chunk":"Processing..."}
|
||||
|
||||
data: {"blockId":"agent2-uuid","chunk":"Analyzing..."}
|
||||
|
||||
data: {"blockId":"agent1-uuid","chunk":" complete"}
|
||||
```
|
||||
|
||||
## 输出参考
|
||||
|
||||
| 参考 | 描述 |
|
||||
@@ -48,5 +126,5 @@ curl -X POST \
|
||||
如果未定义输入格式,执行器仅在 `<api.input>` 处暴露原始 JSON。
|
||||
|
||||
<Callout type="warning">
|
||||
一个工作流只能包含一个 API 触发器。更改后发布新的部署,以确保端点保持最新。
|
||||
一个工作流只能包含一个 API 触发器。更改后发布新部署,以确保端点保持最新。
|
||||
</Callout>
|
||||
|
||||
@@ -2231,8 +2231,8 @@ checksums:
|
||||
d394ac42b56429e524dc5a771b0610b9:
|
||||
meta/title: 9da9098244c6c7a0ebcc3009cef66c7b
|
||||
content/0: 9218a2e190598690d0fc5c27c30f01bb
|
||||
content/1: 8a3feb937915c3191f1eecb10b94297d
|
||||
content/2: 99af1bfe8d1629acdb5a9229430af791
|
||||
content/1: 6c88f52bdb4e4a5668d1b25b5f444f48
|
||||
content/2: 7e827833339b6b4c6abdf154de7f9a0c
|
||||
content/3: 391128dee61b5d0d43eba88567aaef42
|
||||
content/4: 4d132e6346723ecf45c408afeab2757b
|
||||
content/5: d3df764a69d2926d10aed65ad8693e9f
|
||||
@@ -2254,79 +2254,155 @@ checksums:
|
||||
content/21: bd0e851fdde30c0e94c00b60f85d655e
|
||||
content/22: 837ca74ccf63f23333c54e010faf681c
|
||||
content/23: 8fb33cfc314b86d35df8ea1b10466f20
|
||||
content/24: 09e003c28fb1810e9afefe51324265fd
|
||||
content/25: 07fb2d6b16c75839a32d383f12419ca5
|
||||
content/26: 9fd0cd99a879360d355d91e9cfb41531
|
||||
content/27: f6fed8ebf67ba12199b4474a754969ae
|
||||
content/28: bcee3febe1be079e53aea841e2b08b3b
|
||||
content/29: f00be560fcd4ff3f53d61c70c249597b
|
||||
content/30: fa4fa1573c369fcc2eee57d7852caf9c
|
||||
content/31: fa68c1f8c9ea3dba96b2ea7edb8680d7
|
||||
content/32: 304e608d459ef53f308e6ea1f6f8b54a
|
||||
content/33: cb63e267fb16a7aaeea45c4ca29bf697
|
||||
content/34: f00be560fcd4ff3f53d61c70c249597b
|
||||
content/35: f7c266db4d07d040f8f788be598476cf
|
||||
content/36: cd306281b5136831335e6376edb1e822
|
||||
content/37: afb7b7f27d48deb3154da26135e17fb8
|
||||
content/38: 179000198c9cd78601b5c862e9c8659f
|
||||
content/39: 28c37db52f39323e125fcaf0e60911db
|
||||
content/40: 8de5041c3c93b70619ec1723f657757f
|
||||
content/41: 07fb2d6b16c75839a32d383f12419ca5
|
||||
content/42: 65db7855a79ab283c6409e81a7703d19
|
||||
content/43: 191fb7087315702a36001c69d745ebed
|
||||
content/44: f6113edfd7a0062af4d88bcf31a73f45
|
||||
content/45: 1e84fc1eee794c20e3411b3a34a02278
|
||||
content/46: ec31e300f79185f734d32b1cfaf8a137
|
||||
content/47: f7ad301d02e8826921644a5268f13f32
|
||||
content/48: 025d60fdaf93713ccb34abcbc71dfa2b
|
||||
content/49: 70a9ece41fdad09f3a06ca0efdb92ae9
|
||||
content/50: 356d67409ae0d82a72d052573314f660
|
||||
content/51: bb172f1678686d9d49666c516716de24
|
||||
content/52: 529711647eccfdf031dbb5bc70581986
|
||||
content/53: 9a84c92505eb468916637fcf2cef70f2
|
||||
content/54: 225bca3fb37bd38cd645e8a698abbfa9
|
||||
content/55: 33b9b1e9744318597da4b925b0995be2
|
||||
content/56: 6afe3b62e6d53c3dcd07149abcab4c05
|
||||
content/57: b6363faee219321c16d41a9c3f8d3bdd
|
||||
content/58: 24ef65dd034a2881a978d8d0065fb258
|
||||
content/59: b8b23ab79a7eb32c6f8d5f49f43c51f6
|
||||
content/60: be358297e2bbb9ab4689d11d072611d1
|
||||
content/61: b2eaadc86870d2e64b55c89e0348ef93
|
||||
content/62: 450265802cb0ba5b435b74b9cac1bf23
|
||||
content/63: b735ede8764e4b2dfb25967e33ab5143
|
||||
content/64: 0f881e586a03c4b916456c73fad48358
|
||||
content/65: 62bbbeca4e0500062f5cdbbc1614dde0
|
||||
content/66: 55d47e12745c1b0b62c9bdf6e8449730
|
||||
content/67: 1d873c7ccd87f564e2b30387b40ee9e9
|
||||
content/68: 3304a33dfb626c6e2267c062e8956a9d
|
||||
content/69: 77256b36307e9f7293bd00063239c8ee
|
||||
content/70: ac686382ccbb07d75b0f141af500dfd5
|
||||
content/71: 38f7308105b0843792c8e2fb93e1895d
|
||||
content/72: 62f6977928b2f596ed7d54383d1e779d
|
||||
content/73: 3415d6c5ad1df56b212d69519bdf0fea
|
||||
content/74: d1a104e667cd2284ab5b3fead4a6ba1d
|
||||
content/75: a81d7cd4a644a0061dad3a5973b4fe06
|
||||
content/76: 981447969a71fd038049e9d9f40f4f8c
|
||||
content/77: 531941216d31cb1947367c3c02127baa
|
||||
content/78: bf1afa789fdfa5815faaf43574341e90
|
||||
content/79: 5f2fe55d098d4e4f438af595708b2280
|
||||
content/80: 41b8f7cf8899a0e92e255a3f845f9584
|
||||
content/81: 5040bab65fb6bb77862f8098d16afbb5
|
||||
content/82: a88260a5b5e23da73e4534376adeb193
|
||||
content/83: e5e2329cdc226186fe9d44767528a4a0
|
||||
content/84: 1773624e9ac3d5132b505894ef51977e
|
||||
content/85: d62c9575cc66feec7589fba95c9f7aee
|
||||
content/86: 7af652c5407ae7e156ab27b21a4f26d3
|
||||
content/87: 4aa69b29cca745389dea8cd74eba4f83
|
||||
content/88: 46877074b69519165997fa0968169611
|
||||
content/89: d8ebc69b18baf83689ba315e7b4946ea
|
||||
content/90: ecd571818ddf3d31b08b80a25958a662
|
||||
content/91: 7dcdf2fbf3fce3f94987046506e12a9b
|
||||
content/24: f8fbd9375113651be0f2498bdacde0ef
|
||||
content/25: 2c57d87589b65f785e0fbbda60d32e54
|
||||
content/26: 2541eb37fca67a6d7c5a10f8067127a3
|
||||
content/27: 9fd0cd99a879360d355d91e9cfb41531
|
||||
content/28: f6fed8ebf67ba12199b4474a754969ae
|
||||
content/29: bcee3febe1be079e53aea841e2b08b3b
|
||||
content/30: f00be560fcd4ff3f53d61c70c249597b
|
||||
content/31: fa4fa1573c369fcc2eee57d7852caf9c
|
||||
content/32: fa68c1f8c9ea3dba96b2ea7edb8680d7
|
||||
content/33: 304e608d459ef53f308e6ea1f6f8b54a
|
||||
content/34: cb63e267fb16a7aaeea45c4ca29bf697
|
||||
content/35: f00be560fcd4ff3f53d61c70c249597b
|
||||
content/36: f7c266db4d07d040f8f788be598476cf
|
||||
content/37: d93b320646fde160c0fdd1936ee63cfb
|
||||
content/38: c76e2089a41880dd6feac759ec8867c2
|
||||
content/39: 0d61b9631788e64d1c1335b08c907107
|
||||
content/40: 5ec50e6f56bd0a9a55fae14fa02185d9
|
||||
content/41: 47bdc3ba4908bf1ce3d1a0a8f646b339
|
||||
content/42: 5e8af7125448a6021a6ea431486dd587
|
||||
content/43: 15017685691db74889cc6116373e44a5
|
||||
content/44: 4d4ad5d56e800e5d227a07339300fc7f
|
||||
content/45: c035728b4b81d006a18ba9ba7b9c638d
|
||||
content/46: f1c9ad60574d19a5f93c837ab9d88890
|
||||
content/47: 2c57d87589b65f785e0fbbda60d32e54
|
||||
content/48: e7019a0e12f7295893c5822356fc0df0
|
||||
content/49: 5912d8d9df5bbe435579d8eb0677685c
|
||||
content/50: 4e1da4edce56837c750ce8da4c0e6cf2
|
||||
content/51: 3d35097bb958e6eddd6976aeb1fe9e41
|
||||
content/52: 78dce98d48ba070dbe100ee2a94cb17d
|
||||
content/53: 38ec85acf292485e3dd837a29208fd2c
|
||||
content/54: 58d582d90c8715f5570f76fed2be508d
|
||||
content/55: 7d2b7134d447172c502b5f40fc3b38e6
|
||||
content/56: 4a71171863d7329da6813b94772c0d4e
|
||||
content/57: 1900d5b89dbca22d7a455bdc3367f0f5
|
||||
content/58: 45126feb4fc831922a7edabfa2d54e4a
|
||||
content/59: 65db7855a79ab283c6409e81a7703d19
|
||||
content/60: 191fb7087315702a36001c69d745ebed
|
||||
content/61: f6113edfd7a0062af4d88bcf31a73f45
|
||||
content/62: 1e84fc1eee794c20e3411b3a34a02278
|
||||
content/63: ec31e300f79185f734d32b1cfaf8a137
|
||||
content/64: f7ad301d02e8826921644a5268f13f32
|
||||
content/65: 025d60fdaf93713ccb34abcbc71dfa2b
|
||||
content/66: 70a9ece41fdad09f3a06ca0efdb92ae9
|
||||
content/67: 356d67409ae0d82a72d052573314f660
|
||||
content/68: 5a80933fb21deea17a0a200564f0111b
|
||||
content/69: 9527ba2ab5ddd8001baaaaf25f1a7acc
|
||||
content/70: bb172f1678686d9d49666c516716de24
|
||||
content/71: 529711647eccfdf031dbb5bc70581986
|
||||
content/72: baa408b1603f35a8e24dd60b88773c72
|
||||
content/73: c42a9f19d0678d8d1a36cf1f93e4a5ba
|
||||
content/74: f6180f2341e8a7ae24afb05d7a185340
|
||||
content/75: 8196e101e443ec2aac13cefd90a6d454
|
||||
content/76: 9a84c92505eb468916637fcf2cef70f2
|
||||
content/77: 225bca3fb37bd38cd645e8a698abbfa9
|
||||
content/78: 7431c09b430effd69de843ee0fbaafe8
|
||||
content/79: 33b9b1e9744318597da4b925b0995be2
|
||||
content/80: 6afe3b62e6d53c3dcd07149abcab4c05
|
||||
content/81: b6363faee219321c16d41a9c3f8d3bdd
|
||||
content/82: 2449c8e8f55e2bf3f732527352d35c9f
|
||||
content/83: b8b23ab79a7eb32c6f8d5f49f43c51f6
|
||||
content/84: be358297e2bbb9ab4689d11d072611d1
|
||||
content/85: eb774a8a86d778153905b0f6cdcdf517
|
||||
content/86: 450265802cb0ba5b435b74b9cac1bf23
|
||||
content/87: b735ede8764e4b2dfb25967e33ab5143
|
||||
content/88: 0f881e586a03c4b916456c73fad48358
|
||||
content/89: f51639ab2b7ccac72b850e2064e694e9
|
||||
content/90: 55d47e12745c1b0b62c9bdf6e8449730
|
||||
content/91: e6223d6aa9efa444282e58d7d9a99ced
|
||||
content/92: 3304a33dfb626c6e2267c062e8956a9d
|
||||
content/93: 77256b36307e9f7293bd00063239c8ee
|
||||
content/94: ac686382ccbb07d75b0f141af500dfd5
|
||||
content/95: 5610b6538a29672335b572d6f35d0657
|
||||
content/96: 62f6977928b2f596ed7d54383d1e779d
|
||||
content/97: 3415d6c5ad1df56b212d69519bdf0fea
|
||||
content/98: 6bd60468d8cc072c5fe4214481fa9f60
|
||||
content/99: a81d7cd4a644a0061dad3a5973b4fe06
|
||||
content/100: 981447969a71fd038049e9d9f40f4f8c
|
||||
content/101: 531941216d31cb1947367c3c02127baa
|
||||
content/102: bf1afa789fdfa5815faaf43574341e90
|
||||
content/103: 5f2fe55d098d4e4f438af595708b2280
|
||||
content/104: 41b8f7cf8899a0e92e255a3f845f9584
|
||||
content/105: 61ddd890032078ffd2da931b1d153b6d
|
||||
content/106: 7873aa7487bc3e8a4826d65c1760a4a0
|
||||
content/107: 98182d9aabe14d5bad43a5ee76a75eab
|
||||
content/108: 2bdb01e4bcb08b1d99f192acf8e2fba7
|
||||
content/109: 7079d9c00b1e1882c329b7e9b8f74552
|
||||
content/110: 0f9d65eaf6e8de43c3d5fa7e62bc838d
|
||||
content/111: 58c8e9d2d0ac37efd958203b8fbc8193
|
||||
content/112: 7859d36a7a6d0122c0818b28ee29aa3e
|
||||
content/113: ce185e7b041b8f95ebc11370d3e0aad9
|
||||
content/114: 701e9bf4fd4d0669da0584eac5bd96e0
|
||||
content/115: d1bab8ec5a51a9da5464eb47e2a16b50
|
||||
content/116: da658275cc81a20f9cf7e4c66c7af1e3
|
||||
content/117: 377d7c99a5df4b72166946573f7210b8
|
||||
content/118: 3afc03a5ab1dc9db2bfa092b0ac4826a
|
||||
content/119: 18ddfcaf2be4a6f1d9819407dad9ce7c
|
||||
content/120: 2f6263b2e95f09f7e4842453f4bf4a0a
|
||||
content/121: 4603578d6b314b662f45564a34ca430d
|
||||
content/122: cf4c97eb254d0bd6ea6633344621c2c2
|
||||
content/123: 7b4640989fab002039936156f857eb21
|
||||
content/124: 65ca9f08745b47b4cce8ea8247d043bf
|
||||
content/125: 162b4180611ff0a53b782e4dc8109293
|
||||
content/126: 6b367a189eb53cb198e3666023def89c
|
||||
content/127: dbb2125cefcf618849600c1eccae8a64
|
||||
content/128: 04eedda0da3767b06e6017c559e05414
|
||||
content/129: 661688450606eb09d8faee1468e88331
|
||||
content/130: 8ff8367c3246103b3e3e02499e34ae0b
|
||||
content/131: 44678bda9166f746da1d61b694ced482
|
||||
content/132: a5e75db27c0a901f4cacf6598f450e6c
|
||||
content/133: d1bab8ec5a51a9da5464eb47e2a16b50
|
||||
content/134: da658275cc81a20f9cf7e4c66c7af1e3
|
||||
content/135: 377d7c99a5df4b72166946573f7210b8
|
||||
content/136: 3afc03a5ab1dc9db2bfa092b0ac4826a
|
||||
content/137: 18ddfcaf2be4a6f1d9819407dad9ce7c
|
||||
content/138: 2f6263b2e95f09f7e4842453f4bf4a0a
|
||||
content/139: 4603578d6b314b662f45564a34ca430d
|
||||
content/140: cf4c97eb254d0bd6ea6633344621c2c2
|
||||
content/141: 7b4640989fab002039936156f857eb21
|
||||
content/142: 65ca9f08745b47b4cce8ea8247d043bf
|
||||
content/143: 162b4180611ff0a53b782e4dc8109293
|
||||
content/144: 6b367a189eb53cb198e3666023def89c
|
||||
content/145: dbb2125cefcf618849600c1eccae8a64
|
||||
content/146: 04eedda0da3767b06e6017c559e05414
|
||||
content/147: 661688450606eb09d8faee1468e88331
|
||||
content/148: 8ff8367c3246103b3e3e02499e34ae0b
|
||||
content/149: 44678bda9166f746da1d61b694ced482
|
||||
content/150: 192a89879084dd7a74a6f44bcecae958
|
||||
content/151: 41c2bb95317d7c0421817a2b1a68cc09
|
||||
content/152: 4c95f9fa55f698f220577380dff95011
|
||||
content/153: 9ef273d776aada1b2cff3452f08ff985
|
||||
content/154: 100e12673551d4ceb5b906b1b9c65059
|
||||
content/155: ce253674cd7c49320203cda2bdd3685b
|
||||
content/156: 8910afcea8c205a28256eb30de6a1f26
|
||||
content/157: 4d7ad757d2c70fdff7834146d38dddd8
|
||||
content/158: a88260a5b5e23da73e4534376adeb193
|
||||
content/159: e5e2329cdc226186fe9d44767528a4a0
|
||||
content/160: 1773624e9ac3d5132b505894ef51977e
|
||||
content/161: d62c9575cc66feec7589fba95c9f7aee
|
||||
content/162: 7af652c5407ae7e156ab27b21a4f26d3
|
||||
content/163: 4aa69b29cca745389dea8cd74eba4f83
|
||||
content/164: 46877074b69519165997fa0968169611
|
||||
content/165: 2e81908c18033109ac82a054b3fafd3d
|
||||
content/166: ecd571818ddf3d31b08b80a25958a662
|
||||
content/167: 7dcdf2fbf3fce3f94987046506e12a9b
|
||||
27578f1315b6f1b7418d5e0d6042722e:
|
||||
meta/title: 8c555594662512e95f28e20d3880f186
|
||||
content/0: 9218a2e190598690d0fc5c27c30f01bb
|
||||
content/1: feca29d7cbb17f461bc8706f142cb475
|
||||
content/2: 65705e1bef9ddf2674454c20e77af61f
|
||||
content/2: 9cb58e08402fc80050ad6a62cae3f643
|
||||
content/3: 391128dee61b5d0d43eba88567aaef42
|
||||
content/4: fa77bab0a8660a7999bf3104921aac5c
|
||||
content/5: e8839cfb872185cea76973caaa7f84e0
|
||||
@@ -2342,67 +2418,107 @@ checksums:
|
||||
content/15: 64005abb7b5c1c3edef8970a8a7d17b2
|
||||
content/16: 837ca74ccf63f23333c54e010faf681c
|
||||
content/17: 626054376e08522e7195a60c34db9af8
|
||||
content/18: 03c715df3c784e92ce1c0ce6a4dcd2e3
|
||||
content/19: dcb92b9a1f222393f2e81cdae239885c
|
||||
content/20: 2f5c7e73763a1884893739283f0d0659
|
||||
content/21: f6fed8ebf67ba12199b4474a754969ae
|
||||
content/22: c8f9a1d43885f2b9fe8b64c79d8af8b8
|
||||
content/23: e1a2ca39583549a731d942082e1fa07c
|
||||
content/24: 14e077bdb64d87457870efa215384654
|
||||
content/25: c2e86eaf4b7d1cd53ed8172264337cc9
|
||||
content/26: 304e608d459ef53f308e6ea1f6f8b54a
|
||||
content/27: 9d04294f8385211535ed7622d164871f
|
||||
content/28: e1a2ca39583549a731d942082e1fa07c
|
||||
content/29: 279c20e11af33abb94993e8ea3e80669
|
||||
content/30: eec7d8395f8cf305106deb7b25384ecf
|
||||
content/31: 921824b44c391f8a0cdc5ce4cd283e77
|
||||
content/32: d5aaccb9399a1255f986b703921594e5
|
||||
content/33: dba855cc28255e4576026e3da0cdf05b
|
||||
content/34: 17fdd93c6df75b108e352a62a195bc73
|
||||
content/35: dcb92b9a1f222393f2e81cdae239885c
|
||||
content/36: fb6fddfdf4753a36c7878ef60b345822
|
||||
content/37: 191fb7087315702a36001c69d745ebed
|
||||
content/38: 1ffef0a4e0d6a6bbca85776c113e1164
|
||||
content/39: 61caafaf79e863df9525c4baf72c14e1
|
||||
content/40: ec31e300f79185f734d32b1cfaf8a137
|
||||
content/41: 65a172d64ffca3b03c6e0ed08f0bd821
|
||||
content/42: 2db387754d7fb3539bcb986dfaac1c8c
|
||||
content/43: e118d997ba48a5230ec70a564d436860
|
||||
content/44: 77268362a748dafad471f31acfd230dc
|
||||
content/45: b55b3773df2dfba66b6e675db7e2470e
|
||||
content/46: 70a9ece41fdad09f3a06ca0efdb92ae9
|
||||
content/47: 646ee615d86faf3b6a8da03115a30efa
|
||||
content/48: bb172f1678686d9d49666c516716de24
|
||||
content/49: a025b3b746d72e0f676f58703ee19a47
|
||||
content/50: 9a84c92505eb468916637fcf2cef70f2
|
||||
content/51: a4c78d85ed9be63b07b657166510f440
|
||||
content/52: 33b9b1e9744318597da4b925b0995be2
|
||||
content/53: 6afe3b62e6d53c3dcd07149abcab4c05
|
||||
content/54: b6363faee219321c16d41a9c3f8d3bdd
|
||||
content/55: f939bc99e05d04e1d52bf4b9ec3f1825
|
||||
content/56: b8b23ab79a7eb32c6f8d5f49f43c51f6
|
||||
content/57: be358297e2bbb9ab4689d11d072611d1
|
||||
content/58: d8fcefba15a99bf4a9cf71c985097677
|
||||
content/59: 7d098f0349c782f389431377ee512e92
|
||||
content/60: 22b39537f6a104803389469d211154e4
|
||||
content/61: 5dc147f9fe5e8117dfa6c94808c4ff54
|
||||
content/62: f29d6bfd74ba3fee0b90180f620b4f47
|
||||
content/63: 2a59466500b62e57481fe27692a3ed0f
|
||||
content/64: d3ac9ea2a213cafb1f871dda8f6e6fe0
|
||||
content/65: 450265802cb0ba5b435b74b9cac1bf23
|
||||
content/66: b735ede8764e4b2dfb25967e33ab5143
|
||||
content/67: 0f881e586a03c4b916456c73fad48358
|
||||
content/68: 3f643fb43f3a022a449ded1e7c4db8bf
|
||||
content/69: 55d47e12745c1b0b62c9bdf6e8449730
|
||||
content/70: 166b3975e39841707381880ae4df3984
|
||||
content/71: 3304a33dfb626c6e2267c062e8956a9d
|
||||
content/72: a88260a5b5e23da73e4534376adeb193
|
||||
content/73: cc31ae653c5642b223ec634888de29c6
|
||||
content/74: 1773624e9ac3d5132b505894ef51977e
|
||||
content/75: d62c9575cc66feec7589fba95c9f7aee
|
||||
content/76: 8df5939abc771b5d24c115ef20d42d6f
|
||||
content/77: ecd571818ddf3d31b08b80a25958a662
|
||||
content/78: 7dcdf2fbf3fce3f94987046506e12a9b
|
||||
content/18: 12153919e0229ac0a3699de043eae2a2
|
||||
content/19: 59ceca96004d0746448717245eb65c5c
|
||||
content/20: a0ff152e09498effe90572fe5cdfad1b
|
||||
content/21: 2f5c7e73763a1884893739283f0d0659
|
||||
content/22: f6fed8ebf67ba12199b4474a754969ae
|
||||
content/23: c8f9a1d43885f2b9fe8b64c79d8af8b8
|
||||
content/24: e1a2ca39583549a731d942082e1fa07c
|
||||
content/25: 14e077bdb64d87457870efa215384654
|
||||
content/26: c2e86eaf4b7d1cd53ed8172264337cc9
|
||||
content/27: 304e608d459ef53f308e6ea1f6f8b54a
|
||||
content/28: 9d04294f8385211535ed7622d164871f
|
||||
content/29: e1a2ca39583549a731d942082e1fa07c
|
||||
content/30: 279c20e11af33abb94993e8ea3e80669
|
||||
content/31: 9e772c161a4b008c2f1db15a967d07ab
|
||||
content/32: c76e2089a41880dd6feac759ec8867c2
|
||||
content/33: 5d9a7b1e681cbe8f02def7eefabb0ac5
|
||||
content/34: b4e0e90d40a60a024f64f80b193dcb48
|
||||
content/35: b9f46c03c91c1070dd3ca0eba461f29b
|
||||
content/36: fbecf63d14b56039ba44471f7a8afd4a
|
||||
content/37: 58701f4ec097582ee105714a9363ccbe
|
||||
content/38: 4d4ad5d56e800e5d227a07339300fc7f
|
||||
content/39: 7f2a42a752279d7871064a21d0891b73
|
||||
content/40: 8462e2271506b0545c62e5f70865a2f4
|
||||
content/41: 59ceca96004d0746448717245eb65c5c
|
||||
content/42: e7019a0e12f7295893c5822356fc0df0
|
||||
content/43: 29d376146cd1149025028c61eb33e7ab
|
||||
content/44: 4e1da4edce56837c750ce8da4c0e6cf2
|
||||
content/45: 666a62d9fd54735b2adcad6277b3e07f
|
||||
content/46: db012cfc3749d025f1dd40b5db1d9d63
|
||||
content/47: 478fe7c3fbdd5e7d779691c9a09795c9
|
||||
content/48: 58d582d90c8715f5570f76fed2be508d
|
||||
content/49: 710baf5cf18c21cc284e70df97b36f40
|
||||
content/50: 6363bbb118f3f51ca1b1acf3e9ec2f7c
|
||||
content/51: 1900d5b89dbca22d7a455bdc3367f0f5
|
||||
content/52: 959f29f44825109bf4bb16129896a8dd
|
||||
content/53: fb6fddfdf4753a36c7878ef60b345822
|
||||
content/54: 191fb7087315702a36001c69d745ebed
|
||||
content/55: 1ffef0a4e0d6a6bbca85776c113e1164
|
||||
content/56: 61caafaf79e863df9525c4baf72c14e1
|
||||
content/57: ec31e300f79185f734d32b1cfaf8a137
|
||||
content/58: 65a172d64ffca3b03c6e0ed08f0bd821
|
||||
content/59: 2db387754d7fb3539bcb986dfaac1c8c
|
||||
content/60: e118d997ba48a5230ec70a564d436860
|
||||
content/61: 77268362a748dafad471f31acfd230dc
|
||||
content/62: b55b3773df2dfba66b6e675db7e2470e
|
||||
content/63: 70a9ece41fdad09f3a06ca0efdb92ae9
|
||||
content/64: 646ee615d86faf3b6a8da03115a30efa
|
||||
content/65: 5a80933fb21deea17a0a200564f0111b
|
||||
content/66: a82d5e5fad0fbfd60ca97e5312d11941
|
||||
content/67: bb172f1678686d9d49666c516716de24
|
||||
content/68: a025b3b746d72e0f676f58703ee19a47
|
||||
content/69: baa408b1603f35a8e24dd60b88773c72
|
||||
content/70: c0cc113d0001826984f9c096c79cd18b
|
||||
content/71: f6180f2341e8a7ae24afb05d7a185340
|
||||
content/72: 3d414a5669f152cd296af27b61104858
|
||||
content/73: 9a84c92505eb468916637fcf2cef70f2
|
||||
content/74: a4c78d85ed9be63b07b657166510f440
|
||||
content/75: 7431c09b430effd69de843ee0fbaafe8
|
||||
content/76: 33b9b1e9744318597da4b925b0995be2
|
||||
content/77: 6afe3b62e6d53c3dcd07149abcab4c05
|
||||
content/78: b6363faee219321c16d41a9c3f8d3bdd
|
||||
content/79: 08410ce9f0ec358b3c7230a56bc66399
|
||||
content/80: b8b23ab79a7eb32c6f8d5f49f43c51f6
|
||||
content/81: be358297e2bbb9ab4689d11d072611d1
|
||||
content/82: 09fea7c0d742a0eefa77e982e848de6c
|
||||
content/83: 7d098f0349c782f389431377ee512e92
|
||||
content/84: 22b39537f6a104803389469d211154e4
|
||||
content/85: d9ec74ab28b264d76f797fdae7c8f3d3
|
||||
content/86: f29d6bfd74ba3fee0b90180f620b4f47
|
||||
content/87: 2a59466500b62e57481fe27692a3ed0f
|
||||
content/88: cbbb123fc3a12bf2ab72dc1bbe373a6e
|
||||
content/89: 7873aa7487bc3e8a4826d65c1760a4a0
|
||||
content/90: 98182d9aabe14d5bad43a5ee76a75eab
|
||||
content/91: 67bfa8ae3e22d9a949f08c79a40b8df5
|
||||
content/92: 7079d9c00b1e1882c329b7e9b8f74552
|
||||
content/93: 0f9d65eaf6e8de43c3d5fa7e62bc838d
|
||||
content/94: bcf0ce93a4493586ad32c20d9d2b285c
|
||||
content/95: 7859d36a7a6d0122c0818b28ee29aa3e
|
||||
content/96: ce185e7b041b8f95ebc11370d3e0aad9
|
||||
content/97: dae96b41f0c029b464f02ac65d3c5796
|
||||
content/98: 41c2bb95317d7c0421817a2b1a68cc09
|
||||
content/99: 4c95f9fa55f698f220577380dff95011
|
||||
content/100: 6695bd47a05f9963134d8a71abb3d298
|
||||
content/101: 100e12673551d4ceb5b906b1b9c65059
|
||||
content/102: ce253674cd7c49320203cda2bdd3685b
|
||||
content/103: 94d4346a735149c2a83f6d2a21b8ab4c
|
||||
content/104: 3ee4b16b8204ef3b5b7c0322ff636fab
|
||||
content/105: 450265802cb0ba5b435b74b9cac1bf23
|
||||
content/106: b735ede8764e4b2dfb25967e33ab5143
|
||||
content/107: 0f881e586a03c4b916456c73fad48358
|
||||
content/108: 4570af52d41ecda8d91e6bbe2bc19891
|
||||
content/109: 55d47e12745c1b0b62c9bdf6e8449730
|
||||
content/110: 82507d357ec8766f0173b9b1081c4c56
|
||||
content/111: 3304a33dfb626c6e2267c062e8956a9d
|
||||
content/112: a88260a5b5e23da73e4534376adeb193
|
||||
content/113: cc31ae653c5642b223ec634888de29c6
|
||||
content/114: 1773624e9ac3d5132b505894ef51977e
|
||||
content/115: d62c9575cc66feec7589fba95c9f7aee
|
||||
content/116: 8df5939abc771b5d24c115ef20d42d6f
|
||||
content/117: ecd571818ddf3d31b08b80a25958a662
|
||||
content/118: 7dcdf2fbf3fce3f94987046506e12a9b
|
||||
004fe5dc5ca33719cb175f3619fe5208:
|
||||
meta/title: be754b00d8a2c13c561e314f6f526515
|
||||
content/0: 7e581dbf3e581d503ac94f7fb7938b1f
|
||||
@@ -3970,7 +4086,25 @@ checksums:
|
||||
content/7: e73f4b831f5b77c71d7d86c83abcbf11
|
||||
content/8: 07e064793f3e0bbcb02c4dc6083b6daa
|
||||
content/9: a702b191c3f94458bee880d33853e0cb
|
||||
content/10: ce110ab5da3ff96f8cbf96ce3376fc51
|
||||
content/11: 83f9b3ab46b0501c8eb3989bec3f4f1b
|
||||
content/12: e00be80effb71b0acb014f9aa53dfbe1
|
||||
content/13: 847a381137856ded9faa5994fbc489fb
|
||||
content/10: c497057cbb9dd53599071f8550f327cd
|
||||
content/11: cc6e48f85d5c6bfc05f846341f2d5cc9
|
||||
content/12: 8a80a6a97da9bf375fac565f1caabb49
|
||||
content/13: 098cc8e062187eb877fe5e172a4aa467
|
||||
content/14: e452a7cb33d7cf2f7cf1804703edaa20
|
||||
content/15: 466cfd61b1d0fcd8fc93d867dfd0f3e3
|
||||
content/16: 377572316021236994f444e88949ef34
|
||||
content/17: 54852933b2cbe3deb3b1c3059dba6a15
|
||||
content/18: 9e66b045763abe053a3ba8d2c23e9aa1
|
||||
content/19: d34f0950591e3beb085e99db64d07d2f
|
||||
content/20: 8677ef07618f7289b04fef3cce8bf745
|
||||
content/21: c0e6d2790e369569e7f272a5ec9ae21a
|
||||
content/22: 93643a0d9d9745f131e4eabf7ead2018
|
||||
content/23: 89c7da6d2e8fbc25e303a7381e147237
|
||||
content/24: a8ec63597dc3a3564bc5f0c3a6e5f42c
|
||||
content/25: 379618989b6cd427b319cfdab523297d
|
||||
content/26: bc4c2e699a7514771276e90e9aee53ba
|
||||
content/27: 38e14193b679ef774c3db93d399e700e
|
||||
content/28: ce110ab5da3ff96f8cbf96ce3376fc51
|
||||
content/29: 83f9b3ab46b0501c8eb3989bec3f4f1b
|
||||
content/30: e00be80effb71b0acb014f9aa53dfbe1
|
||||
content/31: 847a381137856ded9faa5994fbc489fb
|
||||
|
||||
@@ -9,7 +9,7 @@ export function cn(...inputs: ClassValue[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for an asset stored in Vercel Blob or local fallback
|
||||
* Get the full URL for an asset stored in Vercel Blob
|
||||
* - If CDN is configured (NEXT_PUBLIC_BLOB_BASE_URL), uses CDN URL
|
||||
* - Otherwise falls back to local static assets served from root path
|
||||
*/
|
||||
@@ -20,12 +20,3 @@ export function getAssetUrl(filename: string) {
|
||||
}
|
||||
return `/${filename}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full URL for a video asset stored in Vercel Blob or local fallback
|
||||
* - If CDN is configured (NEXT_PUBLIC_BLOB_BASE_URL), uses CDN URL
|
||||
* - Otherwise falls back to local static assets served from root path
|
||||
*/
|
||||
export function getVideoUrl(filename: string) {
|
||||
return getAssetUrl(filename)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"@vercel/analytics": "1.5.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"clsx": "^2.1.1",
|
||||
"fumadocs-core": "^15.7.5",
|
||||
"fumadocs-mdx": "^11.5.6",
|
||||
"fumadocs-ui": "^15.7.5",
|
||||
"fumadocs-core": "15.8.2",
|
||||
"fumadocs-mdx": "11.10.1",
|
||||
"fumadocs-ui": "15.8.2",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "15.4.1",
|
||||
"next-themes": "^0.4.6",
|
||||
|
||||
BIN
apps/docs/public/static/blocks/guardrails.png
Normal file
BIN
apps/docs/public/static/blocks/guardrails.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
11
apps/sim/app/(landing)/blog/layout.tsx
Normal file
11
apps/sim/app/(landing)/blog/layout.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Footer, Nav } from '@/app/(landing)/components'
|
||||
|
||||
export default function BlogLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Nav hideAuthButtons={false} variant='landing' />
|
||||
<main className='relative'>{children}</main>
|
||||
<Footer fullWidth={true} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,795 @@
|
||||
import Image from 'next/image'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
export default function OpenAiN8nSim() {
|
||||
const baseUrl = 'https://sim.ai'
|
||||
const articleUrl = `${baseUrl}/blog/openai-vs-n8n-vs-sim`
|
||||
|
||||
const articleStructuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'TechArticle',
|
||||
headline: 'OpenAI AgentKit vs n8n vs Sim: AI Agent Workflow Builder Comparison',
|
||||
description:
|
||||
'Compare OpenAI AgentKit, n8n, and Sim for building AI agent workflows. Explore key differences in capabilities, integrations, collaboration, and which platform best fits your production AI agent needs.',
|
||||
image: `${baseUrl}/blog/openai-vs-n8n-vs-sim/workflow.png`,
|
||||
datePublished: '2025-10-06T00:00:00.000Z',
|
||||
dateModified: '2025-10-06T00:00:00.000Z',
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'Emir Karabeg',
|
||||
url: 'https://x.com/karabegemir',
|
||||
sameAs: ['https://x.com/karabegemir'],
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Sim',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${baseUrl}/logo/sim-logo.png`,
|
||||
},
|
||||
url: baseUrl,
|
||||
},
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': articleUrl,
|
||||
},
|
||||
keywords:
|
||||
'AI agents, OpenAI AgentKit, n8n, Sim, workflow automation, AI agent development, RAG, MCP protocol, agentic workflows, ChatKit, AI Copilot',
|
||||
articleSection: 'Technology',
|
||||
inLanguage: 'en-US',
|
||||
about: [
|
||||
{
|
||||
'@type': 'Thing',
|
||||
name: 'Artificial Intelligence',
|
||||
},
|
||||
{
|
||||
'@type': 'Thing',
|
||||
name: 'Workflow Automation',
|
||||
},
|
||||
{
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'OpenAI AgentKit',
|
||||
},
|
||||
{
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'n8n',
|
||||
},
|
||||
{
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Sim',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const breadcrumbStructuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'Home',
|
||||
item: baseUrl,
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 2,
|
||||
name: 'Blog',
|
||||
item: `${baseUrl}/blog`,
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 3,
|
||||
name: 'OpenAI AgentKit vs n8n vs Sim',
|
||||
item: articleUrl,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Structured Data for SEO */}
|
||||
<script
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(articleStructuredData),
|
||||
}}
|
||||
/>
|
||||
<script
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(breadcrumbStructuredData),
|
||||
}}
|
||||
/>
|
||||
|
||||
<article
|
||||
className={`${soehne.className} w-full`}
|
||||
itemScope
|
||||
itemType='https://schema.org/TechArticle'
|
||||
>
|
||||
{/* Header Section with Image and Title */}
|
||||
<header className='mx-auto max-w-[1450px] px-6 pt-8 sm:px-8 sm:pt-12 md:px-12 md:pt-16'>
|
||||
<div className='flex flex-col gap-8 md:flex-row md:gap-12'>
|
||||
{/* Large Image on Left */}
|
||||
<div className='h-[180px] w-full flex-shrink-0 sm:h-[200px] md:h-auto md:w-[300px]'>
|
||||
<div className='relative h-full w-full overflow-hidden rounded-lg md:aspect-[5/4]'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/workflow.png'
|
||||
alt='Sim AI agent workflow builder interface'
|
||||
width={300}
|
||||
height={240}
|
||||
className='h-full w-full object-cover'
|
||||
priority
|
||||
itemProp='image'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Title - Taking up 80% */}
|
||||
<div className='flex flex-1 flex-col justify-between'>
|
||||
<h1
|
||||
className='font-medium text-[36px] leading-tight tracking-tight sm:text-[48px] md:text-[56px] lg:text-[64px]'
|
||||
itemProp='headline'
|
||||
>
|
||||
OpenAI AgentKit vs n8n vs Sim: AI Agent Workflow Builder Comparison
|
||||
</h1>
|
||||
<div className='mt-4 hidden items-center justify-end gap-2 sm:flex'>
|
||||
<a
|
||||
href='https://x.com/karabegemir'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer author'
|
||||
aria-label='@karabegemir on X'
|
||||
className='block'
|
||||
>
|
||||
<Avatar className='size-6'>
|
||||
<AvatarImage
|
||||
src='/blog/openai-vs-n8n-vs-sim/emir-karabeg.png'
|
||||
alt='Emir Karabeg'
|
||||
/>
|
||||
<AvatarFallback>EK</AvatarFallback>
|
||||
</Avatar>
|
||||
</a>
|
||||
<a
|
||||
href='https://x.com/karabegemir'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer author'
|
||||
className='text-[14px] text-gray-600 leading-[1.5] hover:text-gray-900 sm:text-[16px]'
|
||||
itemProp='author'
|
||||
itemScope
|
||||
itemType='https://schema.org/Person'
|
||||
>
|
||||
<span itemProp='name'>Emir Karabeg</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Horizontal Line Separator */}
|
||||
<hr className='mt-8 border-gray-200 border-t sm:mt-12' />
|
||||
|
||||
{/* Publish Date and Subtitle */}
|
||||
<div className='flex flex-col gap-6 py-8 sm:flex-row sm:items-start sm:justify-between sm:gap-8 sm:py-10'>
|
||||
{/* Publish Date and Author */}
|
||||
<div className='flex flex-shrink-0 items-center justify-between gap-4 sm:gap-0'>
|
||||
<time
|
||||
className='block text-[14px] text-gray-600 leading-[1.5] sm:text-[16px]'
|
||||
dateTime='2025-10-06T00:00:00.000Z'
|
||||
itemProp='datePublished'
|
||||
>
|
||||
Published Oct 6, 2025
|
||||
</time>
|
||||
<meta itemProp='dateModified' content='2025-10-06T00:00:00.000Z' />
|
||||
<div className='flex items-center gap-2 sm:hidden'>
|
||||
<a
|
||||
href='https://x.com/karabegemir'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer author'
|
||||
aria-label='@karabegemir on X'
|
||||
className='block'
|
||||
>
|
||||
<Avatar className='size-6'>
|
||||
<AvatarImage
|
||||
src='/blog/openai-vs-n8n-vs-sim/emir-karabeg.png'
|
||||
alt='Emir Karabeg'
|
||||
/>
|
||||
<AvatarFallback>EK</AvatarFallback>
|
||||
</Avatar>
|
||||
</a>
|
||||
<a
|
||||
href='https://x.com/karabegemir'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer author'
|
||||
className='text-[14px] text-gray-600 leading-[1.5] hover:text-gray-900'
|
||||
>
|
||||
Emir Karabeg
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subtitle on Right */}
|
||||
<div className='flex-1'>
|
||||
<p
|
||||
className='m-0 block translate-y-[-4px] font-[400] text-[18px] leading-[1.5] sm:text-[20px] md:text-[26px]'
|
||||
itemProp='description'
|
||||
>
|
||||
OpenAI just released AgentKit for building AI agents. How does it compare to
|
||||
workflow automation platforms like n8n and purpose-built AI agent builders like Sim?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content Area - Medium-style centered with padding */}
|
||||
<div className='mx-auto max-w-[800px] px-6 pb-20 sm:px-8 md:px-12' itemProp='articleBody'>
|
||||
<div className='prose prose-lg max-w-none'>
|
||||
{/* Introduction */}
|
||||
<section className='mb-12'>
|
||||
<p className='text-[20px] text-gray-800 leading-relaxed'>
|
||||
When building AI agent workflows, developers often evaluate multiple platforms to
|
||||
find the right fit for their needs. Three platforms frequently come up in these
|
||||
discussions: OpenAI's new AgentKit, the established workflow automation tool n8n,
|
||||
and Sim, a purpose-built AI agent workflow builder. While all three enable AI agent
|
||||
development, they take fundamentally different approaches, each with distinct
|
||||
strengths and ideal use cases.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Section 1: OpenAI AgentKit */}
|
||||
<section className='mb-12'>
|
||||
<h2 className='mb-4 font-medium text-[28px] leading-tight sm:text-[32px]'>
|
||||
What is OpenAI AgentKit?
|
||||
</h2>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
OpenAI AgentKit is a set of building blocks designed to help developers take AI
|
||||
agents from prototype to production. Built on top of the OpenAI Responses API, it
|
||||
provides a structured approach to building and deploying intelligent agents.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/openai.png'
|
||||
alt='OpenAI AgentKit workflow interface'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
OpenAI AgentKit visual workflow builder interface
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>Core Features</h3>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Agent Builder Canvas
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
AgentKit provides a visual canvas where developers can design and build agents. This
|
||||
interface allows you to model complex workflows visually, making it easier to
|
||||
understand and iterate on agent behavior. The builder is powered by OpenAI's
|
||||
Responses API.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
ChatKit for Embedded Interfaces
|
||||
</h4>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
ChatKit enables developers to embed chat interfaces to run workflows directly within
|
||||
their applications. It includes custom widgets that you can create and integrate,
|
||||
with the ability to preview interfaces right in the workflow builder before
|
||||
deployment.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/widgets.png'
|
||||
alt='OpenAI AgentKit custom widgets interface'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
OpenAI AgentKit ChatKit custom widgets preview
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Comprehensive Evaluation System
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
AgentKit includes out-of-the-box evaluation capabilities to measure agent
|
||||
performance. Features include datasets to assess agent nodes, prompt optimization
|
||||
tools, and the ability to run evaluations on external models beyond OpenAI's
|
||||
ecosystem.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Connectors and Integrations
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
The platform provides connectors to integrate with both internal tools and external
|
||||
services, enabling agents to interact with your existing tech stack.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>API Publishing</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Once your agent is ready, the publish feature allows you to integrate it as an API
|
||||
inside your codebase, making deployment straightforward.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Built-in Guardrails
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
AgentKit comes with guardrails out of the box, helping ensure agent behavior stays
|
||||
within defined boundaries and safety parameters.
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>
|
||||
What AgentKit Doesn't Offer
|
||||
</h3>
|
||||
<p className='mb-2 text-[19px] text-gray-800 leading-relaxed'>
|
||||
While AgentKit is powerful for building agents, it has some limitations:
|
||||
</p>
|
||||
<ul className='mb-4 ml-6 list-disc text-[19px] text-gray-800 leading-relaxed'>
|
||||
<li className='mb-2'>
|
||||
Only able to run OpenAI models—no support for other AI providers
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
Cannot make generic API requests in workflows—limited to MCP (Model Context
|
||||
Protocol) integrations only
|
||||
</li>
|
||||
<li className='mb-2'>Not an open-source platform</li>
|
||||
<li className='mb-2'>No workflow templates to accelerate development</li>
|
||||
<li className='mb-2'>
|
||||
No execution logs or detailed monitoring for debugging and observability
|
||||
</li>
|
||||
<li className='mb-2'>No ability to trigger workflows via external integrations</li>
|
||||
<li className='mb-2'>
|
||||
Limited out-of-the-box integration options compared to dedicated workflow
|
||||
automation platforms
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Section 2: n8n */}
|
||||
<section className='mb-12'>
|
||||
<h2 className='mb-4 font-medium text-[28px] leading-tight sm:text-[32px]'>
|
||||
What is n8n?
|
||||
</h2>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
n8n is a workflow automation platform that excels at connecting various services and
|
||||
APIs together. While it started as a general automation tool, n8n has evolved to
|
||||
support AI agent workflows alongside its traditional integration capabilities.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/n8n.png'
|
||||
alt='n8n workflow automation interface'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
n8n node-based visual workflow automation platform
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>Core Capabilities</h3>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Extensive Integration Library
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
n8n's primary strength lies in its vast library of pre-built integrations. With
|
||||
hundreds of connectors for popular services, it makes it easy to connect disparate
|
||||
systems without writing custom code.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Visual Workflow Builder
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
The platform provides a node-based visual interface for building workflows. Users
|
||||
can drag and drop nodes representing different services and configure how data flows
|
||||
between them.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Flexible Triggering Options
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
n8n supports various ways to trigger workflows, including webhooks, scheduled
|
||||
executions, and manual triggers, making it versatile for different automation
|
||||
scenarios.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
AI and LLM Integration
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
More recently, n8n has added support for AI models and agent-like capabilities,
|
||||
allowing users to incorporate language models into their automation workflows.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Self-Hosting Options
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
n8n offers both cloud-hosted and self-hosted deployment options, giving
|
||||
organizations control over their data and infrastructure.
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>Primary Use Cases</h3>
|
||||
<p className='mb-2 text-[19px] text-gray-800 leading-relaxed'>
|
||||
n8n is best suited for:
|
||||
</p>
|
||||
<ul className='mb-4 ml-6 list-disc text-[19px] text-gray-800 leading-relaxed'>
|
||||
<li className='mb-2'>Traditional workflow automation and service integration</li>
|
||||
<li className='mb-2'>Data synchronization between business tools</li>
|
||||
<li className='mb-2'>Event-driven automation workflows</li>
|
||||
<li className='mb-2'>Simple AI-enhanced automations</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>
|
||||
What n8n Doesn't Offer
|
||||
</h3>
|
||||
<p className='mb-2 text-[19px] text-gray-800 leading-relaxed'>
|
||||
While n8n is excellent for traditional automation, it has some limitations for AI
|
||||
agent development:
|
||||
</p>
|
||||
<ul className='mb-4 ml-6 list-disc text-[19px] text-gray-800 leading-relaxed'>
|
||||
<li className='mb-2'>
|
||||
No SDK to build workflows programmatically—limited to visual builder only
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
Not fully open source but fair-use licensed, with some restrictions
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
Free trial limited to 14 days, after which paid plans are required
|
||||
</li>
|
||||
<li className='mb-2'>Limited/complex parallel execution handling</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Section 3: Sim */}
|
||||
<section className='mb-12'>
|
||||
<h2 className='mb-4 font-medium text-[28px] leading-tight sm:text-[32px]'>
|
||||
What is Sim?
|
||||
</h2>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim is a fully open-source platform (Apache 2.0 license) specifically designed for
|
||||
AI agent development. Unlike platforms that added AI capabilities as an
|
||||
afterthought, Sim was created from the ground up to address the unique challenges of
|
||||
building, testing, and deploying production-ready AI agents.
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>
|
||||
Comprehensive AI Agent Platform
|
||||
</h3>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Visual AI Workflow Builder
|
||||
</h4>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim provides an intuitive drag-and-drop canvas where developers can build complex AI
|
||||
agent workflows visually. The platform supports sophisticated agent architectures,
|
||||
including multi-agent systems, conditional logic, loops, and parallel execution
|
||||
paths. Additionally, Sim's built-in AI Copilot can assist you directly in the
|
||||
editor, helping you build and modify workflows faster with intelligent suggestions
|
||||
and explanations.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/sim.png'
|
||||
alt='Sim visual workflow builder with AI agent blocks'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
Sim drag-and-drop AI agent workflow builder canvas
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
AI Copilot for Workflow Building
|
||||
</h4>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim includes an intelligent in-editor AI assistant that helps you build and edit
|
||||
workflows faster. Copilot can explain complex concepts, suggest best practices, and
|
||||
even make changes to your workflow when you approve them. Using the @ context menu,
|
||||
you can reference workflows, blocks, knowledge bases, documentation, templates, and
|
||||
execution logs—giving Copilot the full context it needs to provide accurate,
|
||||
relevant assistance. This dramatically accelerates workflow development compared to
|
||||
building from scratch.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/copilot.png'
|
||||
alt='Sim AI Copilot assisting with workflow development'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
Sim AI Copilot in-editor assistant for workflow building
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Pre-Built Workflow Templates
|
||||
</h4>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Get started quickly with Sim's extensive library of pre-built workflow templates.
|
||||
Browse templates across categories like Marketing, Sales, Finance, Support, and
|
||||
Artificial Intelligence. Each template is a production-ready workflow you can
|
||||
customize for your needs, saving hours of development time. Templates are created by
|
||||
the Sim team and community members, with popularity ratings and integration counts
|
||||
to help you find the right starting point.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/templates.png'
|
||||
alt='Sim workflow templates gallery'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
Sim pre-built workflow templates library
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
80+ Built-in Integrations
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Out of the box, Sim connects with 80+ services including multiple AI providers
|
||||
(OpenAI, Anthropic, Google, Groq, Cerebras, local Ollama models), communication
|
||||
tools (Gmail, Slack, Teams, Telegram, WhatsApp), productivity apps (Notion, Google
|
||||
Sheets, Airtable, Monday.com), and developer tools (GitHub, GitLab).
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Multiple Trigger Options
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Unlike AgentKit, Sim workflows can be triggered in multiple ways: chat interfaces,
|
||||
REST APIs, webhooks, scheduled jobs, or external events from integrated services
|
||||
like Slack and GitHub. This flexibility ensures your agents can be activated however
|
||||
your use case demands.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Real-Time Team Collaboration
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim enables multiple team members to work simultaneously on the same workflow with
|
||||
real-time editing, commenting, and comprehensive permissions management. This makes
|
||||
it ideal for teams building complex agent systems together.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Advanced Agent Capabilities
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
The platform includes specialized blocks for AI agents, RAG (Retrieval-Augmented
|
||||
Generation) systems, function calling, code execution, data processing, and
|
||||
evaluation. These purpose-built components enable developers to create sophisticated
|
||||
agentic workflows without custom coding.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Intelligent Knowledge Base with Vector Search
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim's native knowledge base goes far beyond simple document storage. Powered by
|
||||
pgvector, it provides semantic search that understands meaning and context, not just
|
||||
keywords. Upload documents in multiple formats (PDF, Word, Excel, Markdown, and
|
||||
more), and Sim automatically processes them with intelligent chunking, generates
|
||||
vector embeddings, and makes them instantly searchable. The knowledge base supports
|
||||
natural language queries, concept-based retrieval, multi-language understanding, and
|
||||
configurable chunk sizes (100-4,000 characters). This makes building RAG agents
|
||||
straightforward—your AI can search through your organization's knowledge with
|
||||
context-aware precision.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Comprehensive Execution Logging and Monitoring
|
||||
</h4>
|
||||
<p className='mb-6 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim provides enterprise-grade logging that captures every detail of workflow
|
||||
execution. Track workflow runs with execution IDs, view block-level logs with
|
||||
precise timing and duration metrics, monitor token usage and costs per execution,
|
||||
and debug failures with detailed error traces and trace spans. The logging system
|
||||
integrates with Copilot—you can reference execution logs directly in your Copilot
|
||||
conversations to understand what happened and troubleshoot issues. This level of
|
||||
observability is essential for production AI agents where understanding behavior and
|
||||
debugging issues quickly is critical.
|
||||
</p>
|
||||
|
||||
<figure className='my-8 overflow-hidden rounded-lg'>
|
||||
<Image
|
||||
src='/blog/openai-vs-n8n-vs-sim/logs.png'
|
||||
alt='Sim execution logs and monitoring dashboard'
|
||||
width={800}
|
||||
height={450}
|
||||
className='w-full'
|
||||
/>
|
||||
<figcaption className='sr-only'>
|
||||
Sim execution logs dashboard with detailed workflow monitoring
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Custom Integrations via MCP Protocol
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Beyond the 80+ built-in integrations, Sim supports the Model Context Protocol (MCP),
|
||||
allowing developers to create custom integrations for proprietary systems or
|
||||
specialized tools.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Flexible Deployment Options
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Sim offers both cloud-hosted and self-hosted deployment options. Organizations can
|
||||
run Sim on their own infrastructure for complete control, or use the managed cloud
|
||||
service for simplicity. The platform is SOC2 and HIPAA compliant, ensuring
|
||||
enterprise-level security.
|
||||
</p>
|
||||
|
||||
<h4 className='mt-4 mb-2 font-medium text-[19px] leading-tight'>
|
||||
Production-Ready Infrastructure
|
||||
</h4>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
The platform includes everything needed for production deployments: background job
|
||||
processing, webhook handling, monitoring, and API management. Workflows can be
|
||||
published as REST API endpoints, embedded via SDKs, or run through chat interfaces.
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>
|
||||
What You Can Build with Sim
|
||||
</h3>
|
||||
<ul className='mb-4 ml-6 list-disc text-[19px] text-gray-800 leading-relaxed'>
|
||||
<li className='mb-2'>
|
||||
<strong>AI Assistants & Chatbots:</strong> Intelligent agents that search the web,
|
||||
access calendars, send emails, and interact with business tools
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
<strong>Business Process Automation:</strong> Automate repetitive tasks like data
|
||||
entry, report generation, customer support, and content creation
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
<strong>Data Processing & Analysis:</strong> Extract insights from documents,
|
||||
analyze datasets, generate reports, and sync data between systems
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
<strong>API Integration Workflows:</strong> Connect multiple services into unified
|
||||
endpoints and orchestrate complex business logic
|
||||
</li>
|
||||
<li className='mb-2'>
|
||||
<strong>RAG Systems:</strong> Build sophisticated retrieval-augmented generation
|
||||
pipelines with custom knowledge bases
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>
|
||||
Drawbacks to Consider
|
||||
</h3>
|
||||
<p className='mb-2 text-[19px] text-gray-800 leading-relaxed'>
|
||||
While Sim excels at AI agent workflows, there are some tradeoffs:
|
||||
</p>
|
||||
<ul className='mb-4 ml-6 list-disc text-[19px] text-gray-800 leading-relaxed'>
|
||||
<li className='mb-2'>
|
||||
Fewer pre-built integrations compared to n8n's extensive library—though Sim's 80+
|
||||
integrations cover most AI agent use cases and MCP protocol enables custom
|
||||
integrations
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Comparison Section */}
|
||||
<section className='mb-12'>
|
||||
<h2 className='mb-4 font-medium text-[28px] leading-tight sm:text-[32px]'>
|
||||
Key Differences
|
||||
</h2>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
While all three platforms enable AI agent development, they excel in different
|
||||
areas:
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>OpenAI AgentKit</h3>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
<strong>Best for:</strong> Teams deeply invested in the OpenAI ecosystem who
|
||||
prioritize evaluation and testing capabilities. Ideal when you need tight
|
||||
integration with OpenAI's latest models and want built-in prompt optimization and
|
||||
evaluation tools.
|
||||
</p>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
<strong>Limitations:</strong> Locked into OpenAI models only, not open-source, no
|
||||
workflow templates or execution logs, limited triggering options, and fewer
|
||||
out-of-the-box integrations.
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>n8n</h3>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
<strong>Best for:</strong> Traditional workflow automation with some AI enhancement.
|
||||
Excellent when your primary need is connecting business tools and services, with AI
|
||||
as a complementary feature rather than the core focus.
|
||||
</p>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
<strong>Limitations:</strong> No SDK for programmatic workflow building, fair-use
|
||||
licensing (not fully open source), 14-day trial limitation, and AI agent
|
||||
capabilities are newer and less mature compared to its traditional automation
|
||||
features.
|
||||
</p>
|
||||
|
||||
<h3 className='mt-6 mb-3 font-medium text-[22px] leading-tight'>Sim</h3>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
<strong>Best for:</strong> Building production-ready AI agent workflows that require
|
||||
flexibility, collaboration, and comprehensive tooling. Ideal for teams that need AI
|
||||
Copilot assistance, advanced knowledge base features, detailed logging, and the
|
||||
ability to work across multiple AI providers with purpose-built agentic workflow
|
||||
tools.
|
||||
</p>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
<strong>Limitations:</strong> Fewer integrations than n8n's extensive library,
|
||||
though the 80+ built-in integrations and MCP protocol support cover most AI agent
|
||||
needs.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Conclusion */}
|
||||
<section className='mb-12'>
|
||||
<h2 className='mb-4 font-medium text-[28px] leading-tight sm:text-[32px]'>
|
||||
Which Should You Choose?
|
||||
</h2>
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
The right platform depends on your specific needs and context:
|
||||
</p>
|
||||
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Choose <strong>OpenAI AgentKit</strong> if you're exclusively using OpenAI models
|
||||
and want to build chat interfaces with the ChatKit. It's a solid choice for teams
|
||||
that want to stay within OpenAI's ecosystem and prioritize testing capabilities.
|
||||
</p>
|
||||
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Choose <strong>n8n</strong> if your primary use case is traditional workflow
|
||||
automation between business tools, with occasional AI enhancement. It's ideal for
|
||||
organizations already familiar with n8n who want to add some AI capabilities to
|
||||
existing automations.
|
||||
</p>
|
||||
|
||||
<p className='mb-4 text-[19px] text-gray-800 leading-relaxed'>
|
||||
Choose <strong>Sim</strong> if you're building AI agents as your primary objective
|
||||
and need a platform purpose-built for that use case. Sim provides the most
|
||||
comprehensive feature set for agentic workflows, with AI Copilot to accelerate
|
||||
development, parallel execution handling, intelligent knowledge base for RAG
|
||||
applications, detailed execution logging for production monitoring, flexibility
|
||||
across AI providers, extensive integrations, team collaboration, and deployment
|
||||
options that scale from prototype to production.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Publisher information for schema */}
|
||||
<meta itemProp='publisher' content='Sim' />
|
||||
<meta itemProp='inLanguage' content='en-US' />
|
||||
<meta
|
||||
itemProp='keywords'
|
||||
content='AI agents, OpenAI AgentKit, n8n, Sim, workflow automation'
|
||||
/>
|
||||
</article>
|
||||
</>
|
||||
)
|
||||
}
|
||||
135
apps/sim/app/(landing)/blog/openai-vs-n8n-vs-sim/page.tsx
Normal file
135
apps/sim/app/(landing)/blog/openai-vs-n8n-vs-sim/page.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import type { Metadata } from 'next'
|
||||
import OpenAiN8nSim from './openai-n8n-sim'
|
||||
|
||||
const baseUrl = 'https://sim.ai'
|
||||
const canonicalUrl = `${baseUrl}/blog/openai-vs-n8n-vs-sim`
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'OpenAI AgentKit vs n8n vs Sim: AI Agent Workflow Builder Comparison | Sim',
|
||||
description:
|
||||
'Compare OpenAI AgentKit, n8n, and Sim for building AI agent workflows. Explore key differences in capabilities, integrations, collaboration, and which platform best fits your production AI agent needs.',
|
||||
keywords: [
|
||||
'AgentKit',
|
||||
'AI agents',
|
||||
'AI agent development',
|
||||
'agents',
|
||||
'workflow builder',
|
||||
'visual workflow builder',
|
||||
'workflows',
|
||||
'OpenAI AgentKit',
|
||||
'OpenAI',
|
||||
'OpenAI Responses API',
|
||||
'n8n',
|
||||
'n8n workflow automation',
|
||||
'AI workflow automation',
|
||||
'workflow automation platform',
|
||||
'Sim',
|
||||
'agent builder comparison',
|
||||
'RAG agents',
|
||||
'RAG systems',
|
||||
'retrieval augmented generation',
|
||||
'ChatKit',
|
||||
'agent evaluation',
|
||||
'prompt optimization',
|
||||
'multi-agent systems',
|
||||
'team collaboration workflows',
|
||||
'production AI agents',
|
||||
'AI guardrails',
|
||||
'workflow integrations',
|
||||
'self-hosted AI agents',
|
||||
'cloud AI agent platform',
|
||||
'MCP protocol',
|
||||
'Model Context Protocol',
|
||||
'knowledge base integration',
|
||||
'vector embeddings',
|
||||
'AI agent canvas',
|
||||
'agentic workflows',
|
||||
'AI agent API',
|
||||
'AI chatbot workflows',
|
||||
'business process automation',
|
||||
'AI Copilot',
|
||||
'workflow copilot',
|
||||
'AI assistant for workflows',
|
||||
'vector search',
|
||||
'semantic search',
|
||||
'pgvector',
|
||||
'knowledge base AI',
|
||||
'document embeddings',
|
||||
'execution logging',
|
||||
'workflow monitoring',
|
||||
'AI agent observability',
|
||||
'workflow debugging',
|
||||
'execution traces',
|
||||
'AI workflow logs',
|
||||
'intelligent chunking',
|
||||
'context-aware search',
|
||||
],
|
||||
authors: [{ name: 'Emir Karabeg', url: 'https://x.com/karabegemir' }],
|
||||
creator: 'Emir Karabeg',
|
||||
publisher: 'Sim',
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
alternates: {
|
||||
canonical: canonicalUrl,
|
||||
},
|
||||
openGraph: {
|
||||
title: 'OpenAI AgentKit vs n8n vs Sim: AI Agent Workflow Builder Comparison',
|
||||
description:
|
||||
'Compare OpenAI AgentKit, n8n, and Sim for building AI agent workflows. Explore key differences in capabilities, integrations, and which platform fits your production needs.',
|
||||
url: canonicalUrl,
|
||||
siteName: 'Sim',
|
||||
locale: 'en_US',
|
||||
type: 'article',
|
||||
publishedTime: '2025-10-06T00:00:00.000Z',
|
||||
modifiedTime: '2025-10-06T00:00:00.000Z',
|
||||
authors: ['Emir Karabeg'],
|
||||
section: 'Technology',
|
||||
tags: [
|
||||
'AI Agents',
|
||||
'Workflow Automation',
|
||||
'OpenAI AgentKit',
|
||||
'n8n',
|
||||
'Sim',
|
||||
'AgentKit',
|
||||
'AI Development',
|
||||
'RAG',
|
||||
'MCP Protocol',
|
||||
],
|
||||
images: [
|
||||
{
|
||||
url: `${baseUrl}/blog/openai-vs-n8n-vs-sim/workflow.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Sim AI agent workflow builder interface comparison',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'OpenAI AgentKit vs n8n vs Sim: AI Agent Workflow Builder Comparison',
|
||||
description:
|
||||
'Compare OpenAI AgentKit, n8n, and Sim for building AI agent workflows. Explore key differences in capabilities, integrations, and which platform fits your production needs.',
|
||||
images: ['/blog/openai-vs-n8n-vs-sim/workflow.png'],
|
||||
creator: '@karabegemir',
|
||||
site: '@simai',
|
||||
},
|
||||
other: {
|
||||
'article:published_time': '2025-10-06T00:00:00.000Z',
|
||||
'article:modified_time': '2025-10-06T00:00:00.000Z',
|
||||
'article:author': 'Emir Karabeg',
|
||||
'article:section': 'Technology',
|
||||
},
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return <OpenAiN8nSim />
|
||||
}
|
||||
5
apps/sim/app/(landing)/blog/page.tsx
Normal file
5
apps/sim/app/(landing)/blog/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export default function BlogPage() {
|
||||
redirect('/blog/openai-vs-n8n-vs-sim')
|
||||
}
|
||||
@@ -215,6 +215,12 @@ export default function Footer({ fullWidth = false }: FooterProps) {
|
||||
>
|
||||
Enterprise
|
||||
</Link>
|
||||
<Link
|
||||
href='/blog'
|
||||
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
<Link
|
||||
href='/changelog'
|
||||
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'
|
||||
|
||||
@@ -20,7 +20,7 @@ interface NavProps {
|
||||
}
|
||||
|
||||
export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) {
|
||||
const [githubStars, setGithubStars] = useState('15.4k')
|
||||
const [githubStars, setGithubStars] = useState('16.3k')
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [isLoginHovered, setIsLoginHovered] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
|
||||
interface Testimonial {
|
||||
@@ -14,7 +13,6 @@ interface Testimonial {
|
||||
profileImage: string
|
||||
}
|
||||
|
||||
// Import all testimonials
|
||||
const allTestimonials: Testimonial[] = [
|
||||
{
|
||||
text: "🚨 BREAKING: This startup just dropped the fastest way to build AI agents.\n\nThis Figma-like canvas to build agents will blow your mind.\n\nHere's why this is the best tool for building AI agents:",
|
||||
@@ -22,7 +20,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@hasantoxr',
|
||||
viewCount: '515k',
|
||||
tweetUrl: 'https://x.com/hasantoxr/status/1912909502036525271',
|
||||
profileImage: getAssetUrl('twitter/hasan.jpg'),
|
||||
profileImage: '/twitter/hasan.jpg',
|
||||
},
|
||||
{
|
||||
text: "Drag-and-drop AI workflows for devs who'd rather build agents than babysit them.",
|
||||
@@ -30,7 +28,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@GithubProjects',
|
||||
viewCount: '90.4k',
|
||||
tweetUrl: 'https://x.com/GithubProjects/status/1906383555707490499',
|
||||
profileImage: getAssetUrl('twitter/github-projects.jpg'),
|
||||
profileImage: '/twitter/github-projects.jpg',
|
||||
},
|
||||
{
|
||||
text: "🚨 BREAKING: This startup just dropped the fastest way to build AI agents.\n\nThis Figma-like canvas to build agents will blow your mind.\n\nHere's why this is the best tool for building AI agents:",
|
||||
@@ -38,7 +36,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@lazukars',
|
||||
viewCount: '47.4k',
|
||||
tweetUrl: 'https://x.com/lazukars/status/1913136390503600575',
|
||||
profileImage: getAssetUrl('twitter/lazukars.png'),
|
||||
profileImage: '/twitter/lazukars.png',
|
||||
},
|
||||
{
|
||||
text: 'omfggggg this is the zapier of agent building\n\ni always believed that building agents and using ai should not be limited to technical people. i think this solves just that\n\nthe fact that this is also open source makes me so optimistic about the future of building with ai :)))\n\ncongrats @karabegemir & @typingwala !!!',
|
||||
@@ -46,7 +44,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@nizzyabi',
|
||||
viewCount: '6,269',
|
||||
tweetUrl: 'https://x.com/nizzyabi/status/1907864421227180368',
|
||||
profileImage: getAssetUrl('twitter/nizzy.jpg'),
|
||||
profileImage: '/twitter/nizzy.jpg',
|
||||
},
|
||||
{
|
||||
text: 'A very good looking agent workflow builder 🔥 and open source!',
|
||||
@@ -54,7 +52,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@xyflowdev',
|
||||
viewCount: '3,246',
|
||||
tweetUrl: 'https://x.com/xyflowdev/status/1909501499719438670',
|
||||
profileImage: getAssetUrl('twitter/xyflow.jpg'),
|
||||
profileImage: '/twitter/xyflow.jpg',
|
||||
},
|
||||
{
|
||||
text: "One of the best products I've seen in the space, and the hustle and grind I've seen from @karabegemir and @typingwala is insane. Sim is positioned to build something game-changing, and there's no better team for the job.\n\nCongrats on the launch 🚀 🎊 great things ahead!",
|
||||
@@ -62,7 +60,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@firestorm776',
|
||||
viewCount: '1,256',
|
||||
tweetUrl: 'https://x.com/firestorm776/status/1907896097735061598',
|
||||
profileImage: getAssetUrl('twitter/samarth.jpg'),
|
||||
profileImage: '/twitter/samarth.jpg',
|
||||
},
|
||||
{
|
||||
text: 'lfgg got access to @simstudioai via @zerodotemail 😎',
|
||||
@@ -70,7 +68,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@nizzyabi',
|
||||
viewCount: '1,762',
|
||||
tweetUrl: 'https://x.com/nizzyabi/status/1910482357821595944',
|
||||
profileImage: getAssetUrl('twitter/nizzy.jpg'),
|
||||
profileImage: '/twitter/nizzy.jpg',
|
||||
},
|
||||
{
|
||||
text: 'Feels like we\'re finally getting a "Photoshop moment" for AI devs—visual, intuitive, and fast enough to keep up with ideas mid-flow.',
|
||||
@@ -78,7 +76,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@syamrajk',
|
||||
viewCount: '2,784',
|
||||
tweetUrl: 'https://x.com/syamrajk/status/1912911980110946491',
|
||||
profileImage: getAssetUrl('twitter/syamrajk.jpg'),
|
||||
profileImage: '/twitter/syamrajk.jpg',
|
||||
},
|
||||
{
|
||||
text: 'The use cases are endless. Great work @simstudioai',
|
||||
@@ -86,7 +84,7 @@ const allTestimonials: Testimonial[] = [
|
||||
username: '@daniel_zkim',
|
||||
viewCount: '103',
|
||||
tweetUrl: 'https://x.com/daniel_zkim/status/1907891273664782708',
|
||||
profileImage: getAssetUrl('twitter/daniel.jpg'),
|
||||
profileImage: '/twitter/daniel.jpg',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -95,11 +93,9 @@ export default function Testimonials() {
|
||||
const [isTransitioning, setIsTransitioning] = useState(false)
|
||||
const [isPaused, setIsPaused] = useState(false)
|
||||
|
||||
// Create an extended array for smooth infinite scrolling
|
||||
const extendedTestimonials = [...allTestimonials, ...allTestimonials]
|
||||
|
||||
useEffect(() => {
|
||||
// Set up automatic sliding every 3 seconds
|
||||
const interval = setInterval(() => {
|
||||
if (!isPaused) {
|
||||
setIsTransitioning(true)
|
||||
@@ -110,17 +106,15 @@ export default function Testimonials() {
|
||||
return () => clearInterval(interval)
|
||||
}, [isPaused])
|
||||
|
||||
// Reset position when reaching the end for infinite loop
|
||||
useEffect(() => {
|
||||
if (currentIndex >= allTestimonials.length) {
|
||||
setTimeout(() => {
|
||||
setIsTransitioning(false)
|
||||
setCurrentIndex(0)
|
||||
}, 500) // Match transition duration
|
||||
}, 500)
|
||||
}
|
||||
}, [currentIndex])
|
||||
|
||||
// Calculate the transform value
|
||||
const getTransformValue = () => {
|
||||
// Each card unit (card + separator) takes exactly 25% width
|
||||
return `translateX(-${currentIndex * 25}%)`
|
||||
|
||||
@@ -15,15 +15,16 @@ export default function PrivacyPolicy() {
|
||||
return (
|
||||
<LegalLayout title='Privacy Policy'>
|
||||
<section>
|
||||
<p className='mb-4'>Last Updated: September 10, 2025</p>
|
||||
<p className='mb-4'>Last Updated: October 11, 2025</p>
|
||||
<p>
|
||||
This Privacy Policy describes how your personal information is collected, used, and shared
|
||||
when you visit or use Sim ("the Service", "we", "us", or "our").
|
||||
This Privacy Policy describes how Sim ("we", "us", "our", or "the Service") collects,
|
||||
uses, discloses, and protects personal data — including data obtained from Google APIs
|
||||
(including Google Workspace APIs) — and your rights and controls regarding that data.
|
||||
</p>
|
||||
<p className='mt-4'>
|
||||
By using the Service, you agree to the collection and use of information in accordance
|
||||
with this policy. Unless otherwise defined in this Privacy Policy, terms used in this
|
||||
Privacy Policy have the same meanings as in our Terms of Service.
|
||||
By using or accessing the Service, you confirm that you have read and understood this
|
||||
Privacy Policy, and you consent to the collection, use, and disclosure of your information
|
||||
as described herein.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -39,6 +40,10 @@ export default function PrivacyPolicy() {
|
||||
<h3 className='mb-2 font-medium text-xl'>Definitions</h3>
|
||||
<p className='mb-4'>For the purposes of this Privacy Policy:</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>
|
||||
<strong>Application</strong> or <strong>Service</strong> means the Sim web or mobile
|
||||
application or related services.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Account</strong> means a unique account created for You to access our Service or
|
||||
parts of our Service.
|
||||
@@ -49,10 +54,6 @@ export default function PrivacyPolicy() {
|
||||
shares, equity interest or other securities entitled to vote for election of directors
|
||||
or other managing authority.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Application</strong> means the software program provided by the Company
|
||||
downloaded by You on any electronic device.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Business</strong>, for the purpose of the CCPA (California Consumer Privacy
|
||||
Act), refers to the Company as the legal entity that collects Consumers' personal
|
||||
@@ -90,13 +91,26 @@ export default function PrivacyPolicy() {
|
||||
tracking of their online activities across websites.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Personal Data</strong> is any information that relates to an identified or
|
||||
identifiable individual. For the purposes for GDPR, Personal Data means any information
|
||||
relating to You such as a name, an identification number, location data, online
|
||||
identifier or to one or more factors specific to the physical, physiological, genetic,
|
||||
mental, economic, cultural or social identity. For the purposes of the CCPA, Personal
|
||||
Data means any information that identifies, relates to, describes or is capable of being
|
||||
associated with, or could reasonably be linked, directly or indirectly, with You.
|
||||
<strong>Personal Data</strong> (or "Personal Information") is any information that
|
||||
relates to an identified or identifiable individual. For the purposes for GDPR, Personal
|
||||
Data means any information relating to You such as a name, an identification number,
|
||||
location data, online identifier or to one or more factors specific to the physical,
|
||||
physiological, genetic, mental, economic, cultural or social identity. For the purposes
|
||||
of the CCPA, Personal Data means any information that identifies, relates to, describes
|
||||
or is capable of being associated with, or could reasonably be linked, directly or
|
||||
indirectly, with You.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google Data</strong> means any data, content, or metadata obtained via Google
|
||||
APIs (including Google Workspace APIs).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Generalized AI/ML model</strong> means an AI or ML model intended to be broadly
|
||||
trained across multiple users, not specific to a single user's data or behavior.
|
||||
</li>
|
||||
<li>
|
||||
<strong>User-facing features</strong> means features directly visible or used by the
|
||||
individual user through the app UI.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Sale</strong>, for the purpose of the CCPA (California Consumer Privacy Act),
|
||||
@@ -105,9 +119,6 @@ export default function PrivacyPolicy() {
|
||||
means, a Consumer's Personal information to another business or a third party for
|
||||
monetary or other valuable consideration.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Service</strong> refers to the Application or the Website or both.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Service Provider</strong> means any natural or legal person who processes the
|
||||
data on behalf of the Company. It refers to third-party companies or individuals
|
||||
@@ -140,20 +151,37 @@ export default function PrivacyPolicy() {
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>1. Information We Collect</h2>
|
||||
<h3 className='mb-2 font-medium text-xl'>Personal Information</h3>
|
||||
<h3 className='mb-2 font-medium text-xl'>Personal Data You Provide</h3>
|
||||
<p className='mb-4'>
|
||||
While using our Service, we may ask you to provide us with certain personally identifiable
|
||||
information that can be used to contact or identify you ("Personal Information").
|
||||
Personally identifiable information may include, but is not limited to:
|
||||
When you sign up, link accounts, or use features, you may provide Personal Data such as:
|
||||
</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>Email address</li>
|
||||
<li>First name and last name</li>
|
||||
<li>Phone number</li>
|
||||
<li>Address, State, Province, ZIP/Postal code, City</li>
|
||||
<li>Cookies and Usage Data</li>
|
||||
<li>Name and email address</li>
|
||||
<li>Phone number and mailing address</li>
|
||||
<li>Profile picture, settings, and preferences</li>
|
||||
<li>Content you upload (e.g., documents, files) within Sim</li>
|
||||
<li>Any data you explicitly input or connect, including via Google integrations</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Google Data via API Scopes</h3>
|
||||
<p className='mb-4'>
|
||||
If you choose to connect your Google account (e.g., Google Workspace, Gmail, Drive,
|
||||
Calendar, Contacts), we may request specific scopes. Types of Google Data we may access
|
||||
include:
|
||||
</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>Basic profile (name, email)</li>
|
||||
<li>Drive files and documents</li>
|
||||
<li>Calendar events</li>
|
||||
<li>Contacts</li>
|
||||
<li>Gmail messages (only if explicitly requested for a specific feature)</li>
|
||||
<li>Other Google Workspace content or metadata as needed per feature</li>
|
||||
</ul>
|
||||
<p className='mb-4'>
|
||||
We only request the minimal scopes necessary for the features you enable. We do not
|
||||
request scopes for unimplemented features.
|
||||
</p>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Usage Data</h3>
|
||||
<p className='mb-4'>
|
||||
We may also collect information on how the Service is accessed and used ("Usage Data").
|
||||
@@ -212,7 +240,21 @@ export default function PrivacyPolicy() {
|
||||
To contact You by email, telephone calls, SMS, or other equivalent forms of electronic
|
||||
communication
|
||||
</li>
|
||||
<li>
|
||||
To enable and support user-enabled integrations with Google services (e.g., syncing
|
||||
files or calendar) and provide personalization, suggestions, and user-specific
|
||||
automation for that individual user.
|
||||
</li>
|
||||
<li>
|
||||
To detect and prevent fraud, abuse, or security incidents and to comply with legal
|
||||
obligations.
|
||||
</li>
|
||||
</ul>
|
||||
<p className='mt-4'>
|
||||
<strong>Importantly:</strong> any Google Data used within Sim is used only for features
|
||||
tied to that specific user (user-facing features), and <strong>never</strong> for
|
||||
generalized AI/ML training or shared model improvement across users.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -290,32 +332,10 @@ export default function PrivacyPolicy() {
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>7. Analytics</h2>
|
||||
<p className='mb-4'>
|
||||
We may use third-party Service Providers to monitor and analyze the use of our Service.
|
||||
</p>
|
||||
<h3 className='mb-2 font-medium text-xl'>Google Analytics</h3>
|
||||
<p className='mb-4'>
|
||||
Google Analytics is a web analytics service offered by Google that tracks and reports
|
||||
website traffic. Google uses the data collected to track and monitor the use of our
|
||||
Service. This data is shared with other Google services. Google may use the collected data
|
||||
to contextualize and personalize the ads of its own advertising network.
|
||||
</p>
|
||||
<p className='mb-4'>
|
||||
You can opt-out of having made your activity on the Service available to Google Analytics
|
||||
by installing the Google Analytics opt-out browser add-on. The add-on prevents the Google
|
||||
Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google
|
||||
Analytics about visits activity.
|
||||
</p>
|
||||
<p>
|
||||
For more information on the privacy practices of Google, please visit the Google Privacy &
|
||||
Terms web page:{' '}
|
||||
<Link
|
||||
href='https://policies.google.com/privacy?hl=en'
|
||||
className='text-[var(--brand-primary-hex)] underline hover:text-[var(--brand-primary-hover-hex)]'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
https://policies.google.com/privacy
|
||||
</Link>
|
||||
We may aggregate or anonymize non-Google data (not tied to personal identity) for internal
|
||||
analytics, product improvement, usage trends, or performance monitoring. This data cannot
|
||||
be tied back to individual users and is not used for generalized AI/ML training with
|
||||
Google Data.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -366,29 +386,117 @@ export default function PrivacyPolicy() {
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>10. Google Workspace APIs</h2>
|
||||
<p className='mb-4'>
|
||||
We want to explicitly affirm that any user data obtained through Google Workspace APIs is{' '}
|
||||
<strong>not</strong> used to develop, improve, or train generalized AI and/or machine
|
||||
learning models. We use data obtained through Google Workspace APIs solely for the purpose
|
||||
of providing and improving the specific functionality of our Service for which the API
|
||||
access was granted.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>
|
||||
11. Information Collected while Using Google APIs
|
||||
10. Use of Google / Workspace APIs & Data — Limited Use
|
||||
</h2>
|
||||
<h3 className='mb-2 font-medium text-xl'>Affirmative Statement & Compliance</h3>
|
||||
<p className='mb-4'>
|
||||
Sim's use and transfer to any other app of information received from Google APIs will
|
||||
adhere to Google API Services User Data Policy, including the Limited Use requirements.
|
||||
Sim’s use, storage, processing, and transfer of Google Data (raw or derived) strictly
|
||||
adheres to the Google API Services User Data Policy, including the Limited Use
|
||||
requirements, and to the Google Workspace API user data policy (when applicable). We
|
||||
explicitly affirm that:
|
||||
</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>
|
||||
Sim does not use, transfer, or allow Google Data to be used to train, improve, or
|
||||
develop generalized or non-personalized AI/ML models.
|
||||
</li>
|
||||
<li>
|
||||
Any processing of Google Data is limited to providing or improving user-facing features
|
||||
visible in the app UI.
|
||||
</li>
|
||||
<li>
|
||||
We do not allow third parties to access Google Data for purposes of training or model
|
||||
improvement.
|
||||
</li>
|
||||
<li>Transfers of Google Data are disallowed except in limited permitted cases.</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Permitted Transfers & Data Use</h3>
|
||||
<p className='mb-4'>
|
||||
We may only transfer Google Data (raw or derived) to third parties under the following
|
||||
limited conditions and always aligned with user disclosures and consent:
|
||||
</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>To provide or improve user-facing features (with the user's explicit consent)</li>
|
||||
<li>For security, abuse investigation, or system integrity</li>
|
||||
<li>To comply with laws or legal obligations</li>
|
||||
<li>
|
||||
As part of a merger, acquisition, divestiture, or sale of assets, with explicit user
|
||||
consent
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Human Access Restrictions</h3>
|
||||
<p className='mb-4'>
|
||||
We restrict human review of Google Data strictly. No employee, contractor, or agent may
|
||||
view Google Data unless one of the following is true:
|
||||
</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>
|
||||
The user gave explicit, documented consent to view specific items (e.g., "Let customer
|
||||
support view this email/file").
|
||||
</li>
|
||||
<li>It is necessary for security, abuse investigation, or legal process.</li>
|
||||
<li>
|
||||
Data is aggregated, anonymized, and used for internal operations only (without
|
||||
re-identification).
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Scope Minimization & Justification</h3>
|
||||
<p className='mb-4'>
|
||||
We only request scopes essential to features you opt into; we do not request broad or
|
||||
unused permissions. For each Google API scope we request, we maintain internal
|
||||
documentation justifying why that scope is needed and why narrower scopes are
|
||||
insufficient. Where possible, we follow incremental authorization and request additional
|
||||
scopes only when needed in context.
|
||||
</p>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Secure Handling & Storage</h3>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>Google Data is encrypted in transit (TLS/HTTPS) and at rest.</li>
|
||||
<li>Access controls, role-based permissions, logging, and auditing protect data.</li>
|
||||
<li>
|
||||
OAuth tokens and credentials are stored securely (e.g., encrypted vault, hardware or
|
||||
secure key management).
|
||||
</li>
|
||||
<li>We regularly review security practices and infrastructure.</li>
|
||||
<li>
|
||||
If a security incident affects Google Data, we will notify Google as required and
|
||||
cooperate fully.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Retention & Deletion</h3>
|
||||
<p className='mb-4'>We retain data only as long as necessary for the purposes disclosed:</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>
|
||||
<strong>Account Data:</strong> Retained during active account + 30 days after deletion
|
||||
request
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google API Data:</strong> Retained during feature use + 7 days after revocation
|
||||
or account deletion
|
||||
</li>
|
||||
<li>
|
||||
<strong>Usage Logs:</strong> 90 days for analytics; up to 1 year for security
|
||||
investigations
|
||||
</li>
|
||||
<li>
|
||||
<strong>Transaction Records:</strong> Up to 7 years for legal and tax compliance
|
||||
</li>
|
||||
</ul>
|
||||
<p className='mb-4'>
|
||||
When you revoke access, delete your account, or stop using a feature, we remove associated
|
||||
data within the timeframes above. You may request deletion via in-app settings or by
|
||||
contacting us; we will comply promptly.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>12. Links To Other Sites</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>11. Links To Other Sites</h2>
|
||||
<p className='mb-4'>
|
||||
Our Service may contain links to other sites that are not operated by us. If you click on
|
||||
a third party link, you will be directed to that third party's site. We strongly advise
|
||||
@@ -401,7 +509,7 @@ export default function PrivacyPolicy() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>13. Children's Privacy</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>12. Children's Privacy</h2>
|
||||
<p className='mb-4'>
|
||||
Our Service does not address anyone under the age of 18 ("Children").
|
||||
</p>
|
||||
@@ -415,7 +523,7 @@ export default function PrivacyPolicy() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>14. Changes To This Privacy Policy</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>13. Changes To This Privacy Policy</h2>
|
||||
<p className='mb-4'>
|
||||
We may update our Privacy Policy from time to time. We will notify you of any changes by
|
||||
posting the new Privacy Policy on this page.
|
||||
@@ -433,7 +541,7 @@ export default function PrivacyPolicy() {
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>
|
||||
15. Your Data Protection Rights Under General Data Protection Regulation (GDPR)
|
||||
14. Your Data Protection Rights Under General Data Protection Regulation (GDPR)
|
||||
</h2>
|
||||
<p className='mb-4'>
|
||||
If you are a resident of the European Economic Area (EEA), you have certain data
|
||||
@@ -482,23 +590,50 @@ export default function PrivacyPolicy() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>16. California Privacy Rights</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>15. California Privacy Rights</h2>
|
||||
<p className='mb-4'>
|
||||
California Civil Code Section 1798.83, also known as the "Shine The Light" law, permits
|
||||
our users who are California residents to request and obtain from us, once a year and free
|
||||
of charge, information about categories of personal information (if any) we disclosed to
|
||||
third parties for direct marketing purposes and the names and addresses of all third
|
||||
parties with which we shared personal information in the immediately preceding calendar
|
||||
year.
|
||||
If you are a California resident, you have specific rights under the California Consumer
|
||||
Privacy Act (CCPA) and California Privacy Rights Act (CPRA), including the right to know
|
||||
what personal information we collect, the right to delete your information, and the right
|
||||
to opt-out of the sale or sharing of your personal information.
|
||||
</p>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Do Not Sell or Share My Personal Information</h3>
|
||||
<p className='mb-4'>
|
||||
We do not sell your personal information for monetary consideration. However, some data
|
||||
sharing practices (such as analytics or advertising services) may be considered a "sale"
|
||||
or "share" under CCPA/CPRA. You have the right to opt-out of such data sharing. To
|
||||
exercise this right, contact us at{' '}
|
||||
<Link
|
||||
href='mailto:privacy@sim.ai'
|
||||
className='text-[var(--brand-primary-hex)] underline hover:text-[var(--brand-primary-hover-hex)]'
|
||||
>
|
||||
privacy@sim.ai
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Global Privacy Control (GPC)</h3>
|
||||
<p className='mb-4'>
|
||||
We recognize and honor Global Privacy Control (GPC) signals. When your browser sends a GPC
|
||||
signal, we will treat it as a valid request to opt-out of the sale or sharing of your
|
||||
personal information.
|
||||
</p>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Shine The Light Law</h3>
|
||||
<p className='mb-4'>
|
||||
California Civil Code Section 1798.83 permits California residents to request information
|
||||
about categories of personal information we disclosed to third parties for direct
|
||||
marketing purposes in the preceding calendar year.
|
||||
</p>
|
||||
<p>
|
||||
If you are a California resident and would like to make such a request, please submit your
|
||||
request in writing to us using the contact information provided below.
|
||||
To make a request under CCPA or the Shine The Light law, please submit your request using
|
||||
the contact information provided below.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>17. Vulnerability Disclosure Policy</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>16. Vulnerability Disclosure Policy</h2>
|
||||
|
||||
<h3 className='mb-2 font-medium text-xl'>Introduction</h3>
|
||||
<p className='mb-4'>
|
||||
@@ -617,16 +752,24 @@ export default function PrivacyPolicy() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>18. Contact Us</h2>
|
||||
<p>
|
||||
If you have any questions about this Privacy Policy, please contact us at:{' '}
|
||||
<Link
|
||||
href='mailto:privacy@sim.ai'
|
||||
className='text-[var(--brand-primary-hex)] underline hover:text-[var(--brand-primary-hover-hex)]'
|
||||
>
|
||||
privacy@sim.ai
|
||||
</Link>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>17. Contact & Dispute Resolution</h2>
|
||||
<p className='mb-4'>
|
||||
If you have questions, requests, or complaints regarding this Privacy Policy or our data
|
||||
practices, you may contact us at:
|
||||
</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>
|
||||
Email:{' '}
|
||||
<Link
|
||||
href='mailto:privacy@sim.ai'
|
||||
className='text-[var(--brand-primary-hex)] underline hover:text-[var(--brand-primary-hover-hex)]'
|
||||
>
|
||||
privacy@sim.ai
|
||||
</Link>
|
||||
</li>
|
||||
<li>Mailing Address: Sim, 80 Langton St, San Francisco, CA 94133, USA</li>
|
||||
</ul>
|
||||
<p>We will respond to your request within a reasonable timeframe.</p>
|
||||
</section>
|
||||
</LegalLayout>
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function TermsOfService() {
|
||||
return (
|
||||
<LegalLayout title='Terms of Service'>
|
||||
<section>
|
||||
<p className='mb-4'>Last Updated: September 10, 2025</p>
|
||||
<p className='mb-4'>Last Updated: October 11, 2025</p>
|
||||
<p>
|
||||
Please read these Terms of Service ("Terms") carefully before using the Sim platform (the
|
||||
"Service") operated by Sim, Inc ("us", "we", or "our").
|
||||
@@ -44,7 +44,71 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>2. Intellectual Property</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>2. License to Use Service</h2>
|
||||
<p className='mb-4'>
|
||||
Subject to your compliance with these Terms, we grant you a limited, non-exclusive,
|
||||
non-transferable, revocable license to access and use the Service for your internal
|
||||
business or personal purposes.
|
||||
</p>
|
||||
<p>
|
||||
This license does not permit you to resell, redistribute, or make the Service available to
|
||||
third parties, or to use the Service to build a competitive product or service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>3. Subscription Plans & Payment Terms</h2>
|
||||
<p className='mb-4'>
|
||||
We offer Free, Pro, Team, and Enterprise subscription plans. Paid plans include a base
|
||||
subscription fee plus usage-based charges for inference and other services that exceed
|
||||
your plan's included limits.
|
||||
</p>
|
||||
<p className='mb-4'>
|
||||
You agree to pay all fees associated with your account. Your base subscription fee is
|
||||
charged at the beginning of each billing cycle (monthly or annually). Inference overages
|
||||
are charged incrementally every $50 during your billing period, which may result in
|
||||
multiple invoices within a single billing cycle. Payment is due upon receipt of invoice.
|
||||
If payment fails, we may suspend or terminate your access to paid features.
|
||||
</p>
|
||||
<p>
|
||||
We reserve the right to change our pricing with 30 days' notice to paid subscribers. Price
|
||||
changes will take effect at your next renewal.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>4. Auto-Renewal & Cancellation</h2>
|
||||
<p className='mb-4'>
|
||||
Paid subscriptions automatically renew at the end of each billing period unless you cancel
|
||||
before the renewal date. You can cancel your subscription at any time through your account
|
||||
settings or by contacting us.
|
||||
</p>
|
||||
<p className='mb-4'>
|
||||
Cancellations take effect at the end of the current billing period. You will retain access
|
||||
to paid features until that time. We do not provide refunds for partial billing periods.
|
||||
</p>
|
||||
<p>
|
||||
Upon cancellation or termination, you may export your data within 30 days. After 30 days,
|
||||
we may delete your data in accordance with our data retention policies.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>5. Data Ownership & Retention</h2>
|
||||
<p className='mb-4'>
|
||||
You retain all ownership rights to data, content, and information you submit to the
|
||||
Service ("Your Data"). You grant us a limited license to process, store, and transmit Your
|
||||
Data solely to provide and improve the Service as described in our Privacy Policy.
|
||||
</p>
|
||||
<p>
|
||||
We retain Your Data while your account is active and for 30 days after account termination
|
||||
or cancellation. You may request data export or deletion at any time through your account
|
||||
settings.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>6. Intellectual Property</h2>
|
||||
<p className='mb-4'>
|
||||
The Service and its original content, features, and functionality are and will remain the
|
||||
exclusive property of Sim, Inc and its licensors. The Service is protected by copyright,
|
||||
@@ -57,7 +121,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>3. User Content</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>7. User Content</h2>
|
||||
<p className='mb-4'>
|
||||
Our Service allows you to post, link, store, share and otherwise make available certain
|
||||
information, text, graphics, videos, or other material ("User Content"). You are
|
||||
@@ -84,7 +148,21 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>4. Acceptable Use</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>8. Third-Party Services</h2>
|
||||
<p className='mb-4'>
|
||||
The Service may integrate with third-party services (such as Google Workspace, cloud
|
||||
storage providers, and AI model providers). Your use of third-party services is subject to
|
||||
their respective terms and privacy policies.
|
||||
</p>
|
||||
<p>
|
||||
We are not responsible for the availability, functionality, or actions of third-party
|
||||
services. Any issues with third-party integrations should be directed to the respective
|
||||
provider.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>9. Acceptable Use</h2>
|
||||
<p className='mb-4'>You agree not to use the Service:</p>
|
||||
<ul className='mb-4 list-disc space-y-2 pl-6'>
|
||||
<li>
|
||||
@@ -115,7 +193,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>5. Termination</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>10. Termination</h2>
|
||||
<p className='mb-4'>
|
||||
We may terminate or suspend your account immediately, without prior notice or liability,
|
||||
for any reason whatsoever, including without limitation if you breach the Terms.
|
||||
@@ -127,7 +205,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>6. Limitation of Liability</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>11. Limitation of Liability</h2>
|
||||
<p className='mb-4'>
|
||||
In no event shall Sim, Inc, nor its directors, employees, partners, agents, suppliers, or
|
||||
affiliates, be liable for any indirect, incidental, special, consequential or punitive
|
||||
@@ -147,7 +225,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>7. Disclaimer</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>12. Disclaimer</h2>
|
||||
<p className='mb-4'>
|
||||
Your use of the Service is at your sole risk. The Service is provided on an "AS IS" and
|
||||
"AS AVAILABLE" basis. The Service is provided without warranties of any kind, whether
|
||||
@@ -167,7 +245,17 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>8. Governing Law</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>13. Indemnification</h2>
|
||||
<p>
|
||||
You agree to indemnify, defend, and hold harmless Sim, Inc and its officers, directors,
|
||||
employees, and agents from any claims, damages, losses, liabilities, and expenses
|
||||
(including reasonable attorneys' fees) arising from your use of the Service, your
|
||||
violation of these Terms, or your violation of any rights of another party.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>14. Governing Law</h2>
|
||||
<p>
|
||||
These Terms shall be governed and construed in accordance with the laws of the United
|
||||
States, without regard to its conflict of law provisions.
|
||||
@@ -180,7 +268,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>9. Arbitration Agreement</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>15. Arbitration Agreement</h2>
|
||||
<p className='mb-4'>
|
||||
Please read the following arbitration agreement carefully. It requires you to arbitrate
|
||||
disputes with Sim, Inc, its parent companies, subsidiaries, affiliates, successors and
|
||||
@@ -221,7 +309,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>10. Changes to Terms</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>16. Changes to Terms</h2>
|
||||
<p>
|
||||
We reserve the right, at our sole discretion, to modify or replace these Terms at any
|
||||
time. If a revision is material, we will try to provide at least 30 days' notice prior to
|
||||
@@ -236,7 +324,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>11. Copyright Policy</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>17. Copyright Policy</h2>
|
||||
<p className='mb-4'>
|
||||
We respect the intellectual property of others and ask that users of our Service do the
|
||||
same. If you believe that one of our users is, through the use of our Service, unlawfully
|
||||
@@ -270,7 +358,7 @@ export default function TermsOfService() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>12. Contact Us</h2>
|
||||
<h2 className='mb-4 font-semibold text-2xl'>18. Contact Us</h2>
|
||||
<p>
|
||||
If you have any questions about these Terms, please contact us at:{' '}
|
||||
<Link
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* API Test Setup
|
||||
*/
|
||||
import { afterEach, beforeEach, vi } from 'vitest'
|
||||
|
||||
vi.mock('next/headers', () => ({
|
||||
cookies: () => ({
|
||||
get: vi.fn().mockReturnValue({ value: 'test-session-token' }),
|
||||
}),
|
||||
headers: () => ({
|
||||
get: vi.fn().mockReturnValue('test-value'),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/auth/session', () => ({
|
||||
getSession: vi.fn().mockResolvedValue({
|
||||
user: {
|
||||
id: 'user-id',
|
||||
email: 'test@example.com',
|
||||
},
|
||||
sessionToken: 'test-session-token',
|
||||
}),
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user