@migor/agentia 0.0.1
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 +171 -0
- package/dist/container/container.d.ts +40 -0
- package/dist/container/container.js +66 -0
- package/dist/container/container.js.map +1 -0
- package/dist/core/schema.d.ts +14 -0
- package/dist/core/schema.js +116 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/tool.d.ts +60 -0
- package/dist/core/tool.js +2 -0
- package/dist/core/tool.js.map +1 -0
- package/dist/core/trace.d.ts +52 -0
- package/dist/core/trace.js +6 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/engine/context.d.ts +47 -0
- package/dist/engine/context.js +146 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/errors.d.ts +3 -0
- package/dist/engine/errors.js +22 -0
- package/dist/engine/errors.js.map +1 -0
- package/dist/engine/loop.d.ts +33 -0
- package/dist/engine/loop.js +227 -0
- package/dist/engine/loop.js.map +1 -0
- package/dist/engine/policy.d.ts +30 -0
- package/dist/engine/policy.js +35 -0
- package/dist/engine/policy.js.map +1 -0
- package/dist/engine/tracer.d.ts +27 -0
- package/dist/engine/tracer.js +93 -0
- package/dist/engine/tracer.js.map +1 -0
- package/dist/engine/types.d.ts +58 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/usage.d.ts +5 -0
- package/dist/engine/usage.js +30 -0
- package/dist/engine/usage.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/run/async.d.ts +64 -0
- package/dist/run/async.js +135 -0
- package/dist/run/async.js.map +1 -0
- package/dist/run/context.d.ts +16 -0
- package/dist/run/context.js +48 -0
- package/dist/run/context.js.map +1 -0
- package/dist/run/fsStore.d.ts +29 -0
- package/dist/run/fsStore.js +79 -0
- package/dist/run/fsStore.js.map +1 -0
- package/dist/run/run.d.ts +50 -0
- package/dist/run/run.js +98 -0
- package/dist/run/run.js.map +1 -0
- package/dist/run/scheduler.d.ts +27 -0
- package/dist/run/scheduler.js +85 -0
- package/dist/run/scheduler.js.map +1 -0
- package/dist/run/spec.d.ts +40 -0
- package/dist/run/spec.js +25 -0
- package/dist/run/spec.js.map +1 -0
- package/dist/run/store.d.ts +40 -0
- package/dist/run/store.js +28 -0
- package/dist/run/store.js.map +1 -0
- package/dist/run/systemPrompt.d.ts +25 -0
- package/dist/run/systemPrompt.js +41 -0
- package/dist/run/systemPrompt.js.map +1 -0
- package/dist/run/transport.d.ts +12 -0
- package/dist/run/transport.js +16 -0
- package/dist/run/transport.js.map +1 -0
- package/dist/run/types.d.ts +12 -0
- package/dist/run/types.js +2 -0
- package/dist/run/types.js.map +1 -0
- package/dist/toolkit/asset.d.ts +22 -0
- package/dist/toolkit/asset.js +26 -0
- package/dist/toolkit/asset.js.map +1 -0
- package/dist/toolkit/discover.d.ts +2 -0
- package/dist/toolkit/discover.js +61 -0
- package/dist/toolkit/discover.js.map +1 -0
- package/dist/toolkit/module.d.ts +74 -0
- package/dist/toolkit/module.js +114 -0
- package/dist/toolkit/module.js.map +1 -0
- package/dist/toolkit/prompt.d.ts +32 -0
- package/dist/toolkit/prompt.js +70 -0
- package/dist/toolkit/prompt.js.map +1 -0
- package/dist/toolkit/skill.d.ts +74 -0
- package/dist/toolkit/skill.js +125 -0
- package/dist/toolkit/skill.js.map +1 -0
- package/dist/toolkit/subagent.d.ts +54 -0
- package/dist/toolkit/subagent.js +120 -0
- package/dist/toolkit/subagent.js.map +1 -0
- package/dist/toolkit/tool.d.ts +36 -0
- package/dist/toolkit/tool.js +54 -0
- package/dist/toolkit/tool.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— 长上下文策略的纯函数层(spec §5/§6:compaction / context editing 分清)。
|
|
3
|
+
*
|
|
4
|
+
* 三策略不混:
|
|
5
|
+
* - **context editing**(`trimToolPairs`):清旧 tool_use→tool_result 对,不掉内容、不额外调模型;
|
|
6
|
+
* - **compaction**(`compactMessages`):把旧消息前缀做**服务端摘要**(摘要器由上层注入,可走模型),
|
|
7
|
+
* 只保留最近 N 条 + 一段摘要 —— 新的消息数组仍保证角色交替合法;
|
|
8
|
+
* - **客户端剪裁** = Turn 3 子 agent(另件)。
|
|
9
|
+
*
|
|
10
|
+
* 不在此自研 token 计数黑名单:`estimateMessages` 只是预算策略的默认启发式
|
|
11
|
+
* (CJK 感知估算,明确标注为估算),上层可注入真实 count_tokens 结果。
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 默认估算(仅预算决策用,非精确记账):CJK 字符按 ~1.5 字/token,
|
|
15
|
+
* 其余(ASCII/拉丁)按 ~4 字符/token。纯字符/4 对中文系统性高估,
|
|
16
|
+
* 会让预算护栏过早触发压缩。
|
|
17
|
+
*/
|
|
18
|
+
export function defaultEstimateTokens(text) {
|
|
19
|
+
let cjk = 0;
|
|
20
|
+
for (const ch of text) {
|
|
21
|
+
const cp = ch.codePointAt(0);
|
|
22
|
+
if ((cp >= 0x3400 && cp <= 0x9fff) || // CJK 统一表意文字(扩展A + 基本区)
|
|
23
|
+
(cp >= 0xf900 && cp <= 0xfaff) || // 兼容表意文字
|
|
24
|
+
(cp >= 0x20000 && cp <= 0x2a6df) || // 扩展B
|
|
25
|
+
(cp >= 0x3000 && cp <= 0x30ff) || // 日文假名 + CJK 标点
|
|
26
|
+
(cp >= 0xff00 && cp <= 0xffef) // 全角字符
|
|
27
|
+
) {
|
|
28
|
+
cjk++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const other = text.length - cjk; // 非 BMP 字符按 UTF-16 长度计,属可接受偏差
|
|
32
|
+
return Math.max(1, Math.ceil(cjk / 1.5 + other / 4));
|
|
33
|
+
}
|
|
34
|
+
export function isToolResultMessage(msg) {
|
|
35
|
+
if (msg.role !== 'user')
|
|
36
|
+
return false;
|
|
37
|
+
if (typeof msg.content === 'string')
|
|
38
|
+
return false;
|
|
39
|
+
return msg.content.length > 0 && msg.content.every((b) => b.type === 'tool_result');
|
|
40
|
+
}
|
|
41
|
+
function hasToolUse(msg) {
|
|
42
|
+
if (msg.role !== 'assistant')
|
|
43
|
+
return false;
|
|
44
|
+
if (typeof msg.content === 'string')
|
|
45
|
+
return false;
|
|
46
|
+
return msg.content.some((b) => b.type === 'tool_use');
|
|
47
|
+
}
|
|
48
|
+
function contentToText(content) {
|
|
49
|
+
if (typeof content === 'string')
|
|
50
|
+
return content;
|
|
51
|
+
return content
|
|
52
|
+
.map((b) => {
|
|
53
|
+
switch (b.type) {
|
|
54
|
+
case 'text':
|
|
55
|
+
return b.text;
|
|
56
|
+
case 'tool_use':
|
|
57
|
+
return `${b.name}(${JSON.stringify(b.input)})`;
|
|
58
|
+
case 'tool_result':
|
|
59
|
+
return JSON.stringify(b.content);
|
|
60
|
+
default:
|
|
61
|
+
return JSON.stringify(b);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.join('\n');
|
|
65
|
+
}
|
|
66
|
+
function contentTokens(content, estimate) {
|
|
67
|
+
if (typeof content === 'string')
|
|
68
|
+
return estimate(content);
|
|
69
|
+
let n = 0;
|
|
70
|
+
for (const b of content) {
|
|
71
|
+
n += 1; // block 自身开销
|
|
72
|
+
n += estimate(contentToText([b]));
|
|
73
|
+
}
|
|
74
|
+
return n;
|
|
75
|
+
}
|
|
76
|
+
/** 整组消息的估算 token(含每条 role 开销)。 */
|
|
77
|
+
export function estimateMessages(messages, estimate = defaultEstimateTokens) {
|
|
78
|
+
let n = 0;
|
|
79
|
+
for (const m of messages) {
|
|
80
|
+
n += estimate(m.role);
|
|
81
|
+
n += contentTokens(m.content, estimate);
|
|
82
|
+
}
|
|
83
|
+
return n;
|
|
84
|
+
}
|
|
85
|
+
/** 把消息渲染成可喂给摘要器的纯文本(role: content)。 */
|
|
86
|
+
export function renderMessages(messages) {
|
|
87
|
+
return messages.map((m) => `${m.role}: ${contentToText(m.content)}`).join('\n\n');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* context editing:丢弃旧的 tool_use→tool_result 对(超出 keepRecent 的),
|
|
91
|
+
* 保留最近 N 对以及所有非工具消息。逐对整体移除,保角色交替合法。
|
|
92
|
+
* 返回原数组引用(若无需裁剪)或新数组。
|
|
93
|
+
*/
|
|
94
|
+
export function trimToolPairs(messages, opts = {}) {
|
|
95
|
+
const keep = Math.max(0, opts.keepRecent ?? 1);
|
|
96
|
+
const pairs = [];
|
|
97
|
+
for (let i = 0; i + 1 < messages.length; i++) {
|
|
98
|
+
if (hasToolUse(messages[i]) && isToolResultMessage(messages[i + 1])) {
|
|
99
|
+
pairs.push([i, i + 1]);
|
|
100
|
+
i += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (pairs.length <= keep)
|
|
104
|
+
return messages; // 无需裁剪(或本就留得下)
|
|
105
|
+
// 丢掉最旧的 (pairs.length - keep) 对,最近的 keep 对与所有非工具消息保留
|
|
106
|
+
const drop = new Set();
|
|
107
|
+
for (let k = 0; k < pairs.length - keep; k++) {
|
|
108
|
+
drop.add(pairs[k][0]);
|
|
109
|
+
drop.add(pairs[k][1]);
|
|
110
|
+
}
|
|
111
|
+
const out = messages.filter((_, i) => !drop.has(i));
|
|
112
|
+
return out.length === messages.length ? messages : out;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* compaction:把 messages 里“除最近 keepRecent 条”的旧前缀做摘要,替换成一段
|
|
116
|
+
* 摘要消息,尾部保留。产出保证:
|
|
117
|
+
* - 不拆散 (assistant tool_use → user tool_result) 对 —— cut 落到对中间时整体后移;
|
|
118
|
+
* - 角色交替合法 —— 尾段首条是普通 user 时摘要并入之;否则摘要以 user 角色放最前。
|
|
119
|
+
*/
|
|
120
|
+
export async function compactMessages(messages, opts) {
|
|
121
|
+
const keepRecent = Math.max(1, opts.keepRecent ?? 20);
|
|
122
|
+
if (messages.length <= keepRecent)
|
|
123
|
+
return messages;
|
|
124
|
+
let cut = messages.length - keepRecent;
|
|
125
|
+
// cut 若落在一对 tool_result 的开头(前一条是带 tool_use 的 assistant),整对后移进保留段
|
|
126
|
+
while (cut > 1 && cut < messages.length && isToolResultMessage(messages[cut])) {
|
|
127
|
+
cut -= 1;
|
|
128
|
+
}
|
|
129
|
+
const prefix = messages.slice(0, cut);
|
|
130
|
+
const tail = messages.slice(cut);
|
|
131
|
+
const summary = await opts.summarize(renderMessages(prefix));
|
|
132
|
+
const label = `[此前对话摘要]\n${summary}`;
|
|
133
|
+
if (tail.length === 0)
|
|
134
|
+
return [{ role: 'user', content: label }];
|
|
135
|
+
const [first, ...rest] = tail;
|
|
136
|
+
if (first.role === 'user' && !isToolResultMessage(first)) {
|
|
137
|
+
// 并入首条普通 user 消息,保持 user→assistant 交替
|
|
138
|
+
const merged = typeof first.content === 'string'
|
|
139
|
+
? { ...first, content: `${label}\n\n${first.content}` }
|
|
140
|
+
: { ...first, content: [{ type: 'text', text: label }, ...first.content] };
|
|
141
|
+
return [merged, ...rest];
|
|
142
|
+
}
|
|
143
|
+
// 尾段以 assistant 开头:摘要作首条 user(user→assistant 交替合法)
|
|
144
|
+
return [{ role: 'user', content: label }, ...tail];
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/engine/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
/** 把任意异常分类成 trace 可用的 SpanError(可重试 vs 不可重试)。 */
|
|
3
|
+
export function classifyError(e) {
|
|
4
|
+
if (e instanceof Anthropic.RateLimitError) {
|
|
5
|
+
return { type: 'rate_limit', message: e.message, retryable: true };
|
|
6
|
+
}
|
|
7
|
+
if (e instanceof Anthropic.APIConnectionError) {
|
|
8
|
+
return { type: 'connection', message: e.message, retryable: true };
|
|
9
|
+
}
|
|
10
|
+
if (e instanceof Anthropic.InternalServerError) {
|
|
11
|
+
return { type: 'server', message: e.message, retryable: true };
|
|
12
|
+
}
|
|
13
|
+
if (e instanceof Anthropic.APIError) {
|
|
14
|
+
const status = e.status ?? 0;
|
|
15
|
+
return { type: 'api', message: e.message, retryable: status >= 500 || status === 429 };
|
|
16
|
+
}
|
|
17
|
+
if (e instanceof Error) {
|
|
18
|
+
return { type: 'unknown', message: e.message, retryable: false };
|
|
19
|
+
}
|
|
20
|
+
return { type: 'unknown', message: String(e), retryable: false };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/engine/errors.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAG1C,iDAAiD;AACjD,MAAM,UAAU,aAAa,CAAC,CAAU;IACtC,IAAI,CAAC,YAAY,SAAS,CAAC,cAAc,EAAE,CAAC;QAC1C,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,YAAY,SAAS,CAAC,kBAAkB,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,YAAY,SAAS,CAAC,mBAAmB,EAAE,CAAC;QAC/C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,YAAY,SAAS,CAAC,QAAQ,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;IACzF,CAAC;IACD,IAAI,CAAC,YAAY,KAAK,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACnE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import type { AgentTool, RecorderBackend } from '../core/tool.js';
|
|
3
|
+
import type { SpanError, SpanId } from '../core/trace.js';
|
|
4
|
+
import type { AgentRunResult, AgentStopReason, ContextPolicy, RunAgentOptions, SystemParam } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* 缺省模型解析:显式传入 > AGENTIA_MODEL env > 'claude-opus-5'。
|
|
7
|
+
* 不把端点私有模型写死在代码里 —— 走 Anthropic 兼容网关(如 DeepSeek 端点)时
|
|
8
|
+
* export AGENTIA_MODEL=deepseek-… 即可全局覆盖,无需逐处传 model。
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveDefaultModel(over?: string): string;
|
|
11
|
+
export interface AgentLoopResult {
|
|
12
|
+
stopReason: AgentStopReason;
|
|
13
|
+
finalText: string;
|
|
14
|
+
error?: SpanError;
|
|
15
|
+
/** 本轮循环自己发起的模型往返次数 */
|
|
16
|
+
iterations: number;
|
|
17
|
+
}
|
|
18
|
+
/** 主入口:开 run 根 span,循环跑在其下。返回完整 trace(traceId 即 runId)。 */
|
|
19
|
+
export declare function runAgent(options: RunAgentOptions): Promise<AgentRunResult>;
|
|
20
|
+
/** 嵌套单元(子 agent)入口:不自开 run 根,llm.turn 挂在给定 parentSpanId 下的同一条 trace。 */
|
|
21
|
+
export declare function runAgentScoped(opts: {
|
|
22
|
+
client?: Anthropic;
|
|
23
|
+
system?: SystemParam;
|
|
24
|
+
messages: Anthropic.MessageParam[];
|
|
25
|
+
tools?: AgentTool[];
|
|
26
|
+
model?: string;
|
|
27
|
+
maxTokens?: number;
|
|
28
|
+
maxIterations?: number;
|
|
29
|
+
recorder: RecorderBackend;
|
|
30
|
+
parentSpanId: SpanId;
|
|
31
|
+
onText?: (delta: string) => void;
|
|
32
|
+
contextPolicy?: ContextPolicy;
|
|
33
|
+
}): Promise<AgentLoopResult>;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { validateJsonSchema } from '../core/schema.js';
|
|
3
|
+
import { classifyError } from './errors.js';
|
|
4
|
+
import { TraceRecorder } from './tracer.js';
|
|
5
|
+
import { costEstimate, usageFromAnthropic } from './usage.js';
|
|
6
|
+
/**
|
|
7
|
+
* 缺省模型解析:显式传入 > AGENTIA_MODEL env > 'claude-opus-5'。
|
|
8
|
+
* 不把端点私有模型写死在代码里 —— 走 Anthropic 兼容网关(如 DeepSeek 端点)时
|
|
9
|
+
* export AGENTIA_MODEL=deepseek-… 即可全局覆盖,无需逐处传 model。
|
|
10
|
+
*/
|
|
11
|
+
export function resolveDefaultModel(over) {
|
|
12
|
+
if (over)
|
|
13
|
+
return over;
|
|
14
|
+
return process.env.AGENTIA_MODEL?.trim() || 'claude-opus-5';
|
|
15
|
+
}
|
|
16
|
+
/** 循环体核心:带父 span 跑一轮 manual loop。请求失败按 error 收掉 turn 后抛出,由外层收尾。 */
|
|
17
|
+
async function agentLoop(args) {
|
|
18
|
+
const { client, model, recorder, parentSpanId } = args;
|
|
19
|
+
const messages = [...args.messages];
|
|
20
|
+
const apiTools = args.tools.map(toApiTool);
|
|
21
|
+
let stopReason = 'end_turn';
|
|
22
|
+
let error;
|
|
23
|
+
let finalText = '';
|
|
24
|
+
let finished = false;
|
|
25
|
+
let iterations = 0;
|
|
26
|
+
for (let iteration = 0; iteration < args.maxIterations; iteration++) {
|
|
27
|
+
// 发送前给上下文策略一个机会(编辑/压缩预算超限的历史)
|
|
28
|
+
if (args.contextPolicy) {
|
|
29
|
+
const next = await args.contextPolicy.beforeTurn(messages, { iteration, model });
|
|
30
|
+
if (next && next !== messages) {
|
|
31
|
+
if (parentSpanId) {
|
|
32
|
+
recorder.event(parentSpanId, 'context.budget', {
|
|
33
|
+
from: messages.length,
|
|
34
|
+
to: next.length,
|
|
35
|
+
model,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
messages.splice(0, messages.length, ...next);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const turnId = recorder.begin('llm.turn', model, parentSpanId);
|
|
42
|
+
let message;
|
|
43
|
+
try {
|
|
44
|
+
const stream = client.messages.stream({
|
|
45
|
+
model,
|
|
46
|
+
max_tokens: args.maxTokens,
|
|
47
|
+
...(args.system ? { system: args.system } : {}),
|
|
48
|
+
...(apiTools.length ? { tools: apiTools } : {}),
|
|
49
|
+
messages,
|
|
50
|
+
});
|
|
51
|
+
stream.on('text', (delta) => args.onText?.(delta));
|
|
52
|
+
message = await stream.finalMessage();
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
recorder.end(turnId, { status: 'error', error: classifyError(e) });
|
|
56
|
+
throw e; // 冒泡:runAgent 或子 agent 运行器负责标记根/unit 与收尾
|
|
57
|
+
}
|
|
58
|
+
iterations++;
|
|
59
|
+
const usage = message.usage ? usageFromAnthropic(message.usage) : undefined;
|
|
60
|
+
if (usage)
|
|
61
|
+
usage.costEstimate = costEstimate(model, usage);
|
|
62
|
+
recorder.end(turnId, { usage });
|
|
63
|
+
recorder.setAttribute(turnId, 'input_tokens', usage?.inputTokens ?? 0);
|
|
64
|
+
recorder.setAttribute(turnId, 'output_tokens', usage?.outputTokens ?? 0);
|
|
65
|
+
recorder.setAttribute(turnId, 'cache_read_tokens', usage?.cacheReadTokens ?? 0);
|
|
66
|
+
messages.push({ role: 'assistant', content: message.content });
|
|
67
|
+
// —— 终止/边界分支(每个都置 finished,退出循环不再兜底改判)——
|
|
68
|
+
if (message.stop_reason === 'end_turn') {
|
|
69
|
+
stopReason = 'end_turn';
|
|
70
|
+
finalText = textOf(message);
|
|
71
|
+
finished = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (message.stop_reason === 'refusal') {
|
|
75
|
+
stopReason = 'refusal';
|
|
76
|
+
finalText = textOf(message);
|
|
77
|
+
error = { type: 'refusal', message: 'model refused the request', retryable: false };
|
|
78
|
+
finished = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (message.stop_reason === 'max_tokens') {
|
|
82
|
+
stopReason = 'max_tokens';
|
|
83
|
+
finalText = textOf(message);
|
|
84
|
+
finished = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (message.stop_reason === 'pause_turn') {
|
|
88
|
+
// Turn 0 无 server tools,正常不会到;避免无限循环直接停
|
|
89
|
+
stopReason = 'pause_turn';
|
|
90
|
+
finished = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const toolUses = message.content.filter((b) => b.type === 'tool_use');
|
|
94
|
+
if (toolUses.length === 0) {
|
|
95
|
+
// 到这里的剩余 stop_reason 不会产生可执行块,防死循环直接停
|
|
96
|
+
stopReason = 'tool_use_no_blocks';
|
|
97
|
+
finished = true;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
// —— 执行工具:并行;单条 user 消息回全部 tool_result(抑制并行是反模式)——
|
|
101
|
+
const toolResults = await Promise.all(toolUses.map(async (use) => {
|
|
102
|
+
const tool = args.tools.find((t) => t.name === use.name);
|
|
103
|
+
recorder.event(turnId, 'tool.input', { tool: use.name, input: limit(use.input, 2000) });
|
|
104
|
+
const ctx = { client, recorder, parentSpanId: turnId };
|
|
105
|
+
let ok = true;
|
|
106
|
+
let content = '';
|
|
107
|
+
if (!tool) {
|
|
108
|
+
ok = false;
|
|
109
|
+
content = `unknown tool: ${use.name}`;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// 模型给的 input 先过 schema 校验:不合法直接回 is_error(含路径,
|
|
113
|
+
// 模型可自我修正),不进方法体 —— schema 是方法与模型间的运行时契约。
|
|
114
|
+
const invalid = validateJsonSchema(tool.inputSchema, use.input);
|
|
115
|
+
if (invalid) {
|
|
116
|
+
ok = false;
|
|
117
|
+
content = `invalid input: ${invalid}`;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
try {
|
|
121
|
+
content = await tool.run(use.input, ctx);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
ok = false;
|
|
125
|
+
const err = classifyError(e);
|
|
126
|
+
content = `error(${err.type}): ${err.message}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
recorder.event(turnId, 'tool.output', { tool: use.name, ok, content: ok ? limit(content, 2000) : limit(content, 1000) });
|
|
131
|
+
return {
|
|
132
|
+
type: 'tool_result',
|
|
133
|
+
tool_use_id: use.id,
|
|
134
|
+
content: stringifySafe(content),
|
|
135
|
+
is_error: !ok,
|
|
136
|
+
};
|
|
137
|
+
}));
|
|
138
|
+
messages.push({ role: 'user', content: toolResults });
|
|
139
|
+
}
|
|
140
|
+
if (!finished && stopReason === 'end_turn') {
|
|
141
|
+
// 循环因 maxIterations 上限退出而非正常终止
|
|
142
|
+
stopReason = 'max_iterations';
|
|
143
|
+
}
|
|
144
|
+
return { stopReason, finalText, error, iterations };
|
|
145
|
+
}
|
|
146
|
+
/** 主入口:开 run 根 span,循环跑在其下。返回完整 trace(traceId 即 runId)。 */
|
|
147
|
+
export async function runAgent(options) {
|
|
148
|
+
const recorder = options.recorder ?? new TraceRecorder();
|
|
149
|
+
const rootId = recorder.begin('run', options.runName ?? 'agent.run', null);
|
|
150
|
+
recorder.setAttribute(rootId, 'model', resolveDefaultModel(options.model));
|
|
151
|
+
let result;
|
|
152
|
+
try {
|
|
153
|
+
result = await agentLoop({
|
|
154
|
+
client: options.client ?? new Anthropic(),
|
|
155
|
+
model: resolveDefaultModel(options.model),
|
|
156
|
+
maxTokens: options.maxTokens ?? 64_000,
|
|
157
|
+
maxIterations: options.maxIterations ?? 40,
|
|
158
|
+
system: options.system,
|
|
159
|
+
messages: options.messages,
|
|
160
|
+
tools: options.tools ?? [],
|
|
161
|
+
recorder,
|
|
162
|
+
parentSpanId: rootId,
|
|
163
|
+
onText: options.onText,
|
|
164
|
+
contextPolicy: options.contextPolicy,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
result = { stopReason: 'error', finalText: '', error: classifyError(e), iterations: 0 };
|
|
169
|
+
}
|
|
170
|
+
const runStatus = result.stopReason === 'end_turn' ? 'ok' : 'error';
|
|
171
|
+
recorder.setAttribute(rootId, 'stop_reason', result.stopReason);
|
|
172
|
+
recorder.end(rootId, { status: runStatus, ...(result.error ? { error: result.error } : {}) });
|
|
173
|
+
const trace = recorder.snapshot(runStatus);
|
|
174
|
+
return {
|
|
175
|
+
trace,
|
|
176
|
+
stopReason: result.stopReason,
|
|
177
|
+
finalText: result.finalText,
|
|
178
|
+
iterations: result.iterations,
|
|
179
|
+
error: result.error,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** 嵌套单元(子 agent)入口:不自开 run 根,llm.turn 挂在给定 parentSpanId 下的同一条 trace。 */
|
|
183
|
+
export async function runAgentScoped(opts) {
|
|
184
|
+
return agentLoop({
|
|
185
|
+
client: opts.client ?? new Anthropic(),
|
|
186
|
+
model: resolveDefaultModel(opts.model),
|
|
187
|
+
maxTokens: opts.maxTokens ?? 64_000,
|
|
188
|
+
maxIterations: opts.maxIterations ?? 40,
|
|
189
|
+
system: opts.system,
|
|
190
|
+
messages: opts.messages,
|
|
191
|
+
tools: opts.tools ?? [],
|
|
192
|
+
recorder: opts.recorder,
|
|
193
|
+
parentSpanId: opts.parentSpanId,
|
|
194
|
+
onText: opts.onText,
|
|
195
|
+
contextPolicy: opts.contextPolicy,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function toApiTool(t) {
|
|
199
|
+
return {
|
|
200
|
+
name: t.name,
|
|
201
|
+
description: t.description,
|
|
202
|
+
input_schema: t.inputSchema,
|
|
203
|
+
...(t.strict ? { strict: true } : {}),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function textOf(message) {
|
|
207
|
+
return message.content
|
|
208
|
+
.filter((b) => b.type === 'text')
|
|
209
|
+
.map((b) => b.text)
|
|
210
|
+
.join('\n');
|
|
211
|
+
}
|
|
212
|
+
function stringifySafe(x) {
|
|
213
|
+
if (typeof x === 'string')
|
|
214
|
+
return x;
|
|
215
|
+
try {
|
|
216
|
+
return JSON.stringify(x) ?? String(x);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return String(x);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** 截断到上限字符,超长加省略标记 */
|
|
223
|
+
function limit(x, n) {
|
|
224
|
+
const s = stringifySafe(x);
|
|
225
|
+
return s.length > n ? `${s.slice(0, n)}…(+${s.length - n})` : s;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../../src/engine/loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ContextPolicy } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Agentia —— 预算驱动的上下文策略(spec §5 compaction / §6 budget)。
|
|
4
|
+
*
|
|
5
|
+
* 规则(带滞回,避免每回合反复压缩):
|
|
6
|
+
* 1. 估算整组消息 token;≤ budgetTokens → 原样放行(快路径)。
|
|
7
|
+
* 2. 超预算:先 **context editing**(trimToolPairs 丢旧工具对,不额外调模型);
|
|
8
|
+
* 若裁剪后回到预算内即止。
|
|
9
|
+
* 3. 仍超预算且有 summarize(compaction 摘要器)→ 压缩:旧前缀做成摘要,只留最近 keepRecent 条。
|
|
10
|
+
* 距上次压缩 < compactEvery 个回合则跳过(滞回,防抖)。
|
|
11
|
+
* 4. 无 summarize 时 compaction 不可用,编辑已是上限(返回裁剪后消息)。
|
|
12
|
+
*
|
|
13
|
+
* summarize 未提供时 policy 不会自己调模型 —— 框架不替你造 token(spec:不主动黑名单,
|
|
14
|
+
* 预算只是护栏;真机可注入按 /count_tokens 的 estimate 与走模型的 summarize)。
|
|
15
|
+
*/
|
|
16
|
+
export interface BudgetPolicyOptions {
|
|
17
|
+
/** 预算(估算 input tokens);缺省 60_000 */
|
|
18
|
+
budgetTokens?: number;
|
|
19
|
+
/** compaction 保留的最近消息条数;缺省 20 */
|
|
20
|
+
keepRecent?: number;
|
|
21
|
+
/** token 估算函数(预算决策用,非精确记账);缺省 CJK 感知启发式 */
|
|
22
|
+
estimateTokens?: (text: string) => number;
|
|
23
|
+
/** 是否先编辑再压缩;缺省 true */
|
|
24
|
+
editBeforeCompact?: boolean;
|
|
25
|
+
/** 提供则允许 compaction(旧前缀→摘要)。框架不自动造摘要器 */
|
|
26
|
+
summarize?: (historyText: string) => string | Promise<string>;
|
|
27
|
+
/** 距上次压缩至少隔几个回合才再次压缩(滞回);缺省 1 */
|
|
28
|
+
compactEvery?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function createBudgetPolicy(opts?: BudgetPolicyOptions): ContextPolicy;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { compactMessages, defaultEstimateTokens, estimateMessages, trimToolPairs, } from './context.js';
|
|
2
|
+
export function createBudgetPolicy(opts = {}) {
|
|
3
|
+
const budgetTokens = opts.budgetTokens ?? 60_000;
|
|
4
|
+
const keepRecent = Math.max(1, opts.keepRecent ?? 20);
|
|
5
|
+
const estimate = opts.estimateTokens ?? defaultEstimateTokens;
|
|
6
|
+
const editBeforeCompact = opts.editBeforeCompact ?? true;
|
|
7
|
+
const summarize = opts.summarize;
|
|
8
|
+
const compactEvery = Math.max(1, opts.compactEvery ?? 1);
|
|
9
|
+
let lastCompactAt = Number.NEGATIVE_INFINITY;
|
|
10
|
+
return {
|
|
11
|
+
budgetTokens,
|
|
12
|
+
async beforeTurn(messages, info) {
|
|
13
|
+
if (estimateMessages(messages, estimate) <= budgetTokens)
|
|
14
|
+
return messages;
|
|
15
|
+
// 1) context editing:先丢旧工具对
|
|
16
|
+
let current = messages;
|
|
17
|
+
if (editBeforeCompact) {
|
|
18
|
+
const trimmed = trimToolPairs(current, { keepRecent });
|
|
19
|
+
if (trimmed.length < current.length) {
|
|
20
|
+
current = trimmed;
|
|
21
|
+
if (estimateMessages(current, estimate) <= budgetTokens)
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// 2) compaction:仍超预算且有摘要器
|
|
26
|
+
if (!summarize)
|
|
27
|
+
return current;
|
|
28
|
+
if (info.iteration - lastCompactAt < compactEvery)
|
|
29
|
+
return current;
|
|
30
|
+
lastCompactAt = info.iteration;
|
|
31
|
+
return compactMessages(current, { keepRecent, summarize });
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/engine/policy.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,GACd,MAAM,cAAc,CAAC;AA+BtB,MAAM,UAAU,kBAAkB,CAAC,IAAI,GAAwB,EAAE;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,IAAI,qBAAqB,CAAC;IAC9D,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACjC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC;IACzD,IAAI,aAAa,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAE7C,OAAO;QACL,YAAY;QACZ,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI;YAC7B,IAAI,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,YAAY;gBAAE,OAAO,QAAQ,CAAC;YAE1E,4BAA4B;YAC5B,IAAI,OAAO,GAAG,QAAQ,CAAC;YACvB,IAAI,iBAAiB,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;gBACvD,IAAI,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;oBACpC,OAAO,GAAG,OAAO,CAAC;oBAClB,IAAI,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,YAAY;wBAAE,OAAO,OAAO,CAAC;gBAC1E,CAAC;YACH,CAAC;YAED,0BAA0B;YAC1B,IAAI,CAAC,SAAS;gBAAE,OAAO,OAAO,CAAC;YAC/B,IAAI,IAAI,CAAC,SAAS,GAAG,aAAa,GAAG,YAAY;gBAAE,OAAO,OAAO,CAAC;YAClE,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC;YAC/B,OAAO,eAAe,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SpanError, SpanId, SpanKind, SpanStatus, Trace, TraceId, Usage } from '../core/trace.js';
|
|
2
|
+
/**
|
|
3
|
+
* 内存 TraceRecorder —— v1 实现(spec §9.3)。
|
|
4
|
+
* 显式传 parentSpanId 而非内部栈,保证并行工具调用时父子关系准确。
|
|
5
|
+
* 一次 run 一个 recorder;run 与 trace 1:1。
|
|
6
|
+
*/
|
|
7
|
+
export declare class TraceRecorder {
|
|
8
|
+
readonly traceId: TraceId;
|
|
9
|
+
private readonly spans;
|
|
10
|
+
private readonly index;
|
|
11
|
+
private rootSpanId;
|
|
12
|
+
begin(kind: SpanKind, name: string, parentSpanId: SpanId | null): SpanId;
|
|
13
|
+
get rootStarted(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 结束 span。容错策略:未知 span id 抛错(程序员错误要响亮);
|
|
16
|
+
* 对已结束的 span 幂等忽略(重复 end 视为无害)。event/setAttribute 对未知
|
|
17
|
+
* span 静默忽略 —— 观测不应中断业务,与 end 的严格性刻意区分。
|
|
18
|
+
*/
|
|
19
|
+
end(id: SpanId, patch?: {
|
|
20
|
+
status?: SpanStatus;
|
|
21
|
+
error?: SpanError;
|
|
22
|
+
usage?: Usage;
|
|
23
|
+
}): void;
|
|
24
|
+
event(id: SpanId, name: string, body: unknown): void;
|
|
25
|
+
setAttribute(id: SpanId, key: string, value: string | number | boolean): void;
|
|
26
|
+
snapshot(status: SpanStatus): Trace;
|
|
27
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* 内存 TraceRecorder —— v1 实现(spec §9.3)。
|
|
4
|
+
* 显式传 parentSpanId 而非内部栈,保证并行工具调用时父子关系准确。
|
|
5
|
+
* 一次 run 一个 recorder;run 与 trace 1:1。
|
|
6
|
+
*/
|
|
7
|
+
export class TraceRecorder {
|
|
8
|
+
traceId = randomUUID();
|
|
9
|
+
spans = [];
|
|
10
|
+
index = new Map();
|
|
11
|
+
rootSpanId = null;
|
|
12
|
+
begin(kind, name, parentSpanId) {
|
|
13
|
+
if (kind === 'run') {
|
|
14
|
+
if (this.rootSpanId)
|
|
15
|
+
throw new Error('run root already started');
|
|
16
|
+
this.rootSpanId = randomUUID();
|
|
17
|
+
}
|
|
18
|
+
const id = kind === 'run' ? this.rootSpanId : randomUUID();
|
|
19
|
+
const span = {
|
|
20
|
+
spanId: id,
|
|
21
|
+
traceId: this.traceId,
|
|
22
|
+
parentSpanId,
|
|
23
|
+
kind,
|
|
24
|
+
name,
|
|
25
|
+
status: 'ok',
|
|
26
|
+
startedAt: Date.now(),
|
|
27
|
+
attributes: {},
|
|
28
|
+
events: [],
|
|
29
|
+
};
|
|
30
|
+
this.spans.push(span);
|
|
31
|
+
this.index.set(id, span);
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
get rootStarted() {
|
|
35
|
+
return this.rootSpanId !== null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 结束 span。容错策略:未知 span id 抛错(程序员错误要响亮);
|
|
39
|
+
* 对已结束的 span 幂等忽略(重复 end 视为无害)。event/setAttribute 对未知
|
|
40
|
+
* span 静默忽略 —— 观测不应中断业务,与 end 的严格性刻意区分。
|
|
41
|
+
*/
|
|
42
|
+
end(id, patch = {}) {
|
|
43
|
+
const span = this.index.get(id);
|
|
44
|
+
if (!span)
|
|
45
|
+
throw new Error(`span not found: ${id}`);
|
|
46
|
+
if (span.endedAt !== undefined)
|
|
47
|
+
return; // 幂等:重复 end 忽略
|
|
48
|
+
span.endedAt = Date.now();
|
|
49
|
+
if (patch.status)
|
|
50
|
+
span.status = patch.status;
|
|
51
|
+
if (patch.error)
|
|
52
|
+
span.error = patch.error;
|
|
53
|
+
if (patch.usage)
|
|
54
|
+
span.usage = patch.usage;
|
|
55
|
+
}
|
|
56
|
+
event(id, name, body) {
|
|
57
|
+
this.index.get(id)?.events.push({ time: Date.now(), name, body });
|
|
58
|
+
}
|
|
59
|
+
setAttribute(id, key, value) {
|
|
60
|
+
const span = this.index.get(id);
|
|
61
|
+
if (span)
|
|
62
|
+
span.attributes[key] = value;
|
|
63
|
+
}
|
|
64
|
+
snapshot(status) {
|
|
65
|
+
if (!this.rootSpanId)
|
|
66
|
+
throw new Error('run root not started');
|
|
67
|
+
const totalUsage = {
|
|
68
|
+
inputTokens: 0,
|
|
69
|
+
outputTokens: 0,
|
|
70
|
+
cacheReadTokens: 0,
|
|
71
|
+
cacheCreationTokens: 0,
|
|
72
|
+
};
|
|
73
|
+
for (const s of this.spans) {
|
|
74
|
+
if (!s.usage)
|
|
75
|
+
continue;
|
|
76
|
+
totalUsage.inputTokens += s.usage.inputTokens;
|
|
77
|
+
totalUsage.outputTokens += s.usage.outputTokens;
|
|
78
|
+
totalUsage.cacheReadTokens += s.usage.cacheReadTokens;
|
|
79
|
+
totalUsage.cacheCreationTokens += s.usage.cacheCreationTokens;
|
|
80
|
+
if (s.usage.costEstimate != null) {
|
|
81
|
+
totalUsage.costEstimate = (totalUsage.costEstimate ?? 0) + s.usage.costEstimate;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
traceId: this.traceId,
|
|
86
|
+
rootSpanId: this.rootSpanId,
|
|
87
|
+
spans: [...this.spans],
|
|
88
|
+
status,
|
|
89
|
+
totalUsage,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=tracer.js.map
|