feat(server): improve context metadata & matching (#12064)

fix AI-20

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Enhanced file metadata with MIME type, blob ID, and file name across context and workspace, now visible in UI and API.
  - Added workspace-level matching for files and documents with configurable thresholds and workspace scoping in search queries.
  - Introduced a new error type and user-friendly messaging for global workspace context matching failures.

- **Bug Fixes**
  - Improved consistent handling of file MIME types and nullable context IDs for accurate metadata.

- **Documentation**
  - Updated GraphQL schema, queries, and mutations to include new metadata fields, optional parameters, and error types.

- **Style**
  - Added new localization strings for global context matching error messages.

- **Tests**
  - Extended test coverage with new and updated snapshot tests for metadata and matching logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
darkskygit
2025-05-14 06:32:29 +00:00
parent 04c5fd6dfc
commit cecf545590
36 changed files with 465 additions and 108 deletions
@@ -1316,7 +1316,11 @@ test('should be able to manage context', async t => {
// file record
{
await storage.put(userId, session.workspaceId, 'blob', buffer);
const file = await session.addFile('blob', 'sample.pdf');
const file = await session.addFile(
'blob',
'sample.pdf',
'application/pdf'
);
const handler = Sinon.spy(event, 'emit');
@@ -1345,7 +1349,7 @@ test('should be able to manage context', async t => {
'should list file id'
);
const result = await session.matchFileChunks('test', 1, undefined, 1);
const result = await session.matchFiles('test', 1, undefined, 1);
t.is(result.length, 1, 'should match context');
t.is(result[0].fileId, file.id, 'should match file id');
}
@@ -1499,13 +1503,13 @@ test('should be able to manage workspace embedding', async t => {
});
const contextSession = await context.create(sessionId);
const ret = await contextSession.matchFileChunks('test', 1, undefined, 1);
const ret = await contextSession.matchFiles('test', 1, undefined, 1);
t.is(ret.length, 1, 'should match workspace context');
t.is(ret[0].content, 'content', 'should match content');
await workspace.update(ws.id, { enableDocEmbedding: false });
const ret2 = await contextSession.matchFileChunks('test', 1, undefined, 1);
const ret2 = await contextSession.matchFiles('test', 1, undefined, 1);
t.is(ret2.length, 0, 'should not match workspace context');
}
});
@@ -6,6 +6,17 @@ Generated by [AVA](https://avajs.dev).
## should insert embedding by doc id
> should match file embedding
[
{
chunk: 0,
content: 'content',
distance: 0,
fileId: 'file-id',
},
]
> should return empty array when embedding deleted
[]
@@ -54,3 +54,18 @@ Generated by [AVA](https://avajs.dev).
docId: 'new_doc',
},
]
## should insert and search embedding
> should match workspace file embedding
[
{
blobId: 'blob1',
chunk: 0,
content: 'content',
distance: 0,
mimeType: 'text/plain',
name: 'file1',
},
]
@@ -111,8 +111,7 @@ test('should insert embedding by doc id', async t => {
1,
1
);
t.is(ret.length, 1);
t.is(ret[0].content, 'content');
t.snapshot(ret, 'should match file embedding');
}
{
@@ -107,6 +107,7 @@ test('should insert and search embedding', async t => {
{
const { fileId } = await t.context.copilotWorkspace.addFile(workspace.id, {
fileName: 'file1',
blobId: 'blob1',
mimeType: 'text/plain',
size: 1,
});
@@ -129,8 +130,10 @@ test('should insert and search embedding', async t => {
1,
1
);
t.is(ret.length, 1);
t.is(ret[0].content, 'content');
t.snapshot(
cleanObject(ret, ['fileId']),
'should match workspace file embedding'
);
}
}
});