Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion apps/docs/content/docs/tools/knowledge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ In Sim Studio, the Knowledge Base block enables your agents to perform intellige

## Usage Instructions

Perform semantic vector search across one or more knowledge bases or upload new chunks to documents. Uses advanced AI embeddings to understand meaning and context for search operations.
Perform semantic vector search across knowledge bases, upload individual chunks to existing documents, or create new documents from text content. Uses advanced AI embeddings to understand meaning and context for search operations.



Expand Down Expand Up @@ -100,6 +100,25 @@ Upload a new chunk to a document in a knowledge base
| `createdAt` | string |
| `updatedAt` | string |

### `knowledge_create_document`

Create a new document in a knowledge base

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `knowledgeBaseId` | string | Yes | ID of the knowledge base containing the document |
| `name` | string | Yes | Name of the document |
| `content` | string | Yes | Content of the document |
Comment on lines +111 to +113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: knowledgeBaseId parameter description should clarify whether this is for new or existing knowledge bases


#### Output

| Parameter | Type |
| --------- | ---- |
| `data` | string |
| `name` | string |
Comment on lines +119 to +120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Output parameters 'data' and 'name' need more detailed type specifications and descriptions




## Block Configuration
Expand Down
19 changes: 19 additions & 0 deletions apps/sim/app/api/__test-utils__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,20 @@ export function createStorageProviderMocks(options: StorageProviderMockOptions =
bucket: 'test-s3-bucket',
region: 'us-east-1',
},
S3_KB_CONFIG: {
bucket: 'test-s3-kb-bucket',
region: 'us-east-1',
},
BLOB_CONFIG: {
accountName: 'testaccount',
accountKey: 'testkey',
containerName: 'test-container',
},
BLOB_KB_CONFIG: {
accountName: 'testaccount',
accountKey: 'testkey',
containerName: 'test-kb-container',
},
}))

vi.doMock('@aws-sdk/client-s3', () => ({
Expand Down Expand Up @@ -806,6 +820,11 @@ export function createStorageProviderMocks(options: StorageProviderMockOptions =
accountKey: 'testkey',
containerName: 'test-container',
},
BLOB_KB_CONFIG: {
accountName: 'testaccount',
accountKey: 'testkey',
containerName: 'test-kb-container',
},
}))

vi.doMock('@azure/storage-blob', () => ({
Expand Down
28 changes: 15 additions & 13 deletions apps/sim/app/api/chat/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,12 @@ export async function executeWorkflowForChat(

logger.debug(`[${requestId}] Using ${outputBlockIds.length} output blocks for extraction`)

// Find the workflow
// Find the workflow (deployedState is NOT deprecated - needed for chat execution)
const workflowResult = await db
.select({
state: workflow.state,
deployedState: workflow.deployedState,
isDeployed: workflow.isDeployed,
deployedState: workflow.deployedState,
variables: workflow.variables,
})
.from(workflow)
.where(eq(workflow.id, workflowId))
Expand All @@ -308,9 +308,14 @@ export async function executeWorkflowForChat(
throw new Error('Workflow not available')
}

// Use deployed state for execution
const state = workflowResult[0].deployedState || workflowResult[0].state
const { blocks, edges, loops, parallels } = state as WorkflowState
// For chat execution, use ONLY the deployed state (no fallback)
if (!workflowResult[0].deployedState) {
throw new Error(`Workflow must be deployed to be available for chat`)
}

// Use deployed state for chat execution (this is the stable, deployed version)
const deployedState = workflowResult[0].deployedState as WorkflowState
const { blocks, edges, loops, parallels } = deployedState

// Prepare for execution, similar to use-workflow-execution.ts
const mergedStates = mergeSubblockState(blocks)
Expand Down Expand Up @@ -344,16 +349,13 @@ export async function executeWorkflowForChat(
logger.warn(`[${requestId}] Could not fetch environment variables:`, error)
}

// Get workflow variables
let workflowVariables = {}
try {
// The workflow state may contain variables
const workflowState = state as any
if (workflowState.variables) {
if (workflowResult[0].variables) {
workflowVariables =
typeof workflowState.variables === 'string'
? JSON.parse(workflowState.variables)
: workflowState.variables
typeof workflowResult[0].variables === 'string'
? JSON.parse(workflowResult[0].variables)
: workflowResult[0].variables
}
} catch (error) {
logger.warn(`[${requestId}] Could not parse workflow variables:`, error)
Expand Down
109 changes: 97 additions & 12 deletions apps/sim/app/api/files/presigned/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ describe('/api/files/presigned', () => {
const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(400)
expect(response.status).toBe(500) // Changed from 400 to 500 (StorageConfigError)
expect(data.error).toBe('Direct uploads are only available when cloud storage is enabled')
expect(data.code).toBe('STORAGE_CONFIG_ERROR')
expect(data.directUploadSupported).toBe(false)
})

Expand All @@ -64,7 +65,8 @@ describe('/api/files/presigned', () => {
const data = await response.json()

expect(response.status).toBe(400)
expect(data.error).toBe('Missing fileName or contentType')
expect(data.error).toBe('fileName is required and cannot be empty')
expect(data.code).toBe('VALIDATION_ERROR')
})

it('should return error when contentType is missing', async () => {
Expand All @@ -87,7 +89,59 @@ describe('/api/files/presigned', () => {
const data = await response.json()

expect(response.status).toBe(400)
expect(data.error).toBe('Missing fileName or contentType')
expect(data.error).toBe('contentType is required and cannot be empty')
expect(data.code).toBe('VALIDATION_ERROR')
})

it('should return error when fileSize is invalid', async () => {
setupFileApiMocks({
cloudEnabled: true,
storageProvider: 's3',
})

const { POST } = await import('./route')

const request = new NextRequest('http://localhost:3000/api/files/presigned', {
method: 'POST',
body: JSON.stringify({
fileName: 'test.txt',
contentType: 'text/plain',
fileSize: 0,
}),
})

const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(400)
expect(data.error).toBe('fileSize must be a positive number')
expect(data.code).toBe('VALIDATION_ERROR')
})

it('should return error when file size exceeds limit', async () => {
setupFileApiMocks({
cloudEnabled: true,
storageProvider: 's3',
})

const { POST } = await import('./route')

const largeFileSize = 150 * 1024 * 1024 // 150MB (exceeds 100MB limit)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Magic number 150MB should be defined as a constant at the top of the file

const request = new NextRequest('http://localhost:3000/api/files/presigned', {
method: 'POST',
body: JSON.stringify({
fileName: 'large-file.txt',
contentType: 'text/plain',
fileSize: largeFileSize,
}),
})

const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(400)
expect(data.error).toContain('exceeds maximum allowed size')
expect(data.code).toBe('VALIDATION_ERROR')
})

it('should generate S3 presigned URL successfully', async () => {
Expand Down Expand Up @@ -122,6 +176,34 @@ describe('/api/files/presigned', () => {
expect(data.directUploadSupported).toBe(true)
})

it('should generate knowledge-base S3 presigned URL with kb prefix', async () => {
setupFileApiMocks({
cloudEnabled: true,
storageProvider: 's3',
})

const { POST } = await import('./route')

const request = new NextRequest(
'http://localhost:3000/api/files/presigned?type=knowledge-base',
{
method: 'POST',
body: JSON.stringify({
fileName: 'knowledge-doc.pdf',
contentType: 'application/pdf',
fileSize: 2048,
}),
}
)

const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(200)
expect(data.fileInfo.key).toMatch(/^kb\/.*knowledge-doc\.pdf$/)
expect(data.directUploadSupported).toBe(true)
})

it('should generate Azure Blob presigned URL successfully', async () => {
setupFileApiMocks({
cloudEnabled: true,
Expand Down Expand Up @@ -182,8 +264,9 @@ describe('/api/files/presigned', () => {
const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(400)
expect(data.error).toBe('Unknown storage provider')
expect(response.status).toBe(500) // Changed from 400 to 500 (StorageConfigError)
expect(data.error).toBe('Unknown storage provider: unknown') // Updated error message
expect(data.code).toBe('STORAGE_CONFIG_ERROR')
expect(data.directUploadSupported).toBe(false)
})

Expand Down Expand Up @@ -225,8 +308,10 @@ describe('/api/files/presigned', () => {
const data = await response.json()

expect(response.status).toBe(500)
expect(data.error).toBe('Error')
expect(data.message).toBe('S3 service unavailable')
expect(data.error).toBe(
'Failed to generate S3 presigned URL - check AWS credentials and permissions'
) // Updated error message
expect(data.code).toBe('STORAGE_CONFIG_ERROR')
})

it('should handle Azure Blob errors gracefully', async () => {
Expand Down Expand Up @@ -269,8 +354,8 @@ describe('/api/files/presigned', () => {
const data = await response.json()

expect(response.status).toBe(500)
expect(data.error).toBe('Error')
expect(data.message).toBe('Azure service unavailable')
expect(data.error).toBe('Failed to generate Azure Blob presigned URL') // Updated error message
expect(data.code).toBe('STORAGE_CONFIG_ERROR')
})

it('should handle malformed JSON gracefully', async () => {
Expand All @@ -289,9 +374,9 @@ describe('/api/files/presigned', () => {
const response = await POST(request)
const data = await response.json()

expect(response.status).toBe(500)
expect(data.error).toBe('SyntaxError')
expect(data.message).toContain('Unexpected token')
expect(response.status).toBe(400) // Changed from 500 to 400 (ValidationError)
expect(data.error).toBe('Invalid JSON in request body') // Updated error message
expect(data.code).toBe('VALIDATION_ERROR')
})
})

Expand Down
Loading