@owlmeans/agent 0.1.18-rc.47 → 0.1.18-rc.49
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 +4 -4
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/agent/SKILL.md +9 -9
- package/build/helpers/compaction/types.d.ts +49 -0
- package/build/helpers/compaction/types.d.ts.map +1 -0
- package/build/helpers/compaction/types.js.map +1 -0
- package/build/helpers/compaction.d.ts +6 -20
- package/build/helpers/compaction.d.ts.map +1 -1
- package/build/helpers/compaction.js +72 -81
- package/build/helpers/compaction.js.map +1 -1
- package/build/helpers/index.d.ts +3 -1
- package/build/helpers/index.d.ts.map +1 -1
- package/build/helpers/index.js.map +1 -1
- package/build/helpers/rolling/types.d.ts +16 -0
- package/build/helpers/rolling/types.d.ts.map +1 -0
- package/build/helpers/rolling/types.js +2 -0
- package/build/helpers/rolling/types.js.map +1 -0
- package/build/helpers/rolling.d.ts +1 -1
- package/build/helpers/rolling.d.ts.map +1 -1
- package/build/helpers/tools/types.d.ts +38 -0
- package/build/helpers/tools/types.d.ts.map +1 -0
- package/build/helpers/tools/types.js +2 -0
- package/build/helpers/{types.js.map → tools/types.js.map} +1 -1
- package/build/helpers/tools.d.ts +6 -26
- package/build/helpers/tools.d.ts.map +1 -1
- package/build/helpers/tools.js +27 -42
- package/build/helpers/tools.js.map +1 -1
- package/build/model.d.ts.map +1 -1
- package/build/model.js +4 -4
- package/build/model.js.map +1 -1
- package/build/pipeline/index.d.ts +2 -1
- package/build/pipeline/index.d.ts.map +1 -1
- package/build/pipeline/results/types.d.ts +139 -0
- package/build/pipeline/results/types.d.ts.map +1 -0
- package/build/pipeline/results/types.js +2 -0
- package/build/pipeline/results/types.js.map +1 -0
- package/build/pipeline/{types.local.d.ts → results/types.local.d.ts} +0 -18
- package/build/pipeline/results/types.local.d.ts.map +1 -0
- package/build/pipeline/results/types.local.js.map +1 -0
- package/build/pipeline/results.d.ts +2 -1
- package/build/pipeline/results.d.ts.map +1 -1
- package/build/pipeline/results.js +22 -22
- package/build/pipeline/results.js.map +1 -1
- package/build/pipeline/runner/types.d.ts +293 -0
- package/build/pipeline/runner/types.d.ts.map +1 -0
- package/build/pipeline/runner/types.js +2 -0
- package/build/pipeline/runner/types.js.map +1 -0
- package/build/pipeline/runner/types.local.d.ts +19 -0
- package/build/pipeline/runner/types.local.d.ts.map +1 -0
- package/build/pipeline/runner/types.local.js +2 -0
- package/build/pipeline/runner/types.local.js.map +1 -0
- package/build/pipeline/runner.d.ts +1 -1
- package/build/pipeline/runner.d.ts.map +1 -1
- package/build/pipeline/runner.js +10 -9
- package/build/pipeline/runner.js.map +1 -1
- package/build/pipeline/types.d.ts +2 -93
- package/build/pipeline/types.d.ts.map +1 -1
- package/build/plugins/export.d.ts +2 -2
- package/build/plugins/export.d.ts.map +1 -1
- package/build/plugins/export.js +2 -2
- package/build/plugins/export.js.map +1 -1
- package/build/plugins/inquiry.d.ts.map +1 -1
- package/build/plugins/inquiry.js +4 -4
- package/build/plugins/inquiry.js.map +1 -1
- package/build/plugins/memory-events.d.ts +3 -1
- package/build/plugins/memory-events.d.ts.map +1 -1
- package/build/plugins/memory-events.js +4 -2
- package/build/plugins/memory-events.js.map +1 -1
- package/build/plugins/memory-graph.d.ts +5 -1
- package/build/plugins/memory-graph.d.ts.map +1 -1
- package/build/plugins/memory-graph.js +4 -2
- package/build/plugins/memory-graph.js.map +1 -1
- package/build/plugins/summarize.d.ts.map +1 -1
- package/build/plugins/summarize.js +2 -2
- package/build/plugins/summarize.js.map +1 -1
- package/build/types.d.ts +5 -3
- package/build/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/helpers/compaction/types.ts +51 -0
- package/src/helpers/compaction.ts +98 -101
- package/src/helpers/index.ts +3 -1
- package/src/helpers/rolling/types.ts +16 -0
- package/src/helpers/rolling.ts +1 -1
- package/src/helpers/tools/types.ts +37 -0
- package/src/helpers/tools.ts +39 -47
- package/src/model.ts +4 -4
- package/src/pipeline/index.ts +2 -1
- package/src/pipeline/{types.local.ts → results/types.local.ts} +0 -21
- package/src/pipeline/results/types.ts +152 -0
- package/src/pipeline/results.ts +25 -24
- package/src/pipeline/runner/types.local.ts +21 -0
- package/src/pipeline/{types.ts → runner/types.ts} +4 -156
- package/src/pipeline/runner.ts +14 -12
- package/src/plugins/export.ts +2 -2
- package/src/plugins/inquiry.ts +4 -4
- package/src/plugins/memory-events.ts +8 -2
- package/src/plugins/memory-graph.ts +8 -2
- package/src/plugins/summarize.ts +2 -2
- package/src/types.ts +4 -4
- package/tests/inquiry.spec.ts +14 -15
- package/tests/pipeline-inquiry.spec.ts +3 -3
- package/tests/plugins.spec.ts +10 -10
- package/tests/summary.spec.ts +8 -8
- package/tests/tools.spec.ts +11 -11
- package/build/helpers/types.d.ts +0 -42
- package/build/helpers/types.d.ts.map +0 -1
- package/build/pipeline/types.local.d.ts.map +0 -1
- package/build/pipeline/types.local.js.map +0 -1
- package/src/helpers/types.ts +0 -43
- /package/build/helpers/{types.js → compaction/types.js} +0 -0
- /package/build/pipeline/{types.local.js → results/types.local.js} +0 -0
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAA;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACpF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AACxF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC7H,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/agent",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.49",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -68,14 +68,14 @@
|
|
|
68
68
|
"@langchain/langgraph-checkpoint": "^1.1.5"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@owlmeans/agent-common": "^0.1.18-rc.
|
|
72
|
-
"@owlmeans/basic-ids": "^0.1.18-rc.
|
|
73
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
74
|
-
"@owlmeans/error": "^0.1.18-rc.
|
|
75
|
-
"@owlmeans/flow": "^0.1.18-rc.
|
|
76
|
-
"@owlmeans/llm": "^0.1.18-rc.
|
|
77
|
-
"@owlmeans/llm-common": "^0.1.18-rc.
|
|
78
|
-
"@owlmeans/log": "^0.1.18-rc.
|
|
71
|
+
"@owlmeans/agent-common": "^0.1.18-rc.48",
|
|
72
|
+
"@owlmeans/basic-ids": "^0.1.18-rc.38",
|
|
73
|
+
"@owlmeans/context": "^0.1.18-rc.34",
|
|
74
|
+
"@owlmeans/error": "^0.1.18-rc.38",
|
|
75
|
+
"@owlmeans/flow": "^0.1.18-rc.45",
|
|
76
|
+
"@owlmeans/llm": "^0.1.18-rc.46",
|
|
77
|
+
"@owlmeans/llm-common": "^0.1.18-rc.39",
|
|
78
|
+
"@owlmeans/log": "^0.1.18-rc.2",
|
|
79
79
|
"ajv": "^8.20.0"
|
|
80
80
|
},
|
|
81
81
|
"publishConfig": {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LlmModel } from '@owlmeans/llm'
|
|
2
|
+
import type { BaseMessage } from '@langchain/core/messages'
|
|
3
|
+
import { AgentRunStatus } from '@owlmeans/agent-common'
|
|
4
|
+
|
|
5
|
+
export interface Compaction {
|
|
6
|
+
summary: string
|
|
7
|
+
advice?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CompactionInput {
|
|
11
|
+
/** Omit to skip the model entirely and take the deterministic path. */
|
|
12
|
+
model?: LlmModel
|
|
13
|
+
/** The ask that opened the run. */
|
|
14
|
+
prompt: string
|
|
15
|
+
messages: readonly BaseMessage[]
|
|
16
|
+
status: AgentRunStatus
|
|
17
|
+
/** What happened after the loop — a validation verdict, a build result. */
|
|
18
|
+
note?: string
|
|
19
|
+
maxSummaryChars?: number
|
|
20
|
+
maxAdviceChars?: number
|
|
21
|
+
/** LangChain `runName`. Give it a value the application filters, or the summary of a run streams into the user's view of that run. */
|
|
22
|
+
action?: string
|
|
23
|
+
/** How much of the transcript to show the model. */
|
|
24
|
+
maxTranscriptChars?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The handover between two runs on one subject: a summary and the advice that carries intent. */
|
|
28
|
+
export interface CompactionHelper {
|
|
29
|
+
/** The text of a message, whatever content shape it arrived in. */
|
|
30
|
+
messageText: (message: BaseMessage) => string
|
|
31
|
+
/**
|
|
32
|
+
* A transcript the model can read, newest-biased.
|
|
33
|
+
*
|
|
34
|
+
* The tail is what matters to a compaction — how the run ENDED decides what to do next — so when
|
|
35
|
+
* the budget binds it is the head that goes.
|
|
36
|
+
*/
|
|
37
|
+
renderTranscript: (messages: readonly BaseMessage[], maxChars?: number) => string
|
|
38
|
+
/**
|
|
39
|
+
* Compact a finished run into what the next one needs.
|
|
40
|
+
*
|
|
41
|
+
* Two parts, deliberately. A summary alone leaves the next run to re-derive the plan from the
|
|
42
|
+
* outcome, which is where it invents a different one; the advice is the half that carries intent
|
|
43
|
+
* across the gap.
|
|
44
|
+
*
|
|
45
|
+
* **Never throws, and never trusts the model's arithmetic.** The character caps are applied after
|
|
46
|
+
* the answer comes back, because a cap in a prompt is a request. When the model is absent or fails
|
|
47
|
+
* — an exhausted budget is the common case, and asking again would fail the same way — the
|
|
48
|
+
* deterministic fallback still produces a usable event: what was asked, and how it ended.
|
|
49
|
+
*/
|
|
50
|
+
composeCompaction: (input: CompactionInput) => Promise<Compaction>
|
|
51
|
+
}
|
|
@@ -2,104 +2,86 @@ import type { BaseMessage } from '@langchain/core/messages'
|
|
|
2
2
|
import { AgentRunStatus, DEFAULT_ADVICE_CHARS, DEFAULT_SUMMARY_CHARS, truncateAt } from '@owlmeans/agent-common'
|
|
3
3
|
import { logger } from '@owlmeans/log'
|
|
4
4
|
import { COMPACTION_SCHEMA, DEFAULT_TRANSCRIPT_CHARS } from './consts.local.js'
|
|
5
|
-
import type { Compaction, CompactionInput } from './types.js'
|
|
5
|
+
import type { Compaction, CompactionHelper, CompactionInput } from './compaction/types.js'
|
|
6
6
|
|
|
7
7
|
const log = logger('agent')
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
export const createCompactionHelper = (): CompactionHelper => {
|
|
10
|
+
const messageText = (message: BaseMessage): string => {
|
|
11
|
+
const content = message.content
|
|
12
|
+
if (typeof content === 'string') {
|
|
13
|
+
return content
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(content)) {
|
|
16
|
+
return content
|
|
17
|
+
.map(part => typeof part === 'string' ? part : (part as { text?: string }).text ?? '')
|
|
18
|
+
.filter(text => text !== '')
|
|
19
|
+
.join('\n')
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
}
|
|
22
|
+
return ''
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
25
|
+
const renderTranscript = (
|
|
26
|
+
messages: readonly BaseMessage[], maxChars = DEFAULT_TRANSCRIPT_CHARS,
|
|
27
|
+
): string => {
|
|
28
|
+
const lines: string[] = []
|
|
29
|
+
let used = 0
|
|
30
|
+
|
|
31
|
+
for (let i = messages.length - 1; i >= 0; --i) {
|
|
32
|
+
const message = messages[i]
|
|
33
|
+
const text = messageText(message).trim()
|
|
34
|
+
const calls = (message as { tool_calls?: Array<{ name: string }> }).tool_calls
|
|
35
|
+
const body = text !== ''
|
|
36
|
+
? text
|
|
37
|
+
: calls != null && calls.length > 0
|
|
38
|
+
? `(called ${calls.map(call => call.name).join(', ')})`
|
|
39
|
+
: ''
|
|
40
|
+
if (body === '') {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const line = `${message.getType()}: ${body}`
|
|
45
|
+
if (used + line.length > maxChars) {
|
|
46
|
+
break
|
|
47
|
+
}
|
|
48
|
+
lines.unshift(line)
|
|
49
|
+
used += line.length
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
if (used + line.length > maxChars) {
|
|
52
|
-
break
|
|
53
|
-
}
|
|
54
|
-
lines.unshift(line)
|
|
55
|
-
used += line.length
|
|
52
|
+
return lines.join('\n\n')
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
maxTranscriptChars,
|
|
80
|
-
} = input
|
|
81
|
-
|
|
82
|
-
const fallback = (): Compaction => {
|
|
83
|
-
const last = [...messages].reverse().find(message => messageText(message).trim() !== '')
|
|
84
|
-
const tail = last != null ? messageText(last).trim() : ''
|
|
85
|
-
const head = `Asked: ${prompt.trim()}`
|
|
86
|
-
const ended = status === AgentRunStatus.Ok ? 'Finished.' : 'Did not finish.'
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
summary: truncateAt(
|
|
90
|
-
[head, ended, note?.trim(), tail].filter(part => part != null && part !== '').join(' '),
|
|
91
|
-
maxSummaryChars,
|
|
92
|
-
),
|
|
55
|
+
const composeCompaction = async (input: CompactionInput): Promise<Compaction> => {
|
|
56
|
+
const {
|
|
57
|
+
model, prompt, messages, status, note,
|
|
58
|
+
maxSummaryChars = DEFAULT_SUMMARY_CHARS,
|
|
59
|
+
maxAdviceChars = DEFAULT_ADVICE_CHARS,
|
|
60
|
+
action = 'agent-compaction',
|
|
61
|
+
maxTranscriptChars,
|
|
62
|
+
} = input
|
|
63
|
+
|
|
64
|
+
const fallback = (): Compaction => {
|
|
65
|
+
const last = [...messages].reverse().find(message => messageText(message).trim() !== '')
|
|
66
|
+
const tail = last != null ? messageText(last).trim() : ''
|
|
67
|
+
const head = `Asked: ${prompt.trim()}`
|
|
68
|
+
const ended = status === AgentRunStatus.Ok ? 'Finished.' : 'Did not finish.'
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
summary: truncateAt(
|
|
72
|
+
[head, ended, note?.trim(), tail].filter(part => part != null && part !== '').join(' '),
|
|
73
|
+
maxSummaryChars,
|
|
74
|
+
),
|
|
75
|
+
}
|
|
93
76
|
}
|
|
94
|
-
}
|
|
95
77
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
78
|
+
if (model == null) {
|
|
79
|
+
return fallback()
|
|
80
|
+
}
|
|
99
81
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
82
|
+
try {
|
|
83
|
+
const result = await model.invoke<{ summary: string, advice: string }>(
|
|
84
|
+
`
|
|
103
85
|
Compact the conversation below into a handover for the next session working on the same subject.
|
|
104
86
|
|
|
105
87
|
Write two things:
|
|
@@ -122,20 +104,35 @@ ${status === AgentRunStatus.Ok ? 'Completed' : 'Failed'}${note != null && note !
|
|
|
122
104
|
# Conversation
|
|
123
105
|
${renderTranscript(messages, maxTranscriptChars)}
|
|
124
106
|
`,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
107
|
+
COMPACTION_SCHEMA,
|
|
108
|
+
{ action },
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const summary = truncateAt(result.summary ?? '', maxSummaryChars)
|
|
112
|
+
const advice = truncateAt(result.advice ?? '', maxAdviceChars)
|
|
113
|
+
|
|
114
|
+
// An empty summary is a non-answer, not a short one — take the deterministic path rather than
|
|
115
|
+
// storing a blank event that the next run will read as "nothing happened".
|
|
116
|
+
return summary === ''
|
|
117
|
+
? fallback()
|
|
118
|
+
: { summary, ...(advice !== '' ? { advice } : {}) }
|
|
119
|
+
} catch (e) {
|
|
120
|
+
log.warn('Agent compaction failed, falling back to a deterministic summary', e)
|
|
121
|
+
return fallback()
|
|
122
|
+
}
|
|
140
123
|
}
|
|
124
|
+
|
|
125
|
+
return { messageText, renderTranscript, composeCompaction }
|
|
141
126
|
}
|
|
127
|
+
|
|
128
|
+
export const compactionHelper = createCompactionHelper()
|
|
129
|
+
|
|
130
|
+
/** @deprecated compat:factory-refactor — use `compactionHelper.messageText(…)` */
|
|
131
|
+
export const messageText = (message: BaseMessage): string => compactionHelper.messageText(message)
|
|
132
|
+
|
|
133
|
+
/** @deprecated compat:factory-refactor — use `compactionHelper.renderTranscript(…)` */
|
|
134
|
+
export const renderTranscript = (messages: readonly BaseMessage[], maxChars?: number): string =>
|
|
135
|
+
compactionHelper.renderTranscript(messages, maxChars)
|
|
136
|
+
|
|
137
|
+
/** @deprecated compat:factory-refactor — use `compactionHelper.composeCompaction(…)` */
|
|
138
|
+
export const composeCompaction = (input: CompactionInput): Promise<Compaction> => compactionHelper.composeCompaction(input)
|
package/src/helpers/index.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LlmModel } from '@owlmeans/llm'
|
|
2
|
+
|
|
3
|
+
export interface RollingSummaryInput {
|
|
4
|
+
/** Omit to skip the model and take the deterministic path. */
|
|
5
|
+
model?: LlmModel
|
|
6
|
+
/** The prose account so far. Empty on the first fold. */
|
|
7
|
+
previous: string
|
|
8
|
+
/** What just happened, as one line. */
|
|
9
|
+
event: string
|
|
10
|
+
/** Anything the fold may use but that need not survive into the summary. */
|
|
11
|
+
details?: string
|
|
12
|
+
/** Hard ceiling on the returned prose, in characters. */
|
|
13
|
+
maxChars: number
|
|
14
|
+
/** LangChain `runName`. Give it a value the application filters out of its user-facing stream. */
|
|
15
|
+
action?: string
|
|
16
|
+
}
|
package/src/helpers/rolling.ts
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ToolCall } from '@langchain/core/messages'
|
|
2
|
+
import type { AgentToolSet } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
/** The shape a contained tool failure comes back as. Matches what tool bodies return themselves. */
|
|
5
|
+
export interface ToolErrorResponse { error: string }
|
|
6
|
+
|
|
7
|
+
/** Running a model-requested tool without letting its failure take the run with it. */
|
|
8
|
+
export interface ToolHelper {
|
|
9
|
+
toErrorResponse: (e: unknown) => ToolErrorResponse
|
|
10
|
+
isToolError: (result: unknown) => result is ToolErrorResponse
|
|
11
|
+
/**
|
|
12
|
+
* Run one model-requested tool, contained.
|
|
13
|
+
*
|
|
14
|
+
* **Never throws.** The caller wraps this in a LangGraph task, and a rejected task aborts the whole
|
|
15
|
+
* superstep: every sibling tool call in the same parallel batch dies with AbortError and the run
|
|
16
|
+
* ends on "Multiple errors occurred during superstep 0", discarding work the other calls had
|
|
17
|
+
* already finished. A tool failure has to come back as something the model can read and correct
|
|
18
|
+
* instead — most of them are the model's own mistake (an argument outside an enum, a hallucinated
|
|
19
|
+
* tool name), and the error text already names what was expected.
|
|
20
|
+
*
|
|
21
|
+
* Awaiting `invoke` is what makes the catch reachable: the tool's schema is validated inside it, so
|
|
22
|
+
* a bad argument rejects asynchronously and returning the promise unawaited would carry the
|
|
23
|
+
* rejection straight past this handler.
|
|
24
|
+
*
|
|
25
|
+
* Resolution is by the tool's OWN name, not by the map key. `bindTools` advertises `tool.name`, so
|
|
26
|
+
* that is what the model calls — a map keyed by a local variable silently loses any tool whose two
|
|
27
|
+
* names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
|
|
28
|
+
* as a fallback so a caller may still address a tool by it.
|
|
29
|
+
*
|
|
30
|
+
* `fatal` is the ONE exception to "never throws", and it exists because containment has a cost the
|
|
31
|
+
* containment itself cannot see. A tool may be a whole pipeline behind a single call; when the
|
|
32
|
+
* thing that stopped it is an exhausted budget or a refusal, handing the model a readable error
|
|
33
|
+
* message is an invitation to pick another tool and spend again. A caller that knows which errors
|
|
34
|
+
* mean "stop" says so here, and those alone travel out.
|
|
35
|
+
*/
|
|
36
|
+
safeInvokeTool: (tools: AgentToolSet, toolCall: ToolCall, fatal?: (e: unknown) => boolean) => Promise<unknown>
|
|
37
|
+
}
|
package/src/helpers/tools.ts
CHANGED
|
@@ -1,58 +1,50 @@
|
|
|
1
1
|
import type { ToolCall } from '@langchain/core/messages'
|
|
2
2
|
import { logger } from '@owlmeans/log'
|
|
3
3
|
import type { AgentToolSet } from '../types.js'
|
|
4
|
-
import type { ToolErrorResponse } from './types.js'
|
|
4
|
+
import type { ToolErrorResponse, ToolHelper } from './tools/types.js'
|
|
5
5
|
|
|
6
6
|
const log = logger('agent:tools')
|
|
7
7
|
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export const isToolError = (result: unknown): result is ToolErrorResponse =>
|
|
12
|
-
typeof result === 'object' && result != null && 'error' in result
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Run one model-requested tool, contained.
|
|
16
|
-
*
|
|
17
|
-
* **Never throws.** The caller wraps this in a LangGraph task, and a rejected task aborts the whole
|
|
18
|
-
* superstep: every sibling tool call in the same parallel batch dies with AbortError and the run
|
|
19
|
-
* ends on "Multiple errors occurred during superstep 0", discarding work the other calls had
|
|
20
|
-
* already finished. A tool failure has to come back as something the model can read and correct
|
|
21
|
-
* instead — most of them are the model's own mistake (an argument outside an enum, a hallucinated
|
|
22
|
-
* tool name), and the error text already names what was expected.
|
|
23
|
-
*
|
|
24
|
-
* Awaiting `invoke` is what makes the catch reachable: the tool's schema is validated inside it, so
|
|
25
|
-
* a bad argument rejects asynchronously and returning the promise unawaited would carry the
|
|
26
|
-
* rejection straight past this handler.
|
|
27
|
-
*
|
|
28
|
-
* Resolution is by the tool's OWN name, not by the map key. `bindTools` advertises `tool.name`, so
|
|
29
|
-
* that is what the model calls — a map keyed by a local variable silently loses any tool whose two
|
|
30
|
-
* names drifted apart, leaving it advertised, callable, and permanently "not found". The key stays
|
|
31
|
-
* as a fallback so a caller may still address a tool by it.
|
|
32
|
-
*
|
|
33
|
-
* `fatal` is the ONE exception to "never throws", and it exists because containment has a cost the
|
|
34
|
-
* containment itself cannot see. A tool may be a whole pipeline behind a single call; when the
|
|
35
|
-
* thing that stopped it is an exhausted budget or a refusal, handing the model a readable error
|
|
36
|
-
* message is an invitation to pick another tool and spend again. A caller that knows which errors
|
|
37
|
-
* mean "stop" says so here, and those alone travel out.
|
|
38
|
-
*/
|
|
39
|
-
export const safeInvokeTool = async (
|
|
40
|
-
tools: AgentToolSet, toolCall: ToolCall, fatal?: (e: unknown) => boolean,
|
|
41
|
-
): Promise<unknown> => {
|
|
42
|
-
const tool = tools[toolCall.name]
|
|
43
|
-
?? Object.values(tools).find(entry => entry.name === toolCall.name)
|
|
8
|
+
export const createToolHelper = (): ToolHelper => {
|
|
9
|
+
const toErrorResponse = (e: unknown): ToolErrorResponse =>
|
|
10
|
+
({ error: `Error during tool call: ${(e instanceof Error) ? e.message : String(e)}` })
|
|
44
11
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
12
|
+
const isToolError = (result: unknown): result is ToolErrorResponse =>
|
|
13
|
+
typeof result === 'object' && result != null && 'error' in result
|
|
14
|
+
|
|
15
|
+
const safeInvokeTool = async (
|
|
16
|
+
tools: AgentToolSet, toolCall: ToolCall, fatal?: (e: unknown) => boolean,
|
|
17
|
+
): Promise<unknown> => {
|
|
18
|
+
const tool = tools[toolCall.name]
|
|
19
|
+
?? Object.values(tools).find(entry => entry.name === toolCall.name)
|
|
48
20
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
21
|
+
if (tool == null) {
|
|
22
|
+
return toErrorResponse(new Error(`Tool ${toolCall.name} not found`))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return await tool.invoke(toolCall.args)
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (fatal?.(e) === true) {
|
|
29
|
+
throw e
|
|
30
|
+
}
|
|
31
|
+
log.warn('Tool call failed', { tool: toolCall.name, error: e })
|
|
32
|
+
return toErrorResponse(e)
|
|
54
33
|
}
|
|
55
|
-
log.warn('Tool call failed', { tool: toolCall.name, error: e })
|
|
56
|
-
return toErrorResponse(e)
|
|
57
34
|
}
|
|
35
|
+
|
|
36
|
+
return { toErrorResponse, isToolError, safeInvokeTool }
|
|
58
37
|
}
|
|
38
|
+
|
|
39
|
+
export const toolHelper = createToolHelper()
|
|
40
|
+
|
|
41
|
+
/** @deprecated compat:factory-refactor — use `toolHelper.toErrorResponse(…)` */
|
|
42
|
+
export const toErrorResponse = (e: unknown): ToolErrorResponse => toolHelper.toErrorResponse(e)
|
|
43
|
+
|
|
44
|
+
/** @deprecated compat:factory-refactor — use `toolHelper.isToolError(…)` */
|
|
45
|
+
export const isToolError = (result: unknown): result is ToolErrorResponse => toolHelper.isToolError(result)
|
|
46
|
+
|
|
47
|
+
/** @deprecated compat:factory-refactor — use `toolHelper.safeInvokeTool(…)` */
|
|
48
|
+
export const safeInvokeTool = (
|
|
49
|
+
tools: AgentToolSet, toolCall: ToolCall, fatal?: (e: unknown) => boolean,
|
|
50
|
+
): Promise<unknown> => toolHelper.safeInvokeTool(tools, toolCall, fatal)
|
package/src/model.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { addMessages, entrypoint, task } from '@langchain/langgraph'
|
|
|
4
4
|
import { createIdOfLength } from '@owlmeans/basic-ids'
|
|
5
5
|
import { makeFlowModel } from '@owlmeans/flow'
|
|
6
6
|
import type { FlowModel } from '@owlmeans/flow'
|
|
7
|
-
import {
|
|
7
|
+
import { llmPluginRegistry } from '@owlmeans/llm'
|
|
8
8
|
import type { HelperExecution, ModelInputItem } from '@owlmeans/llm'
|
|
9
9
|
import { logger } from '@owlmeans/log'
|
|
10
10
|
import {
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
} from '@owlmeans/agent-common'
|
|
13
13
|
import { DEFAULT_ACTION, DEFAULT_ENTRYPOINT, DEFAULT_MAX_TURNS, DEFAULT_PLUGIN_ORDER } from './consts.js'
|
|
14
14
|
import { AgentLoopExhaustedError, AgentMissconfiguredError } from './errors.js'
|
|
15
|
-
import { safeInvokeTool } from './helpers/tools.js'
|
|
16
15
|
import type {
|
|
17
16
|
AgentModel, AgentOptions, AgentPlugin, AgentResult, AgentRun, AgentRunOutcome, AgentToolSet,
|
|
18
17
|
} from './types.js'
|
|
18
|
+
import { toolHelper } from './helpers/tools.js'
|
|
19
19
|
|
|
20
20
|
const log = logger('agent')
|
|
21
21
|
|
|
@@ -49,7 +49,7 @@ export const makeAgentModel = (options: AgentOptions): AgentModel => {
|
|
|
49
49
|
|
|
50
50
|
const conversation = options.conversation ?? conversationFor(exec.purpose)
|
|
51
51
|
const prompts = options.prompts ?? exec.prompts
|
|
52
|
-
const provider = options.provider ?? pluginFor(agentModel)
|
|
52
|
+
const provider = options.provider ?? llmPluginRegistry.pluginFor(agentModel)
|
|
53
53
|
const purpose = exec.purpose
|
|
54
54
|
|
|
55
55
|
const registry: AgentPlugin[] = []
|
|
@@ -167,7 +167,7 @@ export const makeAgentModel = (options: AgentOptions): AgentModel => {
|
|
|
167
167
|
// `safeInvokeTool` is what keeps a bad argument from costing the work the others finished.
|
|
168
168
|
const call = task(
|
|
169
169
|
'call-tool',
|
|
170
|
-
async (toolCall: ToolCall) => safeInvokeTool(toolSet, toolCall, options.fatal),
|
|
170
|
+
async (toolCall: ToolCall) => toolHelper.safeInvokeTool(toolSet, toolCall, options.fatal),
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
const agent = entrypoint(entrypointName, async (messages: BaseMessageLike[]) => {
|
package/src/pipeline/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type CumulativeResultEntry, type CumulativeResultFact, type CumulativeResultsSpec, type PipelineSpec, type StepResultsSpec, CumulativeResultSource, ResultViewMode } from '@owlmeans/agent-common'
|
|
2
2
|
import type { StepResults, VisibleResultEntry } from './types.js'
|
|
3
|
-
import type { BaseCheckpointSaver } from '@langchain/langgraph'
|
|
4
3
|
|
|
5
4
|
export interface Recorded {
|
|
6
5
|
files: Set<string>
|
|
@@ -39,23 +38,3 @@ export interface EntryParts {
|
|
|
39
38
|
partial?: boolean
|
|
40
39
|
summary?: string
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* The graph builder, seen loosely.
|
|
45
|
-
*
|
|
46
|
-
* `StateGraph`'s node names are a generic parameter that only a literal-typed builder chain can
|
|
47
|
-
* satisfy, and a pipeline's nodes come from data. The precision belongs at THIS package's public
|
|
48
|
-
* boundary — `PipelineSpec`, `PipelineStep`, `PipelineModel` are all exact — not in the three lines
|
|
49
|
-
* that hand LangGraph a name it will look up in a map either way.
|
|
50
|
-
*/
|
|
51
|
-
export interface LooseGraph {
|
|
52
|
-
addNode: (
|
|
53
|
-
name: string,
|
|
54
|
-
fn: (state: unknown) => Promise<Record<string, unknown>>,
|
|
55
|
-
options?: Record<string, unknown>,
|
|
56
|
-
) => LooseGraph
|
|
57
|
-
addEdge: (start: string | string[], end: string) => LooseGraph
|
|
58
|
-
compile: (options?: { checkpointer?: BaseCheckpointSaver }) => {
|
|
59
|
-
invoke: (input: unknown, config?: Record<string, unknown>) => Promise<unknown>
|
|
60
|
-
}
|
|
61
|
-
}
|