@lobehub/lobehub 2.0.0-next.110 → 2.0.0-next.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +2 -1
- package/package.json +1 -1
- package/packages/context-engine/src/index.ts +1 -1
- package/packages/context-engine/src/providers/KnowledgeInjector.ts +78 -0
- package/packages/context-engine/src/providers/index.ts +2 -0
- package/packages/database/migrations/0047_add_slug_document.sql +1 -5
- package/packages/database/migrations/meta/0047_snapshot.json +30 -14
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/core/migrations.json +3 -3
- package/packages/database/src/models/__tests__/agent.test.ts +172 -3
- package/packages/database/src/models/__tests__/userMemories.test.ts +1382 -0
- package/packages/database/src/models/agent.ts +22 -1
- package/packages/database/src/models/userMemory.ts +993 -0
- package/packages/database/src/schemas/file.ts +5 -10
- package/packages/database/src/schemas/userMemories.ts +22 -5
- package/packages/model-bank/src/aiModels/qwen.ts +41 -3
- package/packages/model-runtime/src/providers/qwen/index.ts +9 -3
- package/packages/prompts/src/prompts/files/__snapshots__/knowledgeBase.test.ts.snap +103 -0
- package/packages/prompts/src/prompts/files/index.ts +3 -0
- package/packages/prompts/src/prompts/files/knowledgeBase.test.ts +167 -0
- package/packages/prompts/src/prompts/files/knowledgeBase.ts +85 -0
- package/packages/types/src/files/index.ts +1 -0
- package/packages/types/src/index.ts +1 -0
- package/packages/types/src/knowledgeBase/index.ts +1 -0
- package/packages/types/src/userMemory/index.ts +3 -0
- package/packages/types/src/userMemory/layers.ts +54 -0
- package/packages/types/src/userMemory/shared.ts +64 -0
- package/packages/types/src/userMemory/tools.ts +240 -0
- package/src/features/ChatList/Messages/index.tsx +16 -19
- package/src/features/ChatList/components/ContextMenu.tsx +23 -16
- package/src/helpers/toolEngineering/index.ts +5 -9
- package/src/hooks/useQueryParam.ts +24 -22
- package/src/server/routers/async/file.ts +2 -7
- package/src/server/routers/lambda/chunk.ts +6 -1
- package/src/services/chat/contextEngineering.ts +19 -0
- package/src/store/agent/slices/chat/selectors/agent.ts +4 -0
- package/src/store/chat/slices/builtinTool/actions/knowledgeBase.ts +5 -16
- package/src/tools/knowledge-base/ExecutionRuntime/index.ts +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
2
2
|
import { isNotNull } from 'drizzle-orm';
|
|
3
3
|
import {
|
|
4
|
+
AnyPgColumn,
|
|
4
5
|
boolean,
|
|
5
6
|
index,
|
|
6
7
|
integer,
|
|
@@ -41,7 +42,6 @@ export type GlobalFileItem = typeof globalFiles.$inferSelect;
|
|
|
41
42
|
/**
|
|
42
43
|
* Documents table - Stores file content or web search results
|
|
43
44
|
*/
|
|
44
|
-
// @ts-ignore
|
|
45
45
|
export const documents = pgTable(
|
|
46
46
|
'documents',
|
|
47
47
|
{
|
|
@@ -72,15 +72,12 @@ export const documents = pgTable(
|
|
|
72
72
|
source: text('source').notNull(), // File path or web URL
|
|
73
73
|
|
|
74
74
|
// Associated file (optional)
|
|
75
|
-
//
|
|
75
|
+
// forward reference needs AnyPgColumn to avoid circular type inference
|
|
76
76
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
77
|
-
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
79
|
-
fileId: text('file_id').references(() => files.id, { onDelete: 'set null' }),
|
|
77
|
+
fileId: text('file_id').references((): AnyPgColumn => files.id, { onDelete: 'set null' }),
|
|
80
78
|
|
|
81
79
|
// Parent document (for folder hierarchy structure)
|
|
82
|
-
|
|
83
|
-
parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
|
|
80
|
+
parentId: varchar('parent_id', { length: 255 }).references((): AnyPgColumn => documents.id, {
|
|
84
81
|
onDelete: 'set null',
|
|
85
82
|
}),
|
|
86
83
|
|
|
@@ -113,7 +110,6 @@ export type NewDocument = typeof documents.$inferInsert;
|
|
|
113
110
|
export type DocumentItem = typeof documents.$inferSelect;
|
|
114
111
|
export const insertDocumentSchema = createInsertSchema(documents);
|
|
115
112
|
|
|
116
|
-
// @ts-ignore
|
|
117
113
|
export const files = pgTable(
|
|
118
114
|
'files',
|
|
119
115
|
{
|
|
@@ -140,8 +136,7 @@ export const files = pgTable(
|
|
|
140
136
|
source: text('source').$type<FileSource>(),
|
|
141
137
|
|
|
142
138
|
// Parent Folder or Document
|
|
143
|
-
|
|
144
|
-
parentId: varchar('parent_id', { length: 255 }).references(() => documents.id, {
|
|
139
|
+
parentId: varchar('parent_id', { length: 255 }).references((): AnyPgColumn => documents.id, {
|
|
145
140
|
onDelete: 'set null',
|
|
146
141
|
}),
|
|
147
142
|
|
|
@@ -17,7 +17,7 @@ export const userMemories = pgTable(
|
|
|
17
17
|
memoryCategory: varchar255('memory_category'),
|
|
18
18
|
memoryLayer: varchar255('memory_layer'),
|
|
19
19
|
memoryType: varchar255('memory_type'),
|
|
20
|
-
metadata: jsonb('metadata'),
|
|
20
|
+
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
|
21
21
|
tags: text('tags').array(),
|
|
22
22
|
|
|
23
23
|
title: varchar255('title'),
|
|
@@ -55,7 +55,7 @@ export const userMemoriesContexts = pgTable(
|
|
|
55
55
|
userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
|
56
56
|
userMemoryIds: jsonb('user_memory_ids'),
|
|
57
57
|
|
|
58
|
-
metadata: jsonb('metadata'),
|
|
58
|
+
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
|
59
59
|
tags: text('tags').array(),
|
|
60
60
|
|
|
61
61
|
associatedObjects: jsonb('associated_objects'),
|
|
@@ -99,7 +99,7 @@ export const userMemoriesPreferences = pgTable(
|
|
|
99
99
|
onDelete: 'cascade',
|
|
100
100
|
}),
|
|
101
101
|
|
|
102
|
-
metadata: jsonb('metadata'),
|
|
102
|
+
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
|
103
103
|
tags: text('tags').array(),
|
|
104
104
|
|
|
105
105
|
conclusionDirectives: text('conclusion_directives'),
|
|
@@ -132,7 +132,7 @@ export const userMemoriesIdentities = pgTable(
|
|
|
132
132
|
onDelete: 'cascade',
|
|
133
133
|
}),
|
|
134
134
|
|
|
135
|
-
metadata: jsonb('metadata'),
|
|
135
|
+
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
|
136
136
|
tags: text('tags').array(),
|
|
137
137
|
|
|
138
138
|
type: varchar255('type'),
|
|
@@ -165,7 +165,7 @@ export const userMemoriesExperiences = pgTable(
|
|
|
165
165
|
onDelete: 'cascade',
|
|
166
166
|
}),
|
|
167
167
|
|
|
168
|
-
metadata: jsonb('metadata'),
|
|
168
|
+
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
|
169
169
|
tags: text('tags').array(),
|
|
170
170
|
|
|
171
171
|
type: varchar255('type'),
|
|
@@ -200,16 +200,33 @@ export const userMemoriesExperiences = pgTable(
|
|
|
200
200
|
);
|
|
201
201
|
|
|
202
202
|
export type UserMemoryItem = typeof userMemories.$inferSelect;
|
|
203
|
+
export type UserMemoryItemWithoutVectors = Omit<
|
|
204
|
+
UserMemoryItem,
|
|
205
|
+
'summaryVector1024' | 'detailsVector1024'
|
|
206
|
+
>;
|
|
203
207
|
export type NewUserMemory = typeof userMemories.$inferInsert;
|
|
204
208
|
|
|
205
209
|
export type UserMemoryPreference = typeof userMemoriesPreferences.$inferSelect;
|
|
210
|
+
export type UserMemoryPreferencesWithoutVectors = Omit<
|
|
211
|
+
UserMemoryPreference,
|
|
212
|
+
'conclusionDirectivesVector'
|
|
213
|
+
>;
|
|
206
214
|
export type NewUserMemoryPreference = typeof userMemoriesPreferences.$inferInsert;
|
|
207
215
|
|
|
208
216
|
export type UserMemoryContext = typeof userMemoriesContexts.$inferSelect;
|
|
217
|
+
export type UserMemoryContextsWithoutVectors = Omit<
|
|
218
|
+
UserMemoryContext,
|
|
219
|
+
'titleVector' | 'descriptionVector'
|
|
220
|
+
>;
|
|
209
221
|
export type NewUserMemoryContext = typeof userMemoriesContexts.$inferInsert;
|
|
210
222
|
|
|
211
223
|
export type UserMemoryIdentity = typeof userMemoriesIdentities.$inferSelect;
|
|
224
|
+
export type UserMemoryIdentitiesWithoutVectors = Omit<UserMemoryIdentity, 'descriptionVector'>;
|
|
212
225
|
export type NewUserMemoryIdentity = typeof userMemoriesIdentities.$inferInsert;
|
|
213
226
|
|
|
214
227
|
export type UserMemoryExperience = typeof userMemoriesExperiences.$inferSelect;
|
|
228
|
+
export type UserMemoryExperiencesWithoutVectors = Omit<
|
|
229
|
+
UserMemoryExperience,
|
|
230
|
+
'situationVector' | 'actionVector' | 'keyLearningVector'
|
|
231
|
+
>;
|
|
215
232
|
export type NewUserMemoryExperience = typeof userMemoriesExperiences.$inferInsert;
|
|
@@ -3,6 +3,31 @@ import { AIChatModelCard, AIImageModelCard } from '../types/aiModel';
|
|
|
3
3
|
// https://help.aliyun.com/zh/model-studio/models?spm=a2c4g.11186623
|
|
4
4
|
|
|
5
5
|
const qwenChatModels: AIChatModelCard[] = [
|
|
6
|
+
{
|
|
7
|
+
abilities: {
|
|
8
|
+
functionCall: true,
|
|
9
|
+
reasoning: true,
|
|
10
|
+
},
|
|
11
|
+
contextWindowTokens: 262_144,
|
|
12
|
+
description:
|
|
13
|
+
'kimi-k2-thinking模型是月之暗面提供的具有通用 Agentic能力和推理能力的思考模型,它擅长深度推理,并可通过多步工具调用,帮助解决各类难题。',
|
|
14
|
+
displayName: 'Kimi K2 Thinking',
|
|
15
|
+
id: 'kimi-k2-thinking',
|
|
16
|
+
maxOutput: 16_384,
|
|
17
|
+
organization: 'Qwen',
|
|
18
|
+
pricing: {
|
|
19
|
+
currency: 'CNY',
|
|
20
|
+
units: [
|
|
21
|
+
{ name: 'textInput', rate: 4, strategy: 'fixed', unit: 'millionTokens' },
|
|
22
|
+
{ name: 'textOutput', rate: 16, strategy: 'fixed', unit: 'millionTokens' },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
releasedAt: '2025-11-10',
|
|
26
|
+
settings: {
|
|
27
|
+
extendParams: ['reasoningBudgetToken'],
|
|
28
|
+
},
|
|
29
|
+
type: 'chat',
|
|
30
|
+
},
|
|
6
31
|
{
|
|
7
32
|
abilities: {
|
|
8
33
|
reasoning: true,
|
|
@@ -109,6 +134,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
109
134
|
{
|
|
110
135
|
abilities: {
|
|
111
136
|
reasoning: true,
|
|
137
|
+
search: true,
|
|
112
138
|
},
|
|
113
139
|
contextWindowTokens: 131_072,
|
|
114
140
|
description:
|
|
@@ -131,6 +157,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
131
157
|
{
|
|
132
158
|
abilities: {
|
|
133
159
|
reasoning: true,
|
|
160
|
+
search: true,
|
|
134
161
|
},
|
|
135
162
|
contextWindowTokens: 131_072,
|
|
136
163
|
description: 'DeepSeek V3.1 模型为混合推理架构模型,同时支持思考模式与非思考模式。',
|
|
@@ -151,6 +178,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
151
178
|
},
|
|
152
179
|
{
|
|
153
180
|
abilities: {
|
|
181
|
+
functionCall: true,
|
|
154
182
|
search: true,
|
|
155
183
|
},
|
|
156
184
|
contextWindowTokens: 131_072,
|
|
@@ -1231,6 +1259,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1231
1259
|
},
|
|
1232
1260
|
{
|
|
1233
1261
|
abilities: {
|
|
1262
|
+
reasoning: true,
|
|
1234
1263
|
vision: true,
|
|
1235
1264
|
},
|
|
1236
1265
|
config: {
|
|
@@ -1239,7 +1268,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1239
1268
|
contextWindowTokens: 65_536,
|
|
1240
1269
|
description:
|
|
1241
1270
|
'Qwen-Omni 模型能够接收文本、图片、音频、视频等多种模态的组合输入,并生成文本或语音形式的回复, 提供多种拟人音色,支持多语言和方言的语音输出,可应用于文本创作、视觉识别、语音助手等场景。',
|
|
1242
|
-
displayName: '
|
|
1271
|
+
displayName: 'Qwen3 Omni Flash',
|
|
1243
1272
|
id: 'qwen3-omni-flash',
|
|
1244
1273
|
maxOutput: 16_384,
|
|
1245
1274
|
organization: 'Qwen',
|
|
@@ -1432,6 +1461,9 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1432
1461
|
{ name: 'textOutput', rate: 5, strategy: 'fixed', unit: 'millionTokens' },
|
|
1433
1462
|
],
|
|
1434
1463
|
},
|
|
1464
|
+
settings: {
|
|
1465
|
+
extendParams: ['reasoningBudgetToken'],
|
|
1466
|
+
},
|
|
1435
1467
|
type: 'chat',
|
|
1436
1468
|
},
|
|
1437
1469
|
{
|
|
@@ -1473,7 +1505,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1473
1505
|
],
|
|
1474
1506
|
},
|
|
1475
1507
|
settings: {
|
|
1476
|
-
extendParams: ['
|
|
1508
|
+
extendParams: ['reasoningBudgetToken'],
|
|
1477
1509
|
},
|
|
1478
1510
|
type: 'chat',
|
|
1479
1511
|
},
|
|
@@ -1517,7 +1549,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1517
1549
|
],
|
|
1518
1550
|
},
|
|
1519
1551
|
settings: {
|
|
1520
|
-
extendParams: ['
|
|
1552
|
+
extendParams: ['reasoningBudgetToken'],
|
|
1521
1553
|
},
|
|
1522
1554
|
type: 'chat',
|
|
1523
1555
|
},
|
|
@@ -1976,7 +2008,9 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1976
2008
|
},
|
|
1977
2009
|
{
|
|
1978
2010
|
abilities: {
|
|
2011
|
+
functionCall: true,
|
|
1979
2012
|
reasoning: true,
|
|
2013
|
+
search: true,
|
|
1980
2014
|
},
|
|
1981
2015
|
contextWindowTokens: 131_072,
|
|
1982
2016
|
description:
|
|
@@ -1996,6 +2030,10 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
|
1996
2030
|
type: 'chat',
|
|
1997
2031
|
},
|
|
1998
2032
|
{
|
|
2033
|
+
abilities: {
|
|
2034
|
+
functionCall: true,
|
|
2035
|
+
search: true,
|
|
2036
|
+
},
|
|
1999
2037
|
contextWindowTokens: 65_536,
|
|
2000
2038
|
description:
|
|
2001
2039
|
'DeepSeek-V3 为自研 MoE 模型,671B 参数,激活 37B,在 14.8T token 上进行了预训练,在长文本、代码、数学、百科、中文能力上表现优秀。',
|
|
@@ -51,9 +51,15 @@ export const LobeQwenAI = createOpenAICompatibleRuntime({
|
|
|
51
51
|
thinking_budget:
|
|
52
52
|
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
53
53
|
}
|
|
54
|
-
: [
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
: [
|
|
55
|
+
'qwen3',
|
|
56
|
+
'qwen-turbo',
|
|
57
|
+
'qwen-plus',
|
|
58
|
+
'qwen-flash',
|
|
59
|
+
'deepseek-v3.1',
|
|
60
|
+
'deepseek-v3.2',
|
|
61
|
+
'glm',
|
|
62
|
+
].some((keyword) => model.toLowerCase().includes(keyword))
|
|
57
63
|
? {
|
|
58
64
|
enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false,
|
|
59
65
|
thinking_budget:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`promptAgentKnowledge > should format both files and knowledge bases 1`] = `
|
|
4
|
+
"<agent_knowledge>
|
|
5
|
+
<instruction>The following files and knowledge bases are available. For files, refer to their content directly. For knowledge bases, use the searchKnowledgeBase tool to find relevant information.</instruction>
|
|
6
|
+
<files totalCount="1">
|
|
7
|
+
<file id="file1" name="readme.md">
|
|
8
|
+
File content here
|
|
9
|
+
</file>
|
|
10
|
+
</files>
|
|
11
|
+
<knowledge_bases totalCount="1">
|
|
12
|
+
<knowledge_base id="kb1" name="Internal Docs" description="Company knowledge base" />
|
|
13
|
+
</knowledge_bases>
|
|
14
|
+
</agent_knowledge>"
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
exports[`promptAgentKnowledge > should format only files when no knowledge bases 1`] = `
|
|
18
|
+
"<agent_knowledge>
|
|
19
|
+
<instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
|
|
20
|
+
<files totalCount="2">
|
|
21
|
+
<file id="file1" name="doc1.txt">
|
|
22
|
+
This is the content of document 1
|
|
23
|
+
</file>
|
|
24
|
+
<file id="file2" name="doc2.md">
|
|
25
|
+
This is the content of document 2
|
|
26
|
+
</file>
|
|
27
|
+
</files>
|
|
28
|
+
</agent_knowledge>"
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
exports[`promptAgentKnowledge > should format only knowledge bases when no files 1`] = `
|
|
32
|
+
"<agent_knowledge>
|
|
33
|
+
<instruction>The following knowledge bases are available for semantic search. Use the searchKnowledgeBase tool to find relevant information.</instruction>
|
|
34
|
+
<knowledge_bases totalCount="2">
|
|
35
|
+
<knowledge_base id="kb1" name="Documentation" description="API documentation" />
|
|
36
|
+
<knowledge_base id="kb2" name="FAQs" />
|
|
37
|
+
</knowledge_bases>
|
|
38
|
+
</agent_knowledge>"
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
exports[`promptAgentKnowledge > should handle file with error 1`] = `
|
|
42
|
+
"<agent_knowledge>
|
|
43
|
+
<instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
|
|
44
|
+
<files totalCount="1">
|
|
45
|
+
<file id="file1" name="missing.txt" error="File not found" />
|
|
46
|
+
</files>
|
|
47
|
+
</agent_knowledge>"
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
exports[`promptAgentKnowledge > should handle file with multiline content 1`] = `
|
|
51
|
+
"<agent_knowledge>
|
|
52
|
+
<instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
|
|
53
|
+
<files totalCount="1">
|
|
54
|
+
<file id="file1" name="multiline.txt">
|
|
55
|
+
Line 1
|
|
56
|
+
Line 2
|
|
57
|
+
Line 3
|
|
58
|
+
|
|
59
|
+
Line 5 with gap
|
|
60
|
+
</file>
|
|
61
|
+
</files>
|
|
62
|
+
</agent_knowledge>"
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
exports[`promptAgentKnowledge > should handle file with special characters in filename 1`] = `
|
|
66
|
+
"<agent_knowledge>
|
|
67
|
+
<instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>
|
|
68
|
+
<files totalCount="1">
|
|
69
|
+
<file id="file1" name="file with spaces & special-chars.txt">
|
|
70
|
+
Special content
|
|
71
|
+
</file>
|
|
72
|
+
</files>
|
|
73
|
+
</agent_knowledge>"
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
exports[`promptAgentKnowledge > should handle knowledge base without description 1`] = `
|
|
77
|
+
"<agent_knowledge>
|
|
78
|
+
<instruction>The following knowledge bases are available for semantic search. Use the searchKnowledgeBase tool to find relevant information.</instruction>
|
|
79
|
+
<knowledge_bases totalCount="1">
|
|
80
|
+
<knowledge_base id="kb1" name="Simple KB" />
|
|
81
|
+
</knowledge_bases>
|
|
82
|
+
</agent_knowledge>"
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
exports[`promptAgentKnowledge > should handle multiple files and multiple knowledge bases 1`] = `
|
|
86
|
+
"<agent_knowledge>
|
|
87
|
+
<instruction>The following files and knowledge bases are available. For files, refer to their content directly. For knowledge bases, use the searchKnowledgeBase tool to find relevant information.</instruction>
|
|
88
|
+
<files totalCount="3">
|
|
89
|
+
<file id="file1" name="first.txt">
|
|
90
|
+
Content of first file
|
|
91
|
+
</file>
|
|
92
|
+
<file id="file2" name="second.md">
|
|
93
|
+
Content of second file
|
|
94
|
+
</file>
|
|
95
|
+
<file id="file3" name="broken.pdf" error="Parse error" />
|
|
96
|
+
</files>
|
|
97
|
+
<knowledge_bases totalCount="3">
|
|
98
|
+
<knowledge_base id="kb1" name="Tech Docs" description="Technical documentation" />
|
|
99
|
+
<knowledge_base id="kb2" name="User Guides" />
|
|
100
|
+
<knowledge_base id="kb3" name="FAQ Database" description="Frequently asked questions" />
|
|
101
|
+
</knowledge_bases>
|
|
102
|
+
</agent_knowledge>"
|
|
103
|
+
`;
|
|
@@ -4,6 +4,9 @@ import { filePrompts } from './file';
|
|
|
4
4
|
import { imagesPrompts } from './image';
|
|
5
5
|
import { videosPrompts } from './video';
|
|
6
6
|
|
|
7
|
+
export type { KnowledgeBaseInfo, PromptKnowledgeOptions } from './knowledgeBase';
|
|
8
|
+
export { promptAgentKnowledge } from './knowledgeBase';
|
|
9
|
+
|
|
7
10
|
export const filesPrompts = ({
|
|
8
11
|
imageList,
|
|
9
12
|
fileList,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { FileContent } from '../knowledgeBaseQA';
|
|
4
|
+
import { promptAgentKnowledge } from './knowledgeBase';
|
|
5
|
+
import type { KnowledgeBaseInfo } from './knowledgeBase';
|
|
6
|
+
|
|
7
|
+
describe('promptAgentKnowledge', () => {
|
|
8
|
+
it('should return empty string when no files and no knowledge bases', () => {
|
|
9
|
+
const result = promptAgentKnowledge({});
|
|
10
|
+
expect(result).toBe('');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should format only files when no knowledge bases', () => {
|
|
14
|
+
const fileContents: FileContent[] = [
|
|
15
|
+
{
|
|
16
|
+
content: 'This is the content of document 1',
|
|
17
|
+
fileId: 'file1',
|
|
18
|
+
filename: 'doc1.txt',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
content: 'This is the content of document 2',
|
|
22
|
+
fileId: 'file2',
|
|
23
|
+
filename: 'doc2.md',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const result = promptAgentKnowledge({ fileContents });
|
|
28
|
+
expect(result).toMatchSnapshot();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should format only knowledge bases when no files', () => {
|
|
32
|
+
const knowledgeBases: KnowledgeBaseInfo[] = [
|
|
33
|
+
{
|
|
34
|
+
description: 'API documentation',
|
|
35
|
+
id: 'kb1',
|
|
36
|
+
name: 'Documentation',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
description: null,
|
|
40
|
+
id: 'kb2',
|
|
41
|
+
name: 'FAQs',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const result = promptAgentKnowledge({ knowledgeBases });
|
|
46
|
+
expect(result).toMatchSnapshot();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should format both files and knowledge bases', () => {
|
|
50
|
+
const fileContents: FileContent[] = [
|
|
51
|
+
{
|
|
52
|
+
content: 'File content here',
|
|
53
|
+
fileId: 'file1',
|
|
54
|
+
filename: 'readme.md',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const knowledgeBases: KnowledgeBaseInfo[] = [
|
|
59
|
+
{
|
|
60
|
+
description: 'Company knowledge base',
|
|
61
|
+
id: 'kb1',
|
|
62
|
+
name: 'Internal Docs',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const result = promptAgentKnowledge({ fileContents, knowledgeBases });
|
|
67
|
+
expect(result).toMatchSnapshot();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle file with error', () => {
|
|
71
|
+
const fileContents: FileContent[] = [
|
|
72
|
+
{
|
|
73
|
+
content: '',
|
|
74
|
+
error: 'File not found',
|
|
75
|
+
fileId: 'file1',
|
|
76
|
+
filename: 'missing.txt',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const result = promptAgentKnowledge({ fileContents });
|
|
81
|
+
expect(result).toMatchSnapshot();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle multiple files and multiple knowledge bases', () => {
|
|
85
|
+
const fileContents: FileContent[] = [
|
|
86
|
+
{
|
|
87
|
+
content: 'Content of first file',
|
|
88
|
+
fileId: 'file1',
|
|
89
|
+
filename: 'first.txt',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
content: 'Content of second file',
|
|
93
|
+
fileId: 'file2',
|
|
94
|
+
filename: 'second.md',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
content: '',
|
|
98
|
+
error: 'Parse error',
|
|
99
|
+
fileId: 'file3',
|
|
100
|
+
filename: 'broken.pdf',
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const knowledgeBases: KnowledgeBaseInfo[] = [
|
|
105
|
+
{
|
|
106
|
+
description: 'Technical documentation',
|
|
107
|
+
id: 'kb1',
|
|
108
|
+
name: 'Tech Docs',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
description: null,
|
|
112
|
+
id: 'kb2',
|
|
113
|
+
name: 'User Guides',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
description: 'Frequently asked questions',
|
|
117
|
+
id: 'kb3',
|
|
118
|
+
name: 'FAQ Database',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const result = promptAgentKnowledge({ fileContents, knowledgeBases });
|
|
123
|
+
expect(result).toMatchSnapshot();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle knowledge base without description', () => {
|
|
127
|
+
const knowledgeBases: KnowledgeBaseInfo[] = [
|
|
128
|
+
{
|
|
129
|
+
id: 'kb1',
|
|
130
|
+
name: 'Simple KB',
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const result = promptAgentKnowledge({ knowledgeBases });
|
|
135
|
+
expect(result).toMatchSnapshot();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle file with special characters in filename', () => {
|
|
139
|
+
const fileContents: FileContent[] = [
|
|
140
|
+
{
|
|
141
|
+
content: 'Special content',
|
|
142
|
+
fileId: 'file1',
|
|
143
|
+
filename: 'file with spaces & special-chars.txt',
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const result = promptAgentKnowledge({ fileContents });
|
|
148
|
+
expect(result).toMatchSnapshot();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle file with multiline content', () => {
|
|
152
|
+
const fileContents: FileContent[] = [
|
|
153
|
+
{
|
|
154
|
+
content: `Line 1
|
|
155
|
+
Line 2
|
|
156
|
+
Line 3
|
|
157
|
+
|
|
158
|
+
Line 5 with gap`,
|
|
159
|
+
fileId: 'file1',
|
|
160
|
+
filename: 'multiline.txt',
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const result = promptAgentKnowledge({ fileContents });
|
|
165
|
+
expect(result).toMatchSnapshot();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { FileContent } from '../knowledgeBaseQA';
|
|
2
|
+
|
|
3
|
+
export interface KnowledgeBaseInfo {
|
|
4
|
+
description?: string | null;
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PromptKnowledgeOptions {
|
|
10
|
+
/** File contents to inject */
|
|
11
|
+
fileContents?: FileContent[];
|
|
12
|
+
/** Knowledge bases to include */
|
|
13
|
+
knowledgeBases?: KnowledgeBaseInfo[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Formats a single file content with XML tags
|
|
18
|
+
*/
|
|
19
|
+
const formatFileContent = (file: FileContent): string => {
|
|
20
|
+
if (file.error) {
|
|
21
|
+
return `<file id="${file.fileId}" name="${file.filename}" error="${file.error}" />`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `<file id="${file.fileId}" name="${file.filename}">
|
|
25
|
+
${file.content}
|
|
26
|
+
</file>`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format agent knowledge (files + knowledge bases) as unified XML prompt
|
|
31
|
+
*/
|
|
32
|
+
export const promptAgentKnowledge = ({
|
|
33
|
+
fileContents = [],
|
|
34
|
+
knowledgeBases = [],
|
|
35
|
+
}: PromptKnowledgeOptions) => {
|
|
36
|
+
const hasFiles = fileContents.length > 0;
|
|
37
|
+
const hasKnowledgeBases = knowledgeBases.length > 0;
|
|
38
|
+
|
|
39
|
+
// If no knowledge at all, return empty
|
|
40
|
+
if (!hasFiles && !hasKnowledgeBases) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const contentParts: string[] = [];
|
|
45
|
+
|
|
46
|
+
// Add instruction based on what's available
|
|
47
|
+
if (hasFiles && hasKnowledgeBases) {
|
|
48
|
+
contentParts.push(
|
|
49
|
+
'<instruction>The following files and knowledge bases are available. For files, refer to their content directly. For knowledge bases, use the searchKnowledgeBase tool to find relevant information.</instruction>',
|
|
50
|
+
);
|
|
51
|
+
} else if (hasFiles) {
|
|
52
|
+
contentParts.push(
|
|
53
|
+
'<instruction>The following files are available. Refer to their content directly to answer questions. No knowledge bases are associated.</instruction>',
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
contentParts.push(
|
|
57
|
+
'<instruction>The following knowledge bases are available for semantic search. Use the searchKnowledgeBase tool to find relevant information.</instruction>',
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add files section
|
|
62
|
+
if (hasFiles) {
|
|
63
|
+
const filesXml = fileContents.map((file) => formatFileContent(file)).join('\n');
|
|
64
|
+
contentParts.push(`<files totalCount="${fileContents.length}">
|
|
65
|
+
${filesXml}
|
|
66
|
+
</files>`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add knowledge bases section
|
|
70
|
+
if (hasKnowledgeBases) {
|
|
71
|
+
const kbItems = knowledgeBases
|
|
72
|
+
.map(
|
|
73
|
+
(kb) =>
|
|
74
|
+
`<knowledge_base id="${kb.id}" name="${kb.name}"${kb.description ? ` description="${kb.description}"` : ''} />`,
|
|
75
|
+
)
|
|
76
|
+
.join('\n');
|
|
77
|
+
contentParts.push(`<knowledge_bases totalCount="${knowledgeBases.length}">
|
|
78
|
+
${kbItems}
|
|
79
|
+
</knowledge_bases>`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return `<agent_knowledge>
|
|
83
|
+
${contentParts.join('\n')}
|
|
84
|
+
</agent_knowledge>`;
|
|
85
|
+
};
|