@yolk-sdk/knowledge 0.1.0-canary.13 → 0.1.0-canary.130
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/README.md +160 -80
- package/dist/agent.d.mts +118 -19
- package/dist/agent.d.mts.map +1 -1
- package/dist/agent.mjs +256 -34
- package/dist/agent.mjs.map +1 -1
- package/dist/chunking.d.mts +13 -16
- package/dist/chunking.d.mts.map +1 -1
- package/dist/chunking.mjs +4 -4
- package/dist/chunking.mjs.map +1 -1
- package/dist/context.d.mts +4 -13
- package/dist/context.d.mts.map +1 -1
- package/dist/context.mjs +17 -13
- package/dist/context.mjs.map +1 -1
- package/dist/documents.d.mts +128 -78
- package/dist/documents.d.mts.map +1 -1
- package/dist/documents.mjs +89 -61
- package/dist/documents.mjs.map +1 -1
- package/dist/embeddings.d.mts +4 -7
- package/dist/embeddings.d.mts.map +1 -1
- package/dist/embeddings.mjs +1 -2
- package/dist/embeddings.mjs.map +1 -1
- package/dist/errors.d.mts +19 -22
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs +12 -13
- package/dist/errors.mjs.map +1 -1
- package/dist/extraction.d.mts +4 -7
- package/dist/extraction.d.mts.map +1 -1
- package/dist/extraction.mjs +1 -2
- package/dist/extraction.mjs.map +1 -1
- package/dist/files.d.mts +19 -0
- package/dist/files.d.mts.map +1 -0
- package/dist/files.mjs +6 -0
- package/dist/files.mjs.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/ingestion.d.mts +21 -23
- package/dist/ingestion.d.mts.map +1 -1
- package/dist/ingestion.mjs +12 -17
- package/dist/ingestion.mjs.map +1 -1
- package/dist/search.d.mts +13 -17
- package/dist/search.d.mts.map +1 -1
- package/dist/search.mjs +9 -12
- package/dist/search.mjs.map +1 -1
- package/dist/store.d.mts +92 -29
- package/dist/store.d.mts.map +1 -1
- package/dist/store.mjs +3 -3
- package/dist/store.mjs.map +1 -1
- package/dist/summarization.d.mts +6 -9
- package/dist/summarization.d.mts.map +1 -1
- package/dist/summarization.mjs +1 -2
- package/dist/summarization.mjs.map +1 -1
- package/package.json +11 -41
- package/src/agent.ts +393 -65
- package/src/chunking.ts +27 -8
- package/src/context.ts +21 -16
- package/src/documents.ts +140 -71
- package/src/errors.ts +4 -4
- package/src/extraction.ts +4 -3
- package/src/files.ts +24 -0
- package/src/index.ts +6 -3
- package/src/ingestion.ts +46 -27
- package/src/search.ts +90 -62
- package/src/store.ts +139 -25
- package/src/summarization.ts +4 -3
- package/dist/artifacts.d.mts +0 -37
- package/dist/artifacts.d.mts.map +0 -1
- package/dist/artifacts.mjs +0 -28
- package/dist/artifacts.mjs.map +0 -1
- package/dist/links.d.mts +0 -17
- package/dist/links.d.mts.map +0 -1
- package/dist/links.mjs +0 -24
- package/dist/links.mjs.map +0 -1
- package/dist/provenance.d.mts +0 -20
- package/dist/provenance.d.mts.map +0 -1
- package/dist/provenance.mjs +0 -26
- package/dist/provenance.mjs.map +0 -1
- package/dist/records.d.mts +0 -59
- package/dist/records.d.mts.map +0 -1
- package/dist/records.mjs +0 -64
- package/dist/records.mjs.map +0 -1
- package/dist/representations.d.mts +0 -36
- package/dist/representations.d.mts.map +0 -1
- package/dist/representations.mjs +0 -44
- package/dist/representations.mjs.map +0 -1
- package/dist/search-store.d.mts +0 -77
- package/dist/search-store.d.mts.map +0 -1
- package/dist/search-store.mjs +0 -7
- package/dist/search-store.mjs.map +0 -1
- package/dist/vector-store.d.mts +0 -3
- package/dist/vector-store.mjs +0 -2
- package/src/artifacts.ts +0 -48
- package/src/links.ts +0 -23
- package/src/provenance.ts +0 -25
- package/src/records.ts +0 -76
- package/src/representations.ts +0 -51
- package/src/search-store.ts +0 -98
- package/src/vector-store.ts +0 -6
package/src/agent.ts
CHANGED
|
@@ -2,95 +2,423 @@ import { Effect } from 'effect'
|
|
|
2
2
|
import * as Schema from 'effect/Schema'
|
|
3
3
|
import { ToolError } from '@yolk-sdk/agent/loop'
|
|
4
4
|
import { ToolResult } from '@yolk-sdk/agent/protocol'
|
|
5
|
-
import { makeTool, type ToolRegistration } from '@yolk-sdk/agent/tools'
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import { makeTool, withToolArgumentsErrorHint, type ToolRegistration } from '@yolk-sdk/agent/tools'
|
|
6
|
+
import {
|
|
7
|
+
KnowledgeAvailabilitySchema,
|
|
8
|
+
KnowledgeDocumentSchema,
|
|
9
|
+
NonNegativeInteger,
|
|
10
|
+
NonEmptyTrimmedString,
|
|
11
|
+
PositiveInteger,
|
|
12
|
+
type KnowledgeAvailability,
|
|
13
|
+
type KnowledgeDocument
|
|
14
|
+
} from './documents.ts'
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
const OptionalNonEmptyTrimmedString = Schema.optional(Schema.NullOr(NonEmptyTrimmedString))
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
const OptionalPositiveInteger = Schema.optional(Schema.NullOr(PositiveInteger))
|
|
19
|
+
|
|
20
|
+
const OptionalNonNegativeInteger = Schema.optional(Schema.NullOr(NonNegativeInteger))
|
|
21
|
+
|
|
22
|
+
const OptionalNumber = Schema.optional(Schema.NullOr(Schema.Number))
|
|
23
|
+
|
|
24
|
+
const KnowledgeTargetParamsSchema = Schema.Struct({
|
|
25
|
+
slug: NonEmptyTrimmedString,
|
|
26
|
+
scope: OptionalNonEmptyTrimmedString
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const KnowledgeLookupParams = Schema.Union([
|
|
30
|
+
Schema.Struct({
|
|
31
|
+
operation: Schema.Literal('search'),
|
|
32
|
+
query: NonEmptyTrimmedString,
|
|
33
|
+
limit: OptionalPositiveInteger,
|
|
34
|
+
minScore: OptionalNumber,
|
|
35
|
+
contextChunks: OptionalNonNegativeInteger
|
|
36
|
+
}),
|
|
37
|
+
Schema.Struct({
|
|
38
|
+
operation: Schema.Literal('get'),
|
|
39
|
+
id: OptionalNonEmptyTrimmedString,
|
|
40
|
+
target: Schema.optional(Schema.NullOr(KnowledgeTargetParamsSchema))
|
|
41
|
+
})
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
const KnowledgeManageParams = Schema.Union([
|
|
45
|
+
Schema.Struct({
|
|
46
|
+
operation: Schema.Literal('upsert'),
|
|
47
|
+
target: KnowledgeTargetParamsSchema,
|
|
48
|
+
title: NonEmptyTrimmedString,
|
|
49
|
+
purpose: NonEmptyTrimmedString,
|
|
50
|
+
origin: NonEmptyTrimmedString,
|
|
51
|
+
content: NonEmptyTrimmedString,
|
|
52
|
+
availability: KnowledgeAvailabilitySchema
|
|
53
|
+
}),
|
|
54
|
+
Schema.Struct({
|
|
55
|
+
operation: Schema.Literal('set_availability'),
|
|
56
|
+
target: KnowledgeTargetParamsSchema,
|
|
57
|
+
availability: KnowledgeAvailabilitySchema
|
|
58
|
+
}),
|
|
59
|
+
Schema.Struct({
|
|
60
|
+
operation: Schema.Literal('rename_slug'),
|
|
61
|
+
target: KnowledgeTargetParamsSchema,
|
|
62
|
+
nextSlug: NonEmptyTrimmedString
|
|
63
|
+
}),
|
|
64
|
+
Schema.Struct({
|
|
65
|
+
operation: Schema.Literal('delete'),
|
|
66
|
+
target: KnowledgeTargetParamsSchema
|
|
67
|
+
})
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
const KnowledgeLookupResultSchema = Schema.Struct({
|
|
71
|
+
document: KnowledgeDocumentSchema,
|
|
72
|
+
score: Schema.optionalKey(Schema.Number),
|
|
73
|
+
context: Schema.optionalKey(Schema.Array(Schema.Struct({ content: Schema.String })))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
/** Declared output of `makeKnowledgeLookupTool`; `structuredContent` is its JSON encoding
|
|
77
|
+
* (`Schema.toCodecJson`: document dates become ISO strings). */
|
|
78
|
+
export const KnowledgeLookupOutput = Schema.Union([
|
|
79
|
+
Schema.Struct({
|
|
80
|
+
operation: Schema.Literal('search'),
|
|
81
|
+
results: Schema.Array(KnowledgeLookupResultSchema)
|
|
82
|
+
}),
|
|
83
|
+
Schema.Struct({
|
|
84
|
+
operation: Schema.Literal('get'),
|
|
85
|
+
document: KnowledgeDocumentSchema
|
|
86
|
+
})
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
export type KnowledgeLookupOutput = typeof KnowledgeLookupOutput.Type
|
|
90
|
+
|
|
91
|
+
/** Declared output of `makeKnowledgeManageTool`; `structuredContent` is its JSON encoding. */
|
|
92
|
+
export const KnowledgeManageOutput = Schema.Struct({
|
|
93
|
+
operation: Schema.Literals(['upsert', 'set_availability', 'rename_slug', 'delete']),
|
|
94
|
+
document: Schema.Struct({ id: Schema.String, slug: Schema.String, title: Schema.String })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
export type KnowledgeManageOutput = typeof KnowledgeManageOutput.Type
|
|
98
|
+
|
|
99
|
+
const encodeLookupOutput = Schema.encodeEffect(Schema.toCodecJson(KnowledgeLookupOutput))
|
|
100
|
+
|
|
101
|
+
const encodeManageOutput = Schema.encodeEffect(Schema.toCodecJson(KnowledgeManageOutput))
|
|
102
|
+
|
|
103
|
+
/** A handler result that does not encode to the declared output is a host bug, not a model
|
|
104
|
+
* mistake: fail with a safe message (no values). */
|
|
105
|
+
const outputError = (tool: string) => () =>
|
|
106
|
+
new ToolError({
|
|
107
|
+
tool,
|
|
108
|
+
message: `${tool} produced a result that does not match its output schema.`,
|
|
109
|
+
cause: 'execution'
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
type KnowledgeTargetParams = typeof KnowledgeTargetParamsSchema.Type
|
|
113
|
+
|
|
114
|
+
type KnowledgeLookupParams = typeof KnowledgeLookupParams.Type
|
|
115
|
+
|
|
116
|
+
type KnowledgeManageParams = typeof KnowledgeManageParams.Type
|
|
117
|
+
|
|
118
|
+
export type KnowledgeTarget = {
|
|
119
|
+
readonly slug: string
|
|
120
|
+
readonly scope?: string
|
|
15
121
|
}
|
|
16
122
|
|
|
17
|
-
export
|
|
18
|
-
|
|
123
|
+
export type KnowledgeLookupResult = {
|
|
124
|
+
readonly document: KnowledgeDocument
|
|
125
|
+
readonly score?: number
|
|
126
|
+
readonly context?: ReadonlyArray<{ readonly content: string }>
|
|
19
127
|
}
|
|
20
128
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
129
|
+
export type KnowledgeSavedDocument = {
|
|
130
|
+
readonly id: string
|
|
131
|
+
readonly slug: string
|
|
132
|
+
readonly title: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type KnowledgeLookupHandlers<Context> = {
|
|
136
|
+
readonly search: (input: {
|
|
137
|
+
readonly context: Context
|
|
138
|
+
readonly query: string
|
|
139
|
+
readonly limit?: number
|
|
140
|
+
readonly minScore?: number
|
|
141
|
+
readonly contextChunks?: number
|
|
142
|
+
}) => Effect.Effect<ReadonlyArray<KnowledgeLookupResult>, ToolError>
|
|
143
|
+
readonly get: (input: {
|
|
144
|
+
readonly context: Context
|
|
145
|
+
readonly id?: string
|
|
146
|
+
readonly target?: KnowledgeTarget
|
|
147
|
+
}) => Effect.Effect<KnowledgeDocument, ToolError>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type KnowledgeManageHandlers<Context> = {
|
|
151
|
+
readonly upsert: (input: {
|
|
152
|
+
readonly context: Context
|
|
153
|
+
readonly target: KnowledgeTarget
|
|
154
|
+
readonly title: string
|
|
155
|
+
readonly purpose: string
|
|
156
|
+
readonly origin: string
|
|
157
|
+
readonly content: string
|
|
158
|
+
readonly availability: KnowledgeAvailability
|
|
159
|
+
}) => Effect.Effect<KnowledgeSavedDocument, ToolError>
|
|
160
|
+
readonly setAvailability: (input: {
|
|
161
|
+
readonly context: Context
|
|
162
|
+
readonly target: KnowledgeTarget
|
|
163
|
+
readonly availability: KnowledgeAvailability
|
|
164
|
+
}) => Effect.Effect<KnowledgeSavedDocument, ToolError>
|
|
165
|
+
readonly renameSlug: (input: {
|
|
166
|
+
readonly context: Context
|
|
167
|
+
readonly target: KnowledgeTarget
|
|
168
|
+
readonly nextSlug: string
|
|
169
|
+
}) => Effect.Effect<KnowledgeSavedDocument, ToolError>
|
|
170
|
+
readonly delete: (input: {
|
|
171
|
+
readonly context: Context
|
|
172
|
+
readonly target: KnowledgeTarget
|
|
173
|
+
}) => Effect.Effect<KnowledgeSavedDocument, ToolError>
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const optionalValue = <Value>(value: Value | null | undefined) => value ?? undefined
|
|
177
|
+
|
|
178
|
+
const targetFromParams = (target: KnowledgeTargetParams): KnowledgeTarget =>
|
|
179
|
+
target.scope === null || target.scope === undefined
|
|
180
|
+
? { slug: target.slug }
|
|
181
|
+
: { slug: target.slug, scope: target.scope }
|
|
27
182
|
|
|
28
|
-
const
|
|
183
|
+
const optionalTargetFromParams = (target: KnowledgeTargetParams | null | undefined) =>
|
|
184
|
+
target === null || target === undefined ? undefined : targetFromParams(target)
|
|
29
185
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
186
|
+
const formatDocument = (document: KnowledgeDocument) =>
|
|
187
|
+
[
|
|
188
|
+
`# ${document.title}`,
|
|
189
|
+
`document_id: ${document.id}`,
|
|
190
|
+
`slug: ${document.slug}`,
|
|
191
|
+
`purpose: ${document.purpose}`,
|
|
192
|
+
`origin: ${document.origin}`,
|
|
193
|
+
`availability: ${document.availability}`,
|
|
194
|
+
`status: ${document.status}`,
|
|
195
|
+
'',
|
|
196
|
+
document.content
|
|
197
|
+
].join('\n')
|
|
33
198
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
199
|
+
const formatSearchResults = (results: ReadonlyArray<KnowledgeLookupResult>) =>
|
|
200
|
+
results.length === 0
|
|
201
|
+
? 'No knowledge matches found.'
|
|
202
|
+
: results
|
|
203
|
+
.map(result =>
|
|
204
|
+
[
|
|
205
|
+
`## ${result.document.title}`,
|
|
206
|
+
`document_id: ${result.document.id}`,
|
|
207
|
+
`slug: ${result.document.slug}`,
|
|
208
|
+
`purpose: ${result.document.purpose}`,
|
|
209
|
+
`origin: ${result.document.origin}`,
|
|
210
|
+
`availability: ${result.document.availability}`,
|
|
211
|
+
result.score === undefined ? undefined : `score: ${result.score}`,
|
|
212
|
+
'',
|
|
213
|
+
result.context?.map(chunk => chunk.content).join('\n\n') ?? result.document.content
|
|
214
|
+
]
|
|
215
|
+
.filter(line => line !== undefined)
|
|
216
|
+
.join('\n')
|
|
217
|
+
)
|
|
218
|
+
.join('\n\n')
|
|
219
|
+
|
|
220
|
+
const formatSaved = (verb: string, document: KnowledgeSavedDocument) =>
|
|
221
|
+
[verb, `title: ${document.title}`, `slug: ${document.slug}`, `document_id: ${document.id}`].join(
|
|
222
|
+
'\n'
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
type KnowledgeLookupResultOutput = {
|
|
226
|
+
readonly document: KnowledgeDocument
|
|
227
|
+
score?: number
|
|
228
|
+
context?: ReadonlyArray<{ readonly content: string }>
|
|
41
229
|
}
|
|
42
230
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
231
|
+
const lookupResultOutput = (result: KnowledgeLookupResult) => {
|
|
232
|
+
const output: KnowledgeLookupResultOutput = { document: result.document }
|
|
233
|
+
|
|
234
|
+
if (result.score !== undefined) output.score = result.score
|
|
235
|
+
|
|
236
|
+
if (result.context !== undefined) {
|
|
237
|
+
output.context = result.context.map(chunk => ({ content: chunk.content }))
|
|
46
238
|
}
|
|
47
239
|
|
|
48
|
-
return
|
|
240
|
+
return output
|
|
49
241
|
}
|
|
50
242
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
243
|
+
const savedResult = (input: {
|
|
244
|
+
readonly name: string
|
|
245
|
+
readonly callId: string
|
|
246
|
+
readonly verb: string
|
|
247
|
+
readonly operation: KnowledgeManageOutput['operation']
|
|
248
|
+
readonly document: KnowledgeSavedDocument
|
|
249
|
+
}) =>
|
|
250
|
+
encodeManageOutput({
|
|
251
|
+
operation: input.operation,
|
|
252
|
+
document: { id: input.document.id, slug: input.document.slug, title: input.document.title }
|
|
253
|
+
}).pipe(
|
|
254
|
+
Effect.mapError(outputError(input.name)),
|
|
255
|
+
Effect.map(structuredContent =>
|
|
256
|
+
ToolResult.make({
|
|
257
|
+
toolCallId: input.callId,
|
|
258
|
+
content: formatSaved(input.verb, input.document),
|
|
259
|
+
structuredContent
|
|
260
|
+
})
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
export const makeKnowledgeLookupTool = <Context>(
|
|
265
|
+
handlers: KnowledgeLookupHandlers<Context>,
|
|
266
|
+
options: { readonly name?: string; readonly description?: string } = {}
|
|
54
267
|
): ToolRegistration<Context> => {
|
|
55
|
-
const name = options.name ?? '
|
|
268
|
+
const name = options.name ?? 'knowledge_lookup'
|
|
56
269
|
|
|
57
270
|
return makeTool({
|
|
58
271
|
name,
|
|
59
|
-
description:
|
|
60
|
-
|
|
272
|
+
description:
|
|
273
|
+
options.description ??
|
|
274
|
+
'Look up durable knowledge. Search for semantic discovery; get when document_id or slug is known.',
|
|
275
|
+
parameters: KnowledgeLookupParams,
|
|
276
|
+
output: KnowledgeLookupOutput,
|
|
61
277
|
access: 'read',
|
|
62
|
-
invalidParamsMessage: error =>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
278
|
+
invalidParamsMessage: error =>
|
|
279
|
+
withToolArgumentsErrorHint(
|
|
280
|
+
`Invalid knowledge lookup arguments: ${error instanceof Error ? error.message : String(error)}`,
|
|
281
|
+
error
|
|
282
|
+
),
|
|
283
|
+
execute: ({ call, context, params }) =>
|
|
284
|
+
Effect.gen(function* () {
|
|
285
|
+
if (params.operation === 'search') {
|
|
286
|
+
const results = yield* handlers.search({
|
|
287
|
+
context,
|
|
288
|
+
query: params.query,
|
|
289
|
+
limit: optionalValue(params.limit),
|
|
290
|
+
minScore: optionalValue(params.minScore),
|
|
291
|
+
contextChunks: optionalValue(params.contextChunks)
|
|
72
292
|
})
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
293
|
+
|
|
294
|
+
const structuredContent = yield* encodeLookupOutput({
|
|
295
|
+
operation: 'search',
|
|
296
|
+
results: results.map(lookupResultOutput)
|
|
297
|
+
}).pipe(Effect.mapError(outputError(name)))
|
|
298
|
+
|
|
299
|
+
return ToolResult.make({
|
|
300
|
+
toolCallId: call.id,
|
|
301
|
+
content: formatSearchResults(results),
|
|
302
|
+
structuredContent
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const document = yield* handlers.get({
|
|
307
|
+
context,
|
|
308
|
+
id: optionalValue(params.id),
|
|
309
|
+
target: optionalTargetFromParams(params.target)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const structuredContent = yield* encodeLookupOutput({ operation: 'get', document }).pipe(
|
|
313
|
+
Effect.mapError(outputError(name))
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return ToolResult.make({
|
|
317
|
+
toolCallId: call.id,
|
|
318
|
+
content: formatDocument(document),
|
|
319
|
+
structuredContent
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const makeKnowledgeManageTool = <Context>(
|
|
326
|
+
handlers: KnowledgeManageHandlers<Context>,
|
|
327
|
+
options: { readonly name?: string; readonly description?: string } = {}
|
|
328
|
+
): ToolRegistration<Context> => {
|
|
329
|
+
const name = options.name ?? 'knowledge_manage'
|
|
330
|
+
|
|
331
|
+
return makeTool({
|
|
332
|
+
name,
|
|
333
|
+
description:
|
|
334
|
+
options.description ??
|
|
335
|
+
'Create, replace, pin, archive, rename, or delete durable knowledge. Use only when explicitly asked.',
|
|
336
|
+
parameters: KnowledgeManageParams,
|
|
337
|
+
output: KnowledgeManageOutput,
|
|
338
|
+
access: 'write',
|
|
339
|
+
invalidParamsMessage: error =>
|
|
340
|
+
withToolArgumentsErrorHint(
|
|
341
|
+
`Invalid knowledge manage arguments: ${error instanceof Error ? error.message : String(error)}`,
|
|
342
|
+
error
|
|
343
|
+
),
|
|
344
|
+
execute: ({ call, context, params }) =>
|
|
345
|
+
Effect.gen(function* () {
|
|
346
|
+
switch (params.operation) {
|
|
347
|
+
case 'upsert': {
|
|
348
|
+
const document = yield* handlers.upsert({
|
|
349
|
+
context,
|
|
350
|
+
target: targetFromParams(params.target),
|
|
351
|
+
title: params.title,
|
|
352
|
+
purpose: params.purpose,
|
|
353
|
+
origin: params.origin,
|
|
354
|
+
content: params.content,
|
|
355
|
+
availability: params.availability
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
return yield* savedResult({
|
|
359
|
+
name,
|
|
360
|
+
callId: call.id,
|
|
361
|
+
verb: 'Knowledge upserted',
|
|
362
|
+
operation: 'upsert',
|
|
363
|
+
document
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
case 'set_availability': {
|
|
368
|
+
const document = yield* handlers.setAvailability({
|
|
369
|
+
context,
|
|
370
|
+
target: targetFromParams(params.target),
|
|
371
|
+
availability: params.availability
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
return yield* savedResult({
|
|
375
|
+
name,
|
|
376
|
+
callId: call.id,
|
|
377
|
+
verb: 'Knowledge availability updated',
|
|
378
|
+
operation: 'set_availability',
|
|
379
|
+
document
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
case 'rename_slug': {
|
|
384
|
+
const document = yield* handlers.renameSlug({
|
|
385
|
+
context,
|
|
386
|
+
target: targetFromParams(params.target),
|
|
387
|
+
nextSlug: params.nextSlug
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
return yield* savedResult({
|
|
391
|
+
name,
|
|
392
|
+
callId: call.id,
|
|
393
|
+
verb: 'Knowledge slug renamed',
|
|
394
|
+
operation: 'rename_slug',
|
|
395
|
+
document
|
|
81
396
|
})
|
|
82
|
-
),
|
|
83
|
-
Effect.mapError(error => {
|
|
84
|
-
if (isToolError(error)) {
|
|
85
|
-
return error
|
|
86
397
|
}
|
|
87
398
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
399
|
+
case 'delete': {
|
|
400
|
+
const document = yield* handlers.delete({
|
|
401
|
+
context,
|
|
402
|
+
target: targetFromParams(params.target)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
return yield* savedResult({
|
|
406
|
+
name,
|
|
407
|
+
callId: call.id,
|
|
408
|
+
verb: 'Knowledge deleted',
|
|
409
|
+
operation: 'delete',
|
|
410
|
+
document
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return yield* Effect.fail(
|
|
416
|
+
new ToolError({
|
|
417
|
+
tool: call.name,
|
|
418
|
+
message: 'Unsupported knowledge operation',
|
|
419
|
+
cause: 'validation'
|
|
92
420
|
})
|
|
93
|
-
|
|
94
|
-
)
|
|
421
|
+
)
|
|
422
|
+
})
|
|
95
423
|
})
|
|
96
424
|
}
|
package/src/chunking.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from 'effect'
|
|
2
2
|
import { countTokens, decode, encode } from 'gpt-tokenizer/encoding/o200k_base'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
KnowledgeChunk,
|
|
5
|
+
KnowledgeDocumentId,
|
|
6
|
+
KnowledgeMetadata,
|
|
7
|
+
KnowledgeScopeId
|
|
8
|
+
} from './documents.ts'
|
|
4
9
|
import { KnowledgeChunkingError } from './errors.ts'
|
|
5
10
|
|
|
6
11
|
const SENTENCE_PATTERN = /[^.!?]+[.!?]+(?:["')\]]+)?\s*|[^.!?]+$/g
|
|
12
|
+
|
|
7
13
|
const PARAGRAPH_BREAK_PATTERN = /(\n{2,})/
|
|
14
|
+
|
|
8
15
|
const WORD_PATTERN = /\S+\s*/g
|
|
9
16
|
|
|
10
17
|
export type ChunkKnowledgeDocumentInput = {
|
|
11
|
-
readonly
|
|
12
|
-
readonly documentId:
|
|
18
|
+
readonly scopeId: KnowledgeScopeId
|
|
19
|
+
readonly documentId: KnowledgeDocumentId
|
|
13
20
|
readonly content: string
|
|
14
21
|
readonly maxTokens?: number
|
|
15
22
|
readonly metadata?: KnowledgeMetadata
|
|
@@ -52,6 +59,7 @@ const splitSentences = (text: string) =>
|
|
|
52
59
|
|
|
53
60
|
const splitEncodedTokens = (text: string, maxTokens: number) => {
|
|
54
61
|
const tokens = encode(text)
|
|
62
|
+
|
|
55
63
|
return Array.from({ length: Math.ceil(tokens.length / maxTokens) }, (_, index) =>
|
|
56
64
|
decode(tokens.slice(index * maxTokens, index * maxTokens + maxTokens)).trim()
|
|
57
65
|
).filter(chunk => chunk.length > 0)
|
|
@@ -68,6 +76,7 @@ const splitOversizedUnit = (text: string, maxTokens: number) => {
|
|
|
68
76
|
|
|
69
77
|
if (wordTokenCount > maxTokens) {
|
|
70
78
|
const currentChunk = current.trim()
|
|
79
|
+
|
|
71
80
|
if (currentChunk.length > 0) {
|
|
72
81
|
chunks.push(currentChunk)
|
|
73
82
|
}
|
|
@@ -80,6 +89,7 @@ const splitOversizedUnit = (text: string, maxTokens: number) => {
|
|
|
80
89
|
|
|
81
90
|
if (currentTokenCount > 0 && currentTokenCount + wordTokenCount > maxTokens) {
|
|
82
91
|
const currentChunk = current.trim()
|
|
92
|
+
|
|
83
93
|
if (currentChunk.length > 0) {
|
|
84
94
|
chunks.push(currentChunk)
|
|
85
95
|
}
|
|
@@ -94,6 +104,7 @@ const splitOversizedUnit = (text: string, maxTokens: number) => {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
const finalChunk = current.trim()
|
|
107
|
+
|
|
97
108
|
if (finalChunk.length > 0) {
|
|
98
109
|
chunks.push(finalChunk)
|
|
99
110
|
}
|
|
@@ -113,6 +124,7 @@ const buildChunkContents = (units: ReadonlyArray<string>, maxTokens: number) =>
|
|
|
113
124
|
|
|
114
125
|
const pushCurrent = () => {
|
|
115
126
|
const content = current.trim()
|
|
127
|
+
|
|
116
128
|
if (content.length > 0) {
|
|
117
129
|
chunks.push(content)
|
|
118
130
|
}
|
|
@@ -133,6 +145,7 @@ const buildChunkContents = (units: ReadonlyArray<string>, maxTokens: number) =>
|
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
pushCurrent()
|
|
148
|
+
|
|
136
149
|
return chunks
|
|
137
150
|
}
|
|
138
151
|
|
|
@@ -145,8 +158,11 @@ export const chunkKnowledgeText = (input: ChunkKnowledgeDocumentInput, maxTokens
|
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
const sanitized = sanitizeText(input.content)
|
|
161
|
+
|
|
148
162
|
if (sanitized.length === 0) {
|
|
149
|
-
return yield* Effect.fail(
|
|
163
|
+
return yield* Effect.fail(
|
|
164
|
+
new KnowledgeChunkingError({ message: 'Cannot chunk empty content' })
|
|
165
|
+
)
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
const units = splitUnitsBySize(splitSentences(sanitized), maxTokens)
|
|
@@ -158,7 +174,7 @@ export const chunkKnowledgeText = (input: ChunkKnowledgeDocumentInput, maxTokens
|
|
|
158
174
|
|
|
159
175
|
return contents.map((content, position) => ({
|
|
160
176
|
id: `${input.documentId}:chunk:${position}`,
|
|
161
|
-
|
|
177
|
+
scopeId: input.scopeId,
|
|
162
178
|
documentId: input.documentId,
|
|
163
179
|
content,
|
|
164
180
|
position,
|
|
@@ -167,9 +183,12 @@ export const chunkKnowledgeText = (input: ChunkKnowledgeDocumentInput, maxTokens
|
|
|
167
183
|
})) satisfies ReadonlyArray<KnowledgeChunk>
|
|
168
184
|
})
|
|
169
185
|
|
|
170
|
-
export const makeDefaultKnowledgeChunker = (input: {
|
|
186
|
+
export const makeDefaultKnowledgeChunker = (input: {
|
|
187
|
+
readonly maxTokens: number
|
|
188
|
+
}): KnowledgeChunkerApi => ({
|
|
171
189
|
chunk: document => chunkKnowledgeText(document, document.maxTokens ?? input.maxTokens)
|
|
172
190
|
})
|
|
173
191
|
|
|
174
|
-
export const DefaultKnowledgeChunkerLive = (
|
|
175
|
-
|
|
192
|
+
export const DefaultKnowledgeChunkerLive = (
|
|
193
|
+
input: { readonly maxTokens: number } = { maxTokens: 512 }
|
|
194
|
+
) => Layer.succeed(KnowledgeChunker, makeDefaultKnowledgeChunker(input))
|
package/src/context.ts
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import { Array as Arr } from 'effect'
|
|
2
|
-
import type {
|
|
3
|
-
import type { KnowledgeRepresentation } from './representations.ts'
|
|
4
|
-
|
|
5
|
-
export type KnowledgeContextItem = {
|
|
6
|
-
readonly record: KnowledgeRecord
|
|
7
|
-
readonly representation?: KnowledgeRepresentation
|
|
8
|
-
}
|
|
2
|
+
import type { KnowledgeDocument } from './documents.ts'
|
|
9
3
|
|
|
10
4
|
export type BuildKnowledgeContextInput = {
|
|
11
|
-
readonly
|
|
5
|
+
readonly documents: ReadonlyArray<KnowledgeDocument>
|
|
12
6
|
readonly maxCharacters: number
|
|
13
7
|
}
|
|
14
8
|
|
|
15
9
|
const truncationMarker = '\n\n[truncated: pinned knowledge exceeded context budget]'
|
|
16
10
|
|
|
17
|
-
const
|
|
18
|
-
|
|
11
|
+
const documentHeader = (document: KnowledgeDocument) =>
|
|
12
|
+
[
|
|
13
|
+
`## ${document.title}`,
|
|
14
|
+
`slug: ${document.slug}`,
|
|
15
|
+
`purpose: ${document.purpose}`,
|
|
16
|
+
`origin: ${document.origin}`,
|
|
17
|
+
`status: ${document.status}`,
|
|
18
|
+
`availability: ${document.availability}`,
|
|
19
|
+
document.reviewedAt === undefined ? undefined : `reviewed_at: ${document.reviewedAt.toString()}`
|
|
20
|
+
]
|
|
21
|
+
.filter(line => line !== undefined && line.trim().length > 0)
|
|
22
|
+
.join('\n')
|
|
19
23
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
return [recordHeader(item.record), item.record.summary, representationText]
|
|
24
|
+
const documentBody = (document: KnowledgeDocument) =>
|
|
25
|
+
[documentHeader(document), document.summary, document.content]
|
|
23
26
|
.filter(section => section !== undefined && section.trim().length > 0)
|
|
24
27
|
.join('\n')
|
|
25
|
-
}
|
|
26
28
|
|
|
27
29
|
export const buildKnowledgeContext = (input: BuildKnowledgeContextInput) => {
|
|
28
|
-
const sections = Arr.map(input.
|
|
29
|
-
|
|
30
|
+
const sections = Arr.map(input.documents, documentBody).filter(
|
|
31
|
+
section => section.trim().length > 0
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const header = '# Pinned knowledge\nUse this durable knowledge as high-priority context.'
|
|
30
35
|
const body = sections.join('\n\n')
|
|
31
36
|
const context = body.length === 0 ? '' : `${header}\n\n${body}`
|
|
32
37
|
|