Skip to content

Commit be2a9ef

Browse files
authored
fix(storage): support Azure connection string for presigned URLs (#2997)
* fix(docs): update requirements to be more accurate for deploying the app * updated kb to support 1536 dimension vectors for models other than text embedding 3 small * fix(storage): support Azure connection string for presigned URLs * fix(kb): update test for embedding dimensions parameter * fix(storage): align credential source ordering for consistency
1 parent 1bf5ed4 commit be2a9ef

File tree

15 files changed

+183
-87
lines changed

15 files changed

+183
-87
lines changed

.devcontainer/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ services:
4444
deploy:
4545
resources:
4646
limits:
47-
memory: 4G
47+
memory: 1G
4848
environment:
4949
- NODE_ENV=development
5050
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio

apps/docs/content/docs/de/self-hosting/index.mdx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ Stellen Sie Sim auf Ihrer eigenen Infrastruktur mit Docker oder Kubernetes berei
1010

1111
## Anforderungen
1212

13-
| Ressource | Minimum | Empfohlen |
14-
|----------|---------|-------------|
15-
| CPU | 2 Kerne | 4+ Kerne |
16-
| RAM | 12 GB | 16+ GB |
17-
| Speicher | 20 GB SSD | 50+ GB SSD |
18-
| Docker | 20.10+ | Neueste Version |
13+
| Ressource | Klein | Standard | Produktion |
14+
|----------|-------|----------|------------|
15+
| CPU | 2 Kerne | 4 Kerne | 8+ Kerne |
16+
| RAM | 12 GB | 16 GB | 32+ GB |
17+
| Speicher | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
18+
| Docker | 20.10+ | 20.10+ | Neueste Version |
19+
20+
**Klein**: Entwicklung, Tests, Einzelnutzer (1-5 Nutzer)
21+
**Standard**: Teams (5-50 Nutzer), moderate Arbeitslasten
22+
**Produktion**: Große Teams (50+ Nutzer), Hochverfügbarkeit, intensive Workflow-Ausführung
23+
24+
<Callout type="info">
25+
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.
26+
</Callout>
1927

2028
## Schnellstart
2129

apps/docs/content/docs/en/self-hosting/index.mdx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,20 @@ Deploy Sim on your own infrastructure with Docker or Kubernetes.
1616

1717
## Requirements
1818

19-
| Resource | Minimum | Recommended |
20-
|----------|---------|-------------|
21-
| CPU | 2 cores | 4+ cores |
22-
| RAM | 12 GB | 16+ GB |
23-
| Storage | 20 GB SSD | 50+ GB SSD |
24-
| Docker | 20.10+ | Latest |
19+
| Resource | Small | Standard | Production |
20+
|----------|-------|----------|------------|
21+
| CPU | 2 cores | 4 cores | 8+ cores |
22+
| RAM | 12 GB | 16 GB | 32+ GB |
23+
| Storage | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
24+
| Docker | 20.10+ | 20.10+ | Latest |
25+
26+
**Small**: Development, testing, single user (1-5 users)
27+
**Standard**: Teams (5-50 users), moderate workloads
28+
**Production**: Large teams (50+ users), high availability, heavy workflow execution
29+
30+
<Callout type="info">
31+
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.
32+
</Callout>
2533

2634
## Quick Start
2735

apps/docs/content/docs/es/self-hosting/index.mdx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ Despliega Sim en tu propia infraestructura con Docker o Kubernetes.
1010

1111
## Requisitos
1212

13-
| Recurso | Mínimo | Recomendado |
14-
|----------|---------|-------------|
15-
| CPU | 2 núcleos | 4+ núcleos |
16-
| RAM | 12 GB | 16+ GB |
17-
| Almacenamiento | 20 GB SSD | 50+ GB SSD |
18-
| Docker | 20.10+ | Última versión |
13+
| Recurso | Pequeño | Estándar | Producción |
14+
|----------|---------|----------|------------|
15+
| CPU | 2 núcleos | 4 núcleos | 8+ núcleos |
16+
| RAM | 12 GB | 16 GB | 32+ GB |
17+
| Almacenamiento | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
18+
| Docker | 20.10+ | 20.10+ | Última versión |
19+
20+
**Pequeño**: Desarrollo, pruebas, usuario único (1-5 usuarios)
21+
**Estándar**: Equipos (5-50 usuarios), cargas de trabajo moderadas
22+
**Producción**: Equipos grandes (50+ usuarios), alta disponibilidad, ejecución intensiva de workflows
23+
24+
<Callout type="info">
25+
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.
26+
</Callout>
1927

2028
## Inicio rápido
2129

apps/docs/content/docs/fr/self-hosting/index.mdx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ Déployez Sim sur votre propre infrastructure avec Docker ou Kubernetes.
1010

1111
## Prérequis
1212

13-
| Ressource | Minimum | Recommandé |
14-
|----------|---------|-------------|
15-
| CPU | 2 cœurs | 4+ cœurs |
16-
| RAM | 12 Go | 16+ Go |
17-
| Stockage | 20 Go SSD | 50+ Go SSD |
18-
| Docker | 20.10+ | Dernière version |
13+
| Ressource | Petit | Standard | Production |
14+
|----------|-------|----------|------------|
15+
| CPU | 2 cœurs | 4 cœurs | 8+ cœurs |
16+
| RAM | 12 Go | 16 Go | 32+ Go |
17+
| Stockage | 20 Go SSD | 50 Go SSD | 100+ Go SSD |
18+
| Docker | 20.10+ | 20.10+ | Dernière version |
19+
20+
**Petit** : Développement, tests, utilisateur unique (1-5 utilisateurs)
21+
**Standard** : Équipes (5-50 utilisateurs), charges de travail modérées
22+
**Production** : Grandes équipes (50+ utilisateurs), haute disponibilité, exécution intensive de workflows
23+
24+
<Callout type="info">
25+
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.
26+
</Callout>
1927

2028
## Démarrage rapide
2129

apps/docs/content/docs/ja/self-hosting/index.mdx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ DockerまたはKubernetesを使用して、自社のインフラストラクチ
1010

1111
## 要件
1212

13-
| リソース | 最小 | 推奨 |
14-
|----------|---------|-------------|
15-
| CPU | 2コア | 4+コア |
16-
| RAM | 12 GB | 16+ GB |
17-
| ストレージ | 20 GB SSD | 50+ GB SSD |
18-
| Docker | 20.10+ | 最新版 |
13+
| リソース | スモール | スタンダード | プロダクション |
14+
|----------|---------|-------------|----------------|
15+
| CPU | 2コア | 4コア | 8+コア |
16+
| RAM | 12 GB | 16 GB | 32+ GB |
17+
| ストレージ | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
18+
| Docker | 20.10+ | 20.10+ | 最新版 |
19+
20+
**スモール**: 開発、テスト、シングルユーザー(1-5ユーザー)
21+
**スタンダード**: チーム(5-50ユーザー)、中程度のワークロード
22+
**プロダクション**: 大規模チーム(50+ユーザー)、高可用性、高負荷ワークフロー実行
23+
24+
<Callout type="info">
25+
リソース要件は、ワークフロー実行(isolated-vmサンドボックス)、ファイル処理(メモリ内ドキュメント解析)、ベクトル演算(pgvector)によって決まります。CPUよりもメモリが制約要因となることが多いです。本番環境のテレメトリによると、メインアプリは平均4-8 GB、高負荷時は最大12 GBを使用します。
26+
</Callout>
1927

2028
## クイックスタート
2129

apps/docs/content/docs/zh/self-hosting/index.mdx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ import { Callout } from 'fumadocs-ui/components/callout'
1010

1111
## 要求
1212

13-
| 资源 | 最低要求 | 推荐配置 |
14-
|----------|---------|-------------|
15-
| CPU | 2 核 | 4 核及以上 |
16-
| 内存 | 12 GB | 16 GB 及以上 |
17-
| 存储 | 20 GB SSD | 50 GB 及以上 SSD |
18-
| Docker | 20.10+ | 最新版本 |
13+
| 资源 | 小型 | 标准 | 生产环境 |
14+
|----------|------|------|----------|
15+
| CPU | 2 核 | 4 核 | 8+ 核 |
16+
| 内存 | 12 GB | 16 GB | 32+ GB |
17+
| 存储 | 20 GB SSD | 50 GB SSD | 100+ GB SSD |
18+
| Docker | 20.10+ | 20.10+ | 最新版本 |
19+
20+
**小型**: 开发、测试、单用户(1-5 用户)
21+
**标准**: 团队(5-50 用户)、中等工作负载
22+
**生产环境**: 大型团队(50+ 用户)、高可用性、密集工作流执行
23+
24+
<Callout type="info">
25+
资源需求由工作流执行(isolated-vm 沙箱)、文件处理(内存中文档解析)和向量运算(pgvector)决定。内存通常是限制因素,而不是 CPU。生产遥测数据显示,主应用平均使用 4-8 GB,高负载时峰值可达 12 GB。
26+
</Callout>
1927

2028
## 快速开始
2129

apps/sim/app/api/knowledge/search/utils.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ describe('Knowledge Search Utils', () => {
408408
input: ['test query'],
409409
model: 'text-embedding-3-small',
410410
encoding_format: 'float',
411+
dimensions: 1536,
411412
}),
412413
})
413414
)

apps/sim/lib/knowledge/embeddings.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ const logger = createLogger('EmbeddingUtils')
88

99
const MAX_TOKENS_PER_REQUEST = 8000
1010
const MAX_CONCURRENT_BATCHES = env.KB_CONFIG_CONCURRENCY_LIMIT || 50
11+
const EMBEDDING_DIMENSIONS = 1536
12+
13+
/**
14+
* Check if the model supports custom dimensions.
15+
* text-embedding-3-* models support the dimensions parameter.
16+
* Checks for 'embedding-3' to handle Azure deployments with custom naming conventions.
17+
*/
18+
function supportsCustomDimensions(modelName: string): boolean {
19+
const name = modelName.toLowerCase()
20+
return name.includes('embedding-3') && !name.includes('ada')
21+
}
1122

1223
export class EmbeddingAPIError extends Error {
1324
public status: number
@@ -93,15 +104,19 @@ async function getEmbeddingConfig(
93104
async function callEmbeddingAPI(inputs: string[], config: EmbeddingConfig): Promise<number[][]> {
94105
return retryWithExponentialBackoff(
95106
async () => {
107+
const useDimensions = supportsCustomDimensions(config.modelName)
108+
96109
const requestBody = config.useAzure
97110
? {
98111
input: inputs,
99112
encoding_format: 'float',
113+
...(useDimensions && { dimensions: EMBEDDING_DIMENSIONS }),
100114
}
101115
: {
102116
input: inputs,
103117
model: config.modelName,
104118
encoding_format: 'float',
119+
...(useDimensions && { dimensions: EMBEDDING_DIMENSIONS }),
105120
}
106121

107122
const response = await fetch(config.apiUrl, {

apps/sim/lib/uploads/providers/blob/client.ts

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,52 @@ const logger = createLogger('BlobClient')
1818

1919
let _blobServiceClient: BlobServiceClientInstance | null = null
2020

21+
interface ParsedCredentials {
22+
accountName: string
23+
accountKey: string
24+
}
25+
26+
/**
27+
* Extract account name and key from an Azure connection string.
28+
* Connection strings have the format: DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=...
29+
*/
30+
function parseConnectionString(connectionString: string): ParsedCredentials {
31+
const accountNameMatch = connectionString.match(/AccountName=([^;]+)/)
32+
if (!accountNameMatch) {
33+
throw new Error('Cannot extract account name from connection string')
34+
}
35+
36+
const accountKeyMatch = connectionString.match(/AccountKey=([^;]+)/)
37+
if (!accountKeyMatch) {
38+
throw new Error('Cannot extract account key from connection string')
39+
}
40+
41+
return {
42+
accountName: accountNameMatch[1],
43+
accountKey: accountKeyMatch[1],
44+
}
45+
}
46+
47+
/**
48+
* Get account credentials from BLOB_CONFIG, extracting from connection string if necessary.
49+
*/
50+
function getAccountCredentials(): ParsedCredentials {
51+
if (BLOB_CONFIG.connectionString) {
52+
return parseConnectionString(BLOB_CONFIG.connectionString)
53+
}
54+
55+
if (BLOB_CONFIG.accountName && BLOB_CONFIG.accountKey) {
56+
return {
57+
accountName: BLOB_CONFIG.accountName,
58+
accountKey: BLOB_CONFIG.accountKey,
59+
}
60+
}
61+
62+
throw new Error(
63+
'Azure Blob Storage credentials are missing – set AZURE_CONNECTION_STRING or both AZURE_ACCOUNT_NAME and AZURE_ACCOUNT_KEY'
64+
)
65+
}
66+
2167
export async function getBlobServiceClient(): Promise<BlobServiceClientInstance> {
2268
if (_blobServiceClient) return _blobServiceClient
2369

@@ -127,6 +173,8 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
127173
const containerClient = blobServiceClient.getContainerClient(BLOB_CONFIG.containerName)
128174
const blockBlobClient = containerClient.getBlockBlobClient(key)
129175

176+
const { accountName, accountKey } = getAccountCredentials()
177+
130178
const sasOptions = {
131179
containerName: BLOB_CONFIG.containerName,
132180
blobName: key,
@@ -137,13 +185,7 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
137185

138186
const sasToken = generateBlobSASQueryParameters(
139187
sasOptions,
140-
new StorageSharedKeyCredential(
141-
BLOB_CONFIG.accountName,
142-
BLOB_CONFIG.accountKey ??
143-
(() => {
144-
throw new Error('AZURE_ACCOUNT_KEY is required when using account name authentication')
145-
})()
146-
)
188+
new StorageSharedKeyCredential(accountName, accountKey)
147189
).toString()
148190

149191
return `${blockBlobClient.url}?${sasToken}`
@@ -168,9 +210,14 @@ export async function getPresignedUrlWithConfig(
168210
StorageSharedKeyCredential,
169211
} = await import('@azure/storage-blob')
170212
let tempBlobServiceClient: BlobServiceClientInstance
213+
let accountName: string
214+
let accountKey: string
171215

172216
if (customConfig.connectionString) {
173217
tempBlobServiceClient = BlobServiceClient.fromConnectionString(customConfig.connectionString)
218+
const credentials = parseConnectionString(customConfig.connectionString)
219+
accountName = credentials.accountName
220+
accountKey = credentials.accountKey
174221
} else if (customConfig.accountName && customConfig.accountKey) {
175222
const sharedKeyCredential = new StorageSharedKeyCredential(
176223
customConfig.accountName,
@@ -180,6 +227,8 @@ export async function getPresignedUrlWithConfig(
180227
`https://${customConfig.accountName}.blob.core.windows.net`,
181228
sharedKeyCredential
182229
)
230+
accountName = customConfig.accountName
231+
accountKey = customConfig.accountKey
183232
} else {
184233
throw new Error(
185234
'Custom blob config must include either connectionString or accountName + accountKey'
@@ -199,13 +248,7 @@ export async function getPresignedUrlWithConfig(
199248

200249
const sasToken = generateBlobSASQueryParameters(
201250
sasOptions,
202-
new StorageSharedKeyCredential(
203-
customConfig.accountName,
204-
customConfig.accountKey ??
205-
(() => {
206-
throw new Error('Account key is required when using account name authentication')
207-
})()
208-
)
251+
new StorageSharedKeyCredential(accountName, accountKey)
209252
).toString()
210253

211254
return `${blockBlobClient.url}?${sasToken}`
@@ -403,13 +446,9 @@ export async function getMultipartPartUrls(
403446
if (customConfig) {
404447
if (customConfig.connectionString) {
405448
blobServiceClient = BlobServiceClient.fromConnectionString(customConfig.connectionString)
406-
const match = customConfig.connectionString.match(/AccountName=([^;]+)/)
407-
if (!match) throw new Error('Cannot extract account name from connection string')
408-
accountName = match[1]
409-
410-
const keyMatch = customConfig.connectionString.match(/AccountKey=([^;]+)/)
411-
if (!keyMatch) throw new Error('Cannot extract account key from connection string')
412-
accountKey = keyMatch[1]
449+
const credentials = parseConnectionString(customConfig.connectionString)
450+
accountName = credentials.accountName
451+
accountKey = credentials.accountKey
413452
} else if (customConfig.accountName && customConfig.accountKey) {
414453
const credential = new StorageSharedKeyCredential(
415454
customConfig.accountName,
@@ -428,12 +467,9 @@ export async function getMultipartPartUrls(
428467
} else {
429468
blobServiceClient = await getBlobServiceClient()
430469
containerName = BLOB_CONFIG.containerName
431-
accountName = BLOB_CONFIG.accountName
432-
accountKey =
433-
BLOB_CONFIG.accountKey ||
434-
(() => {
435-
throw new Error('AZURE_ACCOUNT_KEY is required')
436-
})()
470+
const credentials = getAccountCredentials()
471+
accountName = credentials.accountName
472+
accountKey = credentials.accountKey
437473
}
438474

439475
const containerClient = blobServiceClient.getContainerClient(containerName)
@@ -501,12 +537,10 @@ export async function completeMultipartUpload(
501537
const containerClient = blobServiceClient.getContainerClient(containerName)
502538
const blockBlobClient = containerClient.getBlockBlobClient(key)
503539

504-
// Sort parts by part number and extract block IDs
505540
const sortedBlockIds = parts
506541
.sort((a, b) => a.partNumber - b.partNumber)
507542
.map((part) => part.blockId)
508543

509-
// Commit the block list to create the final blob
510544
await blockBlobClient.commitBlockList(sortedBlockIds, {
511545
metadata: {
512546
multipartUpload: 'completed',
@@ -557,10 +591,8 @@ export async function abortMultipartUpload(key: string, customConfig?: BlobConfi
557591
const blockBlobClient = containerClient.getBlockBlobClient(key)
558592

559593
try {
560-
// Delete the blob if it exists (this also cleans up any uncommitted blocks)
561594
await blockBlobClient.deleteIfExists()
562595
} catch (error) {
563-
// Ignore errors since we're just cleaning up
564596
logger.warn('Error cleaning up multipart upload:', error)
565597
}
566598
}

0 commit comments

Comments
 (0)