mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-26 15:28:03 -05:00
Compare commits
4 Commits
fix/multi-
...
fix/hackat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b0b0d03c9 | ||
|
|
2f297686f6 | ||
|
|
a6adbafe16 | ||
|
|
19cde17eb8 |
@@ -44,7 +44,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1G
|
memory: 4G
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||||
|
|||||||
@@ -10,20 +10,12 @@ Stellen Sie Sim auf Ihrer eigenen Infrastruktur mit Docker oder Kubernetes berei
|
|||||||
|
|
||||||
## Anforderungen
|
## Anforderungen
|
||||||
|
|
||||||
| Ressource | Klein | Standard | Produktion |
|
| Ressource | Minimum | Empfohlen |
|
||||||
|----------|-------|----------|------------|
|
|----------|---------|-------------|
|
||||||
| CPU | 2 Kerne | 4 Kerne | 8+ Kerne |
|
| CPU | 2 Kerne | 4+ Kerne |
|
||||||
| RAM | 12 GB | 16 GB | 32+ GB |
|
| RAM | 12 GB | 16+ GB |
|
||||||
| Speicher | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
|
| Speicher | 20 GB SSD | 50+ GB SSD |
|
||||||
| Docker | 20.10+ | 20.10+ | Neueste Version |
|
| Docker | 20.10+ | Neueste Version |
|
||||||
|
|
||||||
**Klein**: Entwicklung, Tests, Einzelnutzer (1-5 Nutzer)
|
|
||||||
**Standard**: Teams (5-50 Nutzer), moderate Arbeitslasten
|
|
||||||
**Produktion**: Große Teams (50+ Nutzer), Hochverfügbarkeit, intensive Workflow-Ausführung
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Die Ressourcenanforderungen werden durch Workflow-Ausführung (isolated-vm Sandboxing), Dateiverarbeitung (In-Memory-Dokumentenparsing) und Vektoroperationen (pgvector) bestimmt. Arbeitsspeicher ist typischerweise der limitierende Faktor, nicht CPU. Produktionsdaten zeigen, dass die Hauptanwendung durchschnittlich 4-8 GB und bei hoher Last bis zu 12 GB benötigt.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Schnellstart
|
## Schnellstart
|
||||||
|
|
||||||
|
|||||||
@@ -16,20 +16,12 @@ Deploy Sim on your own infrastructure with Docker or Kubernetes.
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
| Resource | Small | Standard | Production |
|
| Resource | Minimum | Recommended |
|
||||||
|----------|-------|----------|------------|
|
|----------|---------|-------------|
|
||||||
| CPU | 2 cores | 4 cores | 8+ cores |
|
| CPU | 2 cores | 4+ cores |
|
||||||
| RAM | 12 GB | 16 GB | 32+ GB |
|
| RAM | 12 GB | 16+ GB |
|
||||||
| Storage | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
|
| Storage | 20 GB SSD | 50+ GB SSD |
|
||||||
| Docker | 20.10+ | 20.10+ | Latest |
|
| Docker | 20.10+ | Latest |
|
||||||
|
|
||||||
**Small**: Development, testing, single user (1-5 users)
|
|
||||||
**Standard**: Teams (5-50 users), moderate workloads
|
|
||||||
**Production**: Large teams (50+ users), high availability, heavy workflow execution
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Resource requirements are driven by workflow execution (isolated-vm sandboxing), file processing (in-memory document parsing), and vector operations (pgvector). Memory is typically the constraining factor rather than CPU. Production telemetry shows the main app uses 4-8 GB average with peaks up to 12 GB under heavy load.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,12 @@ Despliega Sim en tu propia infraestructura con Docker o Kubernetes.
|
|||||||
|
|
||||||
## Requisitos
|
## Requisitos
|
||||||
|
|
||||||
| Recurso | Pequeño | Estándar | Producción |
|
| Recurso | Mínimo | Recomendado |
|
||||||
|----------|---------|----------|------------|
|
|----------|---------|-------------|
|
||||||
| CPU | 2 núcleos | 4 núcleos | 8+ núcleos |
|
| CPU | 2 núcleos | 4+ núcleos |
|
||||||
| RAM | 12 GB | 16 GB | 32+ GB |
|
| RAM | 12 GB | 16+ GB |
|
||||||
| Almacenamiento | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
|
| Almacenamiento | 20 GB SSD | 50+ GB SSD |
|
||||||
| Docker | 20.10+ | 20.10+ | Última versión |
|
| Docker | 20.10+ | Última versión |
|
||||||
|
|
||||||
**Pequeño**: Desarrollo, pruebas, usuario único (1-5 usuarios)
|
|
||||||
**Estándar**: Equipos (5-50 usuarios), cargas de trabajo moderadas
|
|
||||||
**Producción**: Equipos grandes (50+ usuarios), alta disponibilidad, ejecución intensiva de workflows
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Los requisitos de recursos están determinados por la ejecución de workflows (sandboxing isolated-vm), procesamiento de archivos (análisis de documentos en memoria) y operaciones vectoriales (pgvector). La memoria suele ser el factor limitante, no la CPU. La telemetría de producción muestra que la aplicación principal usa 4-8 GB en promedio con picos de hasta 12 GB bajo carga pesada.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Inicio rápido
|
## Inicio rápido
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,12 @@ Déployez Sim sur votre propre infrastructure avec Docker ou Kubernetes.
|
|||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
| Ressource | Petit | Standard | Production |
|
| Ressource | Minimum | Recommandé |
|
||||||
|----------|-------|----------|------------|
|
|----------|---------|-------------|
|
||||||
| CPU | 2 cœurs | 4 cœurs | 8+ cœurs |
|
| CPU | 2 cœurs | 4+ cœurs |
|
||||||
| RAM | 12 Go | 16 Go | 32+ Go |
|
| RAM | 12 Go | 16+ Go |
|
||||||
| Stockage | 20 Go SSD | 50 Go SSD | 100+ Go SSD |
|
| Stockage | 20 Go SSD | 50+ Go SSD |
|
||||||
| Docker | 20.10+ | 20.10+ | Dernière version |
|
| Docker | 20.10+ | Dernière version |
|
||||||
|
|
||||||
**Petit** : Développement, tests, utilisateur unique (1-5 utilisateurs)
|
|
||||||
**Standard** : Équipes (5-50 utilisateurs), charges de travail modérées
|
|
||||||
**Production** : Grandes équipes (50+ utilisateurs), haute disponibilité, exécution intensive de workflows
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
Les besoins en ressources sont déterminés par l'exécution des workflows (sandboxing isolated-vm), le traitement des fichiers (analyse de documents en mémoire) et les opérations vectorielles (pgvector). La mémoire est généralement le facteur limitant, pas le CPU. La télémétrie de production montre que l'application principale utilise 4-8 Go en moyenne avec des pics jusqu'à 12 Go sous forte charge.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## Démarrage rapide
|
## Démarrage rapide
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,12 @@ DockerまたはKubernetesを使用して、自社のインフラストラクチ
|
|||||||
|
|
||||||
## 要件
|
## 要件
|
||||||
|
|
||||||
| リソース | スモール | スタンダード | プロダクション |
|
| リソース | 最小 | 推奨 |
|
||||||
|----------|---------|-------------|----------------|
|
|----------|---------|-------------|
|
||||||
| CPU | 2コア | 4コア | 8+コア |
|
| CPU | 2コア | 4+コア |
|
||||||
| RAM | 12 GB | 16 GB | 32+ GB |
|
| RAM | 12 GB | 16+ GB |
|
||||||
| ストレージ | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
|
| ストレージ | 20 GB SSD | 50+ GB SSD |
|
||||||
| Docker | 20.10+ | 20.10+ | 最新版 |
|
| Docker | 20.10+ | 最新版 |
|
||||||
|
|
||||||
**スモール**: 開発、テスト、シングルユーザー(1-5ユーザー)
|
|
||||||
**スタンダード**: チーム(5-50ユーザー)、中程度のワークロード
|
|
||||||
**プロダクション**: 大規模チーム(50+ユーザー)、高可用性、高負荷ワークフロー実行
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
リソース要件は、ワークフロー実行(isolated-vmサンドボックス)、ファイル処理(メモリ内ドキュメント解析)、ベクトル演算(pgvector)によって決まります。CPUよりもメモリが制約要因となることが多いです。本番環境のテレメトリによると、メインアプリは平均4-8 GB、高負荷時は最大12 GBを使用します。
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## クイックスタート
|
## クイックスタート
|
||||||
|
|
||||||
|
|||||||
@@ -10,20 +10,12 @@ import { Callout } from 'fumadocs-ui/components/callout'
|
|||||||
|
|
||||||
## 要求
|
## 要求
|
||||||
|
|
||||||
| 资源 | 小型 | 标准 | 生产环境 |
|
| 资源 | 最低要求 | 推荐配置 |
|
||||||
|----------|------|------|----------|
|
|----------|---------|-------------|
|
||||||
| CPU | 2 核 | 4 核 | 8+ 核 |
|
| CPU | 2 核 | 4 核及以上 |
|
||||||
| 内存 | 12 GB | 16 GB | 32+ GB |
|
| 内存 | 12 GB | 16 GB 及以上 |
|
||||||
| 存储 | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
|
| 存储 | 20 GB SSD | 50 GB 及以上 SSD |
|
||||||
| Docker | 20.10+ | 20.10+ | 最新版本 |
|
| Docker | 20.10+ | 最新版本 |
|
||||||
|
|
||||||
**小型**: 开发、测试、单用户(1-5 用户)
|
|
||||||
**标准**: 团队(5-50 用户)、中等工作负载
|
|
||||||
**生产环境**: 大型团队(50+ 用户)、高可用性、密集工作流执行
|
|
||||||
|
|
||||||
<Callout type="info">
|
|
||||||
资源需求由工作流执行(isolated-vm 沙箱)、文件处理(内存中文档解析)和向量运算(pgvector)决定。内存通常是限制因素,而不是 CPU。生产遥测数据显示,主应用平均使用 4-8 GB,高负载时峰值可达 12 GB。
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
|
|||||||
@@ -408,7 +408,6 @@ describe('Knowledge Search Utils', () => {
|
|||||||
input: ['test query'],
|
input: ['test query'],
|
||||||
model: 'text-embedding-3-small',
|
model: 'text-embedding-3-small',
|
||||||
encoding_format: 'float',
|
encoding_format: 'float',
|
||||||
dimensions: 1536,
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -581,18 +581,6 @@ export const GmailV2Block: BlockConfig<GmailToolResponse> = {
|
|||||||
results: { type: 'json', description: 'Search/read summary results' },
|
results: { type: 'json', description: 'Search/read summary results' },
|
||||||
attachments: { type: 'json', description: 'Downloaded attachments (if enabled)' },
|
attachments: { type: 'json', description: 'Downloaded attachments (if enabled)' },
|
||||||
|
|
||||||
// Draft-specific outputs
|
|
||||||
draftId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Draft ID',
|
|
||||||
condition: { field: 'operation', value: 'draft_gmail' },
|
|
||||||
},
|
|
||||||
messageId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Gmail message ID for the draft',
|
|
||||||
condition: { field: 'operation', value: 'draft_gmail' },
|
|
||||||
},
|
|
||||||
|
|
||||||
// Trigger outputs (unchanged)
|
// Trigger outputs (unchanged)
|
||||||
email_id: { type: 'string', description: 'Gmail message ID' },
|
email_id: { type: 'string', description: 'Gmail message ID' },
|
||||||
thread_id: { type: 'string', description: 'Gmail thread ID' },
|
thread_id: { type: 'string', description: 'Gmail thread ID' },
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
|
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
|
||||||
import { normalizeName } from '@/executor/constants'
|
import { normalizeName } from '@/executor/constants'
|
||||||
import type { ExecutionContext } from '@/executor/types'
|
import type { ExecutionContext } from '@/executor/types'
|
||||||
import type { OutputSchema } from '@/executor/utils/block-reference'
|
import type { OutputSchema } from '@/executor/utils/block-reference'
|
||||||
import type { SerializedBlock } from '@/serializer/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
import { getTool } from '@/tools/utils'
|
|
||||||
|
|
||||||
export interface BlockDataCollection {
|
export interface BlockDataCollection {
|
||||||
blockData: Record<string, unknown>
|
blockData: Record<string, unknown>
|
||||||
@@ -11,32 +9,6 @@ export interface BlockDataCollection {
|
|||||||
blockOutputSchemas: Record<string, OutputSchema>
|
blockOutputSchemas: Record<string, OutputSchema>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBlockSchema(
|
|
||||||
block: SerializedBlock,
|
|
||||||
toolConfig?: ToolConfig
|
|
||||||
): OutputSchema | undefined {
|
|
||||||
const isTrigger =
|
|
||||||
block.metadata?.category === 'triggers' ||
|
|
||||||
(block.config?.params as Record<string, unknown> | undefined)?.triggerMode === true
|
|
||||||
|
|
||||||
// Triggers use saved outputs (defines the trigger payload schema)
|
|
||||||
if (isTrigger && block.outputs && Object.keys(block.outputs).length > 0) {
|
|
||||||
return block.outputs as OutputSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a tool is selected, tool outputs are the source of truth
|
|
||||||
if (toolConfig?.outputs && Object.keys(toolConfig.outputs).length > 0) {
|
|
||||||
return toolConfig.outputs as OutputSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to saved outputs for blocks without tools
|
|
||||||
if (block.outputs && Object.keys(block.outputs).length > 0) {
|
|
||||||
return block.outputs as OutputSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function collectBlockData(ctx: ExecutionContext): BlockDataCollection {
|
export function collectBlockData(ctx: ExecutionContext): BlockDataCollection {
|
||||||
const blockData: Record<string, unknown> = {}
|
const blockData: Record<string, unknown> = {}
|
||||||
const blockNameMapping: Record<string, string> = {}
|
const blockNameMapping: Record<string, string> = {}
|
||||||
@@ -46,21 +18,24 @@ export function collectBlockData(ctx: ExecutionContext): BlockDataCollection {
|
|||||||
if (state.output !== undefined) {
|
if (state.output !== undefined) {
|
||||||
blockData[id] = state.output
|
blockData[id] = state.output
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const workflowBlocks = ctx.workflow?.blocks ?? []
|
const workflowBlock = ctx.workflow?.blocks?.find((b) => b.id === id)
|
||||||
for (const block of workflowBlocks) {
|
if (!workflowBlock) continue
|
||||||
const id = block.id
|
|
||||||
|
|
||||||
if (block.metadata?.name) {
|
if (workflowBlock.metadata?.name) {
|
||||||
blockNameMapping[normalizeName(block.metadata.name)] = id
|
blockNameMapping[normalizeName(workflowBlock.metadata.name)] = id
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolId = block.config?.tool
|
const blockType = workflowBlock.metadata?.id
|
||||||
const toolConfig = toolId ? getTool(toolId) : undefined
|
if (blockType) {
|
||||||
const schema = getBlockSchema(block, toolConfig)
|
const params = workflowBlock.config?.params as Record<string, unknown> | undefined
|
||||||
if (schema && Object.keys(schema).length > 0) {
|
const subBlocks = params
|
||||||
blockOutputSchemas[id] = schema
|
? Object.fromEntries(Object.entries(params).map(([k, v]) => [k, { value: v }]))
|
||||||
|
: undefined
|
||||||
|
const schema = getBlockOutputs(blockType, subBlocks)
|
||||||
|
if (schema && Object.keys(schema).length > 0) {
|
||||||
|
blockOutputSchemas[id] = schema
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -378,30 +378,8 @@ function buildManualTriggerOutput(
|
|||||||
return mergeFilesIntoOutput(output, workflowInput)
|
return mergeFilesIntoOutput(output, workflowInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildIntegrationTriggerOutput(
|
function buildIntegrationTriggerOutput(workflowInput: unknown): NormalizedBlockOutput {
|
||||||
workflowInput: unknown,
|
return isPlainObject(workflowInput) ? (workflowInput as NormalizedBlockOutput) : {}
|
||||||
structuredInput: Record<string, unknown>,
|
|
||||||
hasStructured: boolean
|
|
||||||
): NormalizedBlockOutput {
|
|
||||||
const output: NormalizedBlockOutput = {}
|
|
||||||
|
|
||||||
if (hasStructured) {
|
|
||||||
for (const [key, value] of Object.entries(structuredInput)) {
|
|
||||||
output[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPlainObject(workflowInput)) {
|
|
||||||
for (const [key, value] of Object.entries(workflowInput)) {
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
output[key] = value
|
|
||||||
} else if (!Object.hasOwn(output, key)) {
|
|
||||||
output[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergeFilesIntoOutput(output, workflowInput)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractSubBlocks(block: SerializedBlock): Record<string, unknown> | undefined {
|
function extractSubBlocks(block: SerializedBlock): Record<string, unknown> | undefined {
|
||||||
@@ -450,7 +428,7 @@ export function buildStartBlockOutput(options: StartBlockOutputOptions): Normali
|
|||||||
return buildManualTriggerOutput(finalInput, workflowInput)
|
return buildManualTriggerOutput(finalInput, workflowInput)
|
||||||
|
|
||||||
case StartBlockPath.EXTERNAL_TRIGGER:
|
case StartBlockPath.EXTERNAL_TRIGGER:
|
||||||
return buildIntegrationTriggerOutput(workflowInput, structuredInput, hasStructured)
|
return buildIntegrationTriggerOutput(workflowInput)
|
||||||
|
|
||||||
case StartBlockPath.LEGACY_STARTER:
|
case StartBlockPath.LEGACY_STARTER:
|
||||||
return buildLegacyStarterOutput(
|
return buildLegacyStarterOutput(
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
|
||||||
import {
|
import {
|
||||||
isReference,
|
isReference,
|
||||||
normalizeName,
|
normalizeName,
|
||||||
parseReferencePath,
|
parseReferencePath,
|
||||||
SPECIAL_REFERENCE_PREFIXES,
|
SPECIAL_REFERENCE_PREFIXES,
|
||||||
} from '@/executor/constants'
|
} from '@/executor/constants'
|
||||||
import { getBlockSchema } from '@/executor/utils/block-data'
|
|
||||||
import {
|
import {
|
||||||
InvalidFieldError,
|
InvalidFieldError,
|
||||||
type OutputSchema,
|
type OutputSchema,
|
||||||
@@ -67,9 +67,15 @@ export class BlockResolver implements Resolver {
|
|||||||
blockData[blockId] = output
|
blockData[blockId] = output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockType = block.metadata?.id
|
||||||
|
const params = block.config?.params as Record<string, unknown> | undefined
|
||||||
|
const subBlocks = params
|
||||||
|
? Object.fromEntries(Object.entries(params).map(([k, v]) => [k, { value: v }]))
|
||||||
|
: undefined
|
||||||
const toolId = block.config?.tool
|
const toolId = block.config?.tool
|
||||||
const toolConfig = toolId ? getTool(toolId) : undefined
|
const toolConfig = toolId ? getTool(toolId) : undefined
|
||||||
const outputSchema = getBlockSchema(block, toolConfig)
|
const outputSchema =
|
||||||
|
toolConfig?.outputs ?? (blockType ? getBlockOutputs(blockType, subBlocks) : block.outputs)
|
||||||
|
|
||||||
if (outputSchema && Object.keys(outputSchema).length > 0) {
|
if (outputSchema && Object.keys(outputSchema).length > 0) {
|
||||||
blockOutputSchemas[blockId] = outputSchema
|
blockOutputSchemas[blockId] = outputSchema
|
||||||
|
|||||||
@@ -8,17 +8,6 @@ const logger = createLogger('EmbeddingUtils')
|
|||||||
|
|
||||||
const MAX_TOKENS_PER_REQUEST = 8000
|
const MAX_TOKENS_PER_REQUEST = 8000
|
||||||
const MAX_CONCURRENT_BATCHES = env.KB_CONFIG_CONCURRENCY_LIMIT || 50
|
const MAX_CONCURRENT_BATCHES = env.KB_CONFIG_CONCURRENCY_LIMIT || 50
|
||||||
const EMBEDDING_DIMENSIONS = 1536
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the model supports custom dimensions.
|
|
||||||
* text-embedding-3-* models support the dimensions parameter.
|
|
||||||
* Checks for 'embedding-3' to handle Azure deployments with custom naming conventions.
|
|
||||||
*/
|
|
||||||
function supportsCustomDimensions(modelName: string): boolean {
|
|
||||||
const name = modelName.toLowerCase()
|
|
||||||
return name.includes('embedding-3') && !name.includes('ada')
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EmbeddingAPIError extends Error {
|
export class EmbeddingAPIError extends Error {
|
||||||
public status: number
|
public status: number
|
||||||
@@ -104,19 +93,15 @@ async function getEmbeddingConfig(
|
|||||||
async function callEmbeddingAPI(inputs: string[], config: EmbeddingConfig): Promise<number[][]> {
|
async function callEmbeddingAPI(inputs: string[], config: EmbeddingConfig): Promise<number[][]> {
|
||||||
return retryWithExponentialBackoff(
|
return retryWithExponentialBackoff(
|
||||||
async () => {
|
async () => {
|
||||||
const useDimensions = supportsCustomDimensions(config.modelName)
|
|
||||||
|
|
||||||
const requestBody = config.useAzure
|
const requestBody = config.useAzure
|
||||||
? {
|
? {
|
||||||
input: inputs,
|
input: inputs,
|
||||||
encoding_format: 'float',
|
encoding_format: 'float',
|
||||||
...(useDimensions && { dimensions: EMBEDDING_DIMENSIONS }),
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
input: inputs,
|
input: inputs,
|
||||||
model: config.modelName,
|
model: config.modelName,
|
||||||
encoding_format: 'float',
|
encoding_format: 'float',
|
||||||
...(useDimensions && { dimensions: EMBEDDING_DIMENSIONS }),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(config.apiUrl, {
|
const response = await fetch(config.apiUrl, {
|
||||||
|
|||||||
@@ -18,52 +18,6 @@ const logger = createLogger('BlobClient')
|
|||||||
|
|
||||||
let _blobServiceClient: BlobServiceClientInstance | null = null
|
let _blobServiceClient: BlobServiceClientInstance | null = null
|
||||||
|
|
||||||
interface ParsedCredentials {
|
|
||||||
accountName: string
|
|
||||||
accountKey: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract account name and key from an Azure connection string.
|
|
||||||
* Connection strings have the format: DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=...
|
|
||||||
*/
|
|
||||||
function parseConnectionString(connectionString: string): ParsedCredentials {
|
|
||||||
const accountNameMatch = connectionString.match(/AccountName=([^;]+)/)
|
|
||||||
if (!accountNameMatch) {
|
|
||||||
throw new Error('Cannot extract account name from connection string')
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountKeyMatch = connectionString.match(/AccountKey=([^;]+)/)
|
|
||||||
if (!accountKeyMatch) {
|
|
||||||
throw new Error('Cannot extract account key from connection string')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
accountName: accountNameMatch[1],
|
|
||||||
accountKey: accountKeyMatch[1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get account credentials from BLOB_CONFIG, extracting from connection string if necessary.
|
|
||||||
*/
|
|
||||||
function getAccountCredentials(): ParsedCredentials {
|
|
||||||
if (BLOB_CONFIG.connectionString) {
|
|
||||||
return parseConnectionString(BLOB_CONFIG.connectionString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BLOB_CONFIG.accountName && BLOB_CONFIG.accountKey) {
|
|
||||||
return {
|
|
||||||
accountName: BLOB_CONFIG.accountName,
|
|
||||||
accountKey: BLOB_CONFIG.accountKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
'Azure Blob Storage credentials are missing – set AZURE_CONNECTION_STRING or both AZURE_ACCOUNT_NAME and AZURE_ACCOUNT_KEY'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getBlobServiceClient(): Promise<BlobServiceClientInstance> {
|
export async function getBlobServiceClient(): Promise<BlobServiceClientInstance> {
|
||||||
if (_blobServiceClient) return _blobServiceClient
|
if (_blobServiceClient) return _blobServiceClient
|
||||||
|
|
||||||
@@ -173,8 +127,6 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
|
|||||||
const containerClient = blobServiceClient.getContainerClient(BLOB_CONFIG.containerName)
|
const containerClient = blobServiceClient.getContainerClient(BLOB_CONFIG.containerName)
|
||||||
const blockBlobClient = containerClient.getBlockBlobClient(key)
|
const blockBlobClient = containerClient.getBlockBlobClient(key)
|
||||||
|
|
||||||
const { accountName, accountKey } = getAccountCredentials()
|
|
||||||
|
|
||||||
const sasOptions = {
|
const sasOptions = {
|
||||||
containerName: BLOB_CONFIG.containerName,
|
containerName: BLOB_CONFIG.containerName,
|
||||||
blobName: key,
|
blobName: key,
|
||||||
@@ -185,7 +137,13 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
|
|||||||
|
|
||||||
const sasToken = generateBlobSASQueryParameters(
|
const sasToken = generateBlobSASQueryParameters(
|
||||||
sasOptions,
|
sasOptions,
|
||||||
new StorageSharedKeyCredential(accountName, accountKey)
|
new StorageSharedKeyCredential(
|
||||||
|
BLOB_CONFIG.accountName,
|
||||||
|
BLOB_CONFIG.accountKey ??
|
||||||
|
(() => {
|
||||||
|
throw new Error('AZURE_ACCOUNT_KEY is required when using account name authentication')
|
||||||
|
})()
|
||||||
|
)
|
||||||
).toString()
|
).toString()
|
||||||
|
|
||||||
return `${blockBlobClient.url}?${sasToken}`
|
return `${blockBlobClient.url}?${sasToken}`
|
||||||
@@ -210,14 +168,9 @@ export async function getPresignedUrlWithConfig(
|
|||||||
StorageSharedKeyCredential,
|
StorageSharedKeyCredential,
|
||||||
} = await import('@azure/storage-blob')
|
} = await import('@azure/storage-blob')
|
||||||
let tempBlobServiceClient: BlobServiceClientInstance
|
let tempBlobServiceClient: BlobServiceClientInstance
|
||||||
let accountName: string
|
|
||||||
let accountKey: string
|
|
||||||
|
|
||||||
if (customConfig.connectionString) {
|
if (customConfig.connectionString) {
|
||||||
tempBlobServiceClient = BlobServiceClient.fromConnectionString(customConfig.connectionString)
|
tempBlobServiceClient = BlobServiceClient.fromConnectionString(customConfig.connectionString)
|
||||||
const credentials = parseConnectionString(customConfig.connectionString)
|
|
||||||
accountName = credentials.accountName
|
|
||||||
accountKey = credentials.accountKey
|
|
||||||
} else if (customConfig.accountName && customConfig.accountKey) {
|
} else if (customConfig.accountName && customConfig.accountKey) {
|
||||||
const sharedKeyCredential = new StorageSharedKeyCredential(
|
const sharedKeyCredential = new StorageSharedKeyCredential(
|
||||||
customConfig.accountName,
|
customConfig.accountName,
|
||||||
@@ -227,8 +180,6 @@ export async function getPresignedUrlWithConfig(
|
|||||||
`https://${customConfig.accountName}.blob.core.windows.net`,
|
`https://${customConfig.accountName}.blob.core.windows.net`,
|
||||||
sharedKeyCredential
|
sharedKeyCredential
|
||||||
)
|
)
|
||||||
accountName = customConfig.accountName
|
|
||||||
accountKey = customConfig.accountKey
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Custom blob config must include either connectionString or accountName + accountKey'
|
'Custom blob config must include either connectionString or accountName + accountKey'
|
||||||
@@ -248,7 +199,13 @@ export async function getPresignedUrlWithConfig(
|
|||||||
|
|
||||||
const sasToken = generateBlobSASQueryParameters(
|
const sasToken = generateBlobSASQueryParameters(
|
||||||
sasOptions,
|
sasOptions,
|
||||||
new StorageSharedKeyCredential(accountName, accountKey)
|
new StorageSharedKeyCredential(
|
||||||
|
customConfig.accountName,
|
||||||
|
customConfig.accountKey ??
|
||||||
|
(() => {
|
||||||
|
throw new Error('Account key is required when using account name authentication')
|
||||||
|
})()
|
||||||
|
)
|
||||||
).toString()
|
).toString()
|
||||||
|
|
||||||
return `${blockBlobClient.url}?${sasToken}`
|
return `${blockBlobClient.url}?${sasToken}`
|
||||||
@@ -446,9 +403,13 @@ export async function getMultipartPartUrls(
|
|||||||
if (customConfig) {
|
if (customConfig) {
|
||||||
if (customConfig.connectionString) {
|
if (customConfig.connectionString) {
|
||||||
blobServiceClient = BlobServiceClient.fromConnectionString(customConfig.connectionString)
|
blobServiceClient = BlobServiceClient.fromConnectionString(customConfig.connectionString)
|
||||||
const credentials = parseConnectionString(customConfig.connectionString)
|
const match = customConfig.connectionString.match(/AccountName=([^;]+)/)
|
||||||
accountName = credentials.accountName
|
if (!match) throw new Error('Cannot extract account name from connection string')
|
||||||
accountKey = credentials.accountKey
|
accountName = match[1]
|
||||||
|
|
||||||
|
const keyMatch = customConfig.connectionString.match(/AccountKey=([^;]+)/)
|
||||||
|
if (!keyMatch) throw new Error('Cannot extract account key from connection string')
|
||||||
|
accountKey = keyMatch[1]
|
||||||
} else if (customConfig.accountName && customConfig.accountKey) {
|
} else if (customConfig.accountName && customConfig.accountKey) {
|
||||||
const credential = new StorageSharedKeyCredential(
|
const credential = new StorageSharedKeyCredential(
|
||||||
customConfig.accountName,
|
customConfig.accountName,
|
||||||
@@ -467,9 +428,12 @@ export async function getMultipartPartUrls(
|
|||||||
} else {
|
} else {
|
||||||
blobServiceClient = await getBlobServiceClient()
|
blobServiceClient = await getBlobServiceClient()
|
||||||
containerName = BLOB_CONFIG.containerName
|
containerName = BLOB_CONFIG.containerName
|
||||||
const credentials = getAccountCredentials()
|
accountName = BLOB_CONFIG.accountName
|
||||||
accountName = credentials.accountName
|
accountKey =
|
||||||
accountKey = credentials.accountKey
|
BLOB_CONFIG.accountKey ||
|
||||||
|
(() => {
|
||||||
|
throw new Error('AZURE_ACCOUNT_KEY is required')
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerClient = blobServiceClient.getContainerClient(containerName)
|
const containerClient = blobServiceClient.getContainerClient(containerName)
|
||||||
@@ -537,10 +501,12 @@ export async function completeMultipartUpload(
|
|||||||
const containerClient = blobServiceClient.getContainerClient(containerName)
|
const containerClient = blobServiceClient.getContainerClient(containerName)
|
||||||
const blockBlobClient = containerClient.getBlockBlobClient(key)
|
const blockBlobClient = containerClient.getBlockBlobClient(key)
|
||||||
|
|
||||||
|
// Sort parts by part number and extract block IDs
|
||||||
const sortedBlockIds = parts
|
const sortedBlockIds = parts
|
||||||
.sort((a, b) => a.partNumber - b.partNumber)
|
.sort((a, b) => a.partNumber - b.partNumber)
|
||||||
.map((part) => part.blockId)
|
.map((part) => part.blockId)
|
||||||
|
|
||||||
|
// Commit the block list to create the final blob
|
||||||
await blockBlobClient.commitBlockList(sortedBlockIds, {
|
await blockBlobClient.commitBlockList(sortedBlockIds, {
|
||||||
metadata: {
|
metadata: {
|
||||||
multipartUpload: 'completed',
|
multipartUpload: 'completed',
|
||||||
@@ -591,8 +557,10 @@ export async function abortMultipartUpload(key: string, customConfig?: BlobConfi
|
|||||||
const blockBlobClient = containerClient.getBlockBlobClient(key)
|
const blockBlobClient = containerClient.getBlockBlobClient(key)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Delete the blob if it exists (this also cleans up any uncommitted blocks)
|
||||||
await blockBlobClient.deleteIfExists()
|
await blockBlobClient.deleteIfExists()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Ignore errors since we're just cleaning up
|
||||||
logger.warn('Error cleaning up multipart upload:', error)
|
logger.warn('Error cleaning up multipart upload:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -618,6 +618,13 @@ export function getToolOutputs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates output paths for a tool-based block.
|
||||||
|
*
|
||||||
|
* @param blockConfig - The block configuration containing tools config
|
||||||
|
* @param subBlocks - SubBlock values for tool selection and condition evaluation
|
||||||
|
* @returns Array of output paths for the tool, or empty array on error
|
||||||
|
*/
|
||||||
export function getToolOutputPaths(
|
export function getToolOutputPaths(
|
||||||
blockConfig: BlockConfig,
|
blockConfig: BlockConfig,
|
||||||
subBlocks?: Record<string, SubBlockWithValue>
|
subBlocks?: Record<string, SubBlockWithValue>
|
||||||
@@ -627,22 +634,12 @@ export function getToolOutputPaths(
|
|||||||
if (!outputs || Object.keys(outputs).length === 0) return []
|
if (!outputs || Object.keys(outputs).length === 0) return []
|
||||||
|
|
||||||
if (subBlocks && blockConfig.outputs) {
|
if (subBlocks && blockConfig.outputs) {
|
||||||
|
const filteredBlockOutputs = filterOutputsByCondition(blockConfig.outputs, subBlocks)
|
||||||
|
const allowedKeys = new Set(Object.keys(filteredBlockOutputs))
|
||||||
|
|
||||||
const filteredOutputs: Record<string, any> = {}
|
const filteredOutputs: Record<string, any> = {}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(outputs)) {
|
for (const [key, value] of Object.entries(outputs)) {
|
||||||
const blockOutput = blockConfig.outputs[key]
|
if (allowedKeys.has(key)) {
|
||||||
|
|
||||||
if (!blockOutput || typeof blockOutput !== 'object') {
|
|
||||||
filteredOutputs[key] = value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const condition = 'condition' in blockOutput ? blockOutput.condition : undefined
|
|
||||||
if (condition) {
|
|
||||||
if (evaluateOutputCondition(condition, subBlocks)) {
|
|
||||||
filteredOutputs[key] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filteredOutputs[key] = value
|
filteredOutputs[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1G
|
memory: 8G
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002/health']
|
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002/health']
|
||||||
interval: 90s
|
interval: 90s
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1G
|
memory: 8G
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002/health']
|
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002/health']
|
||||||
interval: 90s
|
interval: 90s
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1G
|
memory: 4G
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
|
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
|
||||||
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ global:
|
|||||||
app:
|
app:
|
||||||
enabled: true
|
enabled: true
|
||||||
replicaCount: 2
|
replicaCount: 2
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: "8Gi"
|
memory: "6Gi"
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
requests:
|
requests:
|
||||||
memory: "6Gi"
|
memory: "4Gi"
|
||||||
cpu: "1000m"
|
cpu: "1000m"
|
||||||
|
|
||||||
# Production URLs (REQUIRED - update with your actual domain names)
|
# Production URLs (REQUIRED - update with your actual domain names)
|
||||||
@@ -49,14 +49,14 @@ app:
|
|||||||
realtime:
|
realtime:
|
||||||
enabled: true
|
enabled: true
|
||||||
replicaCount: 2
|
replicaCount: 2
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: "1Gi"
|
memory: "4Gi"
|
||||||
cpu: "500m"
|
cpu: "1000m"
|
||||||
requests:
|
requests:
|
||||||
memory: "512Mi"
|
memory: "2Gi"
|
||||||
cpu: "250m"
|
cpu: "500m"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_APP_URL: "https://sim.acme.ai"
|
NEXT_PUBLIC_APP_URL: "https://sim.acme.ai"
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ app:
|
|||||||
# Resource limits and requests
|
# Resource limits and requests
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: "8Gi"
|
memory: "4Gi"
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
requests:
|
requests:
|
||||||
memory: "4Gi"
|
memory: "2Gi"
|
||||||
cpu: "1000m"
|
cpu: "1000m"
|
||||||
|
|
||||||
# Node selector for pod scheduling (leave empty to allow scheduling on any node)
|
# Node selector for pod scheduling (leave empty to allow scheduling on any node)
|
||||||
@@ -232,24 +232,24 @@ app:
|
|||||||
realtime:
|
realtime:
|
||||||
# Enable/disable the realtime service
|
# Enable/disable the realtime service
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
# Image configuration
|
# Image configuration
|
||||||
image:
|
image:
|
||||||
repository: simstudioai/realtime
|
repository: simstudioai/realtime
|
||||||
tag: latest
|
tag: latest
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
# Number of replicas
|
# Number of replicas
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
# Resource limits and requests
|
# Resource limits and requests
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
|
memory: "2Gi"
|
||||||
|
cpu: "1000m"
|
||||||
|
requests:
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
requests:
|
|
||||||
memory: "512Mi"
|
|
||||||
cpu: "250m"
|
|
||||||
|
|
||||||
# Node selector for pod scheduling (leave empty to allow scheduling on any node)
|
# Node selector for pod scheduling (leave empty to allow scheduling on any node)
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user