@faapi/agent 3.0.0 → 3.2.0
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 +1 -1
- package/dist/index.d.ts +359 -96
- package/dist/index.js +333 -57
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/providers/openai.ts
|
|
2
2
|
var DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
3
|
-
var RESERVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["provider", "apiKey", "model", "baseURL"]);
|
|
3
|
+
var RESERVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["provider", "apiKey", "model", "baseURL", "models"]);
|
|
4
4
|
var LLMProviderError = class extends Error {
|
|
5
5
|
/** HTTP 状态码(网络错误 / JSON 解析错误为 undefined) */
|
|
6
6
|
status;
|
|
@@ -17,22 +17,40 @@ function createOpenAIProvider(config) {
|
|
|
17
17
|
const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
18
18
|
const apiKey = config.apiKey;
|
|
19
19
|
function buildRequestBody(request) {
|
|
20
|
+
const modelName = request.model ?? Object.keys(config.models)[0];
|
|
21
|
+
const modelConfig = modelName ? config.models[modelName] : void 0;
|
|
20
22
|
const body = {
|
|
21
|
-
model:
|
|
23
|
+
model: modelName,
|
|
22
24
|
messages: request.messages.map(toOpenAIMessage)
|
|
23
25
|
};
|
|
24
26
|
if (request.tools && request.tools.length > 0) {
|
|
25
27
|
body.tools = request.tools.map(toOpenAITool);
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
|
|
29
|
+
const mergedConfig = {};
|
|
29
30
|
for (const key of Object.keys(config)) {
|
|
30
31
|
if (RESERVED_CONFIG_KEYS.has(key)) continue;
|
|
31
32
|
const value = config[key];
|
|
33
|
+
if (value !== void 0) {
|
|
34
|
+
mergedConfig[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (modelConfig) {
|
|
38
|
+
for (const key of Object.keys(modelConfig)) {
|
|
39
|
+
if (RESERVED_CONFIG_KEYS.has(key)) continue;
|
|
40
|
+
const value = modelConfig[key];
|
|
41
|
+
if (value !== void 0) {
|
|
42
|
+
mergedConfig[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const key of Object.keys(mergedConfig)) {
|
|
47
|
+
const value = mergedConfig[key];
|
|
32
48
|
if (value !== void 0 && !(key in body)) {
|
|
33
49
|
body[key] = value;
|
|
34
50
|
}
|
|
35
51
|
}
|
|
52
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
53
|
+
if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
|
|
36
54
|
return body;
|
|
37
55
|
}
|
|
38
56
|
function buildHeaders() {
|
|
@@ -299,6 +317,11 @@ function createProvider(config) {
|
|
|
299
317
|
}
|
|
300
318
|
}
|
|
301
319
|
|
|
320
|
+
// src/trace.ts
|
|
321
|
+
function isTracingToolResult(value) {
|
|
322
|
+
return typeof value === "object" && value !== null && value.__trace === true;
|
|
323
|
+
}
|
|
324
|
+
|
|
302
325
|
// src/reactLoop.ts
|
|
303
326
|
var ReactLoopError = class extends Error {
|
|
304
327
|
/** 配置的 maxTurns 值 */
|
|
@@ -343,14 +366,29 @@ function buildRequestExtras(config) {
|
|
|
343
366
|
maxTokens: config.maxTokens
|
|
344
367
|
};
|
|
345
368
|
}
|
|
369
|
+
function nowMs() {
|
|
370
|
+
return performance.now();
|
|
371
|
+
}
|
|
372
|
+
function extractSubAgentName(toolName) {
|
|
373
|
+
const prefix = "agent.";
|
|
374
|
+
if (toolName.startsWith(prefix)) {
|
|
375
|
+
return toolName.slice(prefix.length);
|
|
376
|
+
}
|
|
377
|
+
return toolName;
|
|
378
|
+
}
|
|
346
379
|
async function reactLoop(input, config) {
|
|
380
|
+
const enableTracing = config.enableTracing ?? true;
|
|
347
381
|
const messages = buildInitialMessages(input, config.systemPrompt);
|
|
348
382
|
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
349
383
|
const extras = buildRequestExtras(config);
|
|
350
384
|
let totalUsage;
|
|
351
385
|
let turns = 0;
|
|
386
|
+
const traceStartedAt = enableTracing ? nowMs() : 0;
|
|
387
|
+
const traceEvents = enableTracing ? [] : void 0;
|
|
352
388
|
while (turns < maxTurns) {
|
|
353
389
|
turns++;
|
|
390
|
+
const llmStartedAt = enableTracing ? nowMs() : 0;
|
|
391
|
+
const inputSnapshot = enableTracing ? [...messages] : void 0;
|
|
354
392
|
const response = await config.provider.complete({
|
|
355
393
|
messages: [...messages],
|
|
356
394
|
...extras
|
|
@@ -358,23 +396,87 @@ async function reactLoop(input, config) {
|
|
|
358
396
|
if (response.usage) {
|
|
359
397
|
totalUsage = accumulateUsage(totalUsage, response.usage);
|
|
360
398
|
}
|
|
399
|
+
if (enableTracing) {
|
|
400
|
+
const llmEndedAt = nowMs();
|
|
401
|
+
traceEvents.push({
|
|
402
|
+
type: "llm_call",
|
|
403
|
+
turn: turns,
|
|
404
|
+
startedAt: llmStartedAt,
|
|
405
|
+
durationMs: llmEndedAt - llmStartedAt,
|
|
406
|
+
model: config.model ?? "",
|
|
407
|
+
inputMessages: inputSnapshot,
|
|
408
|
+
response: response.message,
|
|
409
|
+
stopReason: response.stopReason,
|
|
410
|
+
usage: response.usage
|
|
411
|
+
});
|
|
412
|
+
}
|
|
361
413
|
messages.push(response.message);
|
|
362
414
|
if (response.stopReason !== "tool_calls" || !response.message.toolCalls) {
|
|
415
|
+
const traceEndedAt = enableTracing ? nowMs() : 0;
|
|
363
416
|
return {
|
|
364
417
|
content: response.message.content,
|
|
365
418
|
messages,
|
|
366
419
|
turns,
|
|
367
420
|
stopReason: response.stopReason,
|
|
368
|
-
usage: totalUsage
|
|
421
|
+
usage: totalUsage,
|
|
422
|
+
trace: enableTracing ? {
|
|
423
|
+
agentName: "",
|
|
424
|
+
startedAt: traceStartedAt,
|
|
425
|
+
durationMs: traceEndedAt - traceStartedAt,
|
|
426
|
+
turns,
|
|
427
|
+
usage: totalUsage,
|
|
428
|
+
stopReason: response.stopReason,
|
|
429
|
+
content: response.message.content,
|
|
430
|
+
events: traceEvents
|
|
431
|
+
} : void 0
|
|
369
432
|
};
|
|
370
433
|
}
|
|
371
434
|
for (const toolCall of response.message.toolCalls) {
|
|
435
|
+
const toolStartedAt = enableTracing ? nowMs() : 0;
|
|
372
436
|
let resultStr;
|
|
437
|
+
let rawResult;
|
|
438
|
+
let toolErr;
|
|
439
|
+
let hasError = false;
|
|
373
440
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
441
|
+
rawResult = await config.executeTool(toolCall.name, toolCall.arguments);
|
|
442
|
+
if (isTracingToolResult(rawResult)) {
|
|
443
|
+
resultStr = stringifyResult(rawResult.result);
|
|
444
|
+
} else {
|
|
445
|
+
resultStr = stringifyResult(rawResult);
|
|
446
|
+
}
|
|
376
447
|
} catch (err) {
|
|
448
|
+
hasError = true;
|
|
449
|
+
toolErr = err;
|
|
377
450
|
resultStr = stringifyError(err);
|
|
451
|
+
rawResult = void 0;
|
|
452
|
+
}
|
|
453
|
+
if (enableTracing) {
|
|
454
|
+
const toolEndedAt = nowMs();
|
|
455
|
+
if (isTracingToolResult(rawResult)) {
|
|
456
|
+
traceEvents.push({
|
|
457
|
+
type: "subagent_call",
|
|
458
|
+
turn: turns,
|
|
459
|
+
startedAt: toolStartedAt,
|
|
460
|
+
durationMs: toolEndedAt - toolStartedAt,
|
|
461
|
+
toolCallId: toolCall.id,
|
|
462
|
+
agentName: extractSubAgentName(toolCall.name),
|
|
463
|
+
input: JSON.stringify(toolCall.arguments),
|
|
464
|
+
trace: rawResult.trace,
|
|
465
|
+
result: resultStr
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
traceEvents.push({
|
|
469
|
+
type: "tool_call",
|
|
470
|
+
turn: turns,
|
|
471
|
+
startedAt: toolStartedAt,
|
|
472
|
+
durationMs: toolEndedAt - toolStartedAt,
|
|
473
|
+
toolCallId: toolCall.id,
|
|
474
|
+
name: toolCall.name,
|
|
475
|
+
arguments: toolCall.arguments,
|
|
476
|
+
result: resultStr,
|
|
477
|
+
error: hasError ? stringifyError(toolErr) : void 0
|
|
478
|
+
});
|
|
479
|
+
}
|
|
378
480
|
}
|
|
379
481
|
messages.push({
|
|
380
482
|
role: "tool",
|
|
@@ -389,6 +491,7 @@ async function reactLoop(input, config) {
|
|
|
389
491
|
);
|
|
390
492
|
}
|
|
391
493
|
async function* reactLoopStream(input, config) {
|
|
494
|
+
const enableTracing = config.enableTracing ?? true;
|
|
392
495
|
const messages = buildInitialMessages(input, config.systemPrompt);
|
|
393
496
|
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
394
497
|
const extras = buildRequestExtras(config);
|
|
@@ -396,9 +499,12 @@ async function* reactLoopStream(input, config) {
|
|
|
396
499
|
let turns = 0;
|
|
397
500
|
while (turns < maxTurns) {
|
|
398
501
|
turns++;
|
|
502
|
+
const llmStartedAt = enableTracing ? nowMs() : 0;
|
|
503
|
+
const inputSnapshot = enableTracing ? [...messages] : void 0;
|
|
399
504
|
let turnContent = "";
|
|
400
505
|
let toolCalls;
|
|
401
506
|
let finishReason;
|
|
507
|
+
let turnUsage;
|
|
402
508
|
for await (const chunk of config.provider.stream({
|
|
403
509
|
messages: [...messages],
|
|
404
510
|
...extras
|
|
@@ -415,6 +521,7 @@ async function* reactLoopStream(input, config) {
|
|
|
415
521
|
}
|
|
416
522
|
if (chunk.usage) {
|
|
417
523
|
totalUsage = accumulateUsage(totalUsage, chunk.usage);
|
|
524
|
+
turnUsage = chunk.usage;
|
|
418
525
|
}
|
|
419
526
|
}
|
|
420
527
|
const assistantMessage = {
|
|
@@ -425,6 +532,22 @@ async function* reactLoopStream(input, config) {
|
|
|
425
532
|
assistantMessage.toolCalls = toolCalls;
|
|
426
533
|
}
|
|
427
534
|
messages.push(assistantMessage);
|
|
535
|
+
if (enableTracing) {
|
|
536
|
+
const llmEndedAt = nowMs();
|
|
537
|
+
yield {
|
|
538
|
+
traceEvent: {
|
|
539
|
+
type: "llm_call",
|
|
540
|
+
turn: turns,
|
|
541
|
+
startedAt: llmStartedAt,
|
|
542
|
+
durationMs: llmEndedAt - llmStartedAt,
|
|
543
|
+
model: config.model ?? "",
|
|
544
|
+
inputMessages: inputSnapshot,
|
|
545
|
+
response: assistantMessage,
|
|
546
|
+
stopReason: finishReason ?? "other",
|
|
547
|
+
usage: turnUsage
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
428
551
|
if (finishReason !== "tool_calls" || !toolCalls) {
|
|
429
552
|
yield {
|
|
430
553
|
done: {
|
|
@@ -438,14 +561,57 @@ async function* reactLoopStream(input, config) {
|
|
|
438
561
|
}
|
|
439
562
|
for (const toolCall of toolCalls) {
|
|
440
563
|
yield { toolCall: { name: toolCall.name, arguments: toolCall.arguments } };
|
|
564
|
+
const toolStartedAt = enableTracing ? nowMs() : 0;
|
|
441
565
|
let resultStr;
|
|
566
|
+
let rawResult;
|
|
567
|
+
let toolErr;
|
|
568
|
+
let hasError = false;
|
|
442
569
|
try {
|
|
443
|
-
|
|
444
|
-
|
|
570
|
+
rawResult = await config.executeTool(toolCall.name, toolCall.arguments);
|
|
571
|
+
if (isTracingToolResult(rawResult)) {
|
|
572
|
+
resultStr = stringifyResult(rawResult.result);
|
|
573
|
+
} else {
|
|
574
|
+
resultStr = stringifyResult(rawResult);
|
|
575
|
+
}
|
|
445
576
|
} catch (err) {
|
|
577
|
+
hasError = true;
|
|
578
|
+
toolErr = err;
|
|
446
579
|
resultStr = stringifyError(err);
|
|
580
|
+
rawResult = void 0;
|
|
447
581
|
}
|
|
448
582
|
yield { toolResult: { name: toolCall.name, result: resultStr } };
|
|
583
|
+
if (enableTracing) {
|
|
584
|
+
const toolEndedAt = nowMs();
|
|
585
|
+
if (isTracingToolResult(rawResult)) {
|
|
586
|
+
yield {
|
|
587
|
+
traceEvent: {
|
|
588
|
+
type: "subagent_call",
|
|
589
|
+
turn: turns,
|
|
590
|
+
startedAt: toolStartedAt,
|
|
591
|
+
durationMs: toolEndedAt - toolStartedAt,
|
|
592
|
+
toolCallId: toolCall.id,
|
|
593
|
+
agentName: extractSubAgentName(toolCall.name),
|
|
594
|
+
input: JSON.stringify(toolCall.arguments),
|
|
595
|
+
trace: rawResult.trace,
|
|
596
|
+
result: resultStr
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
} else {
|
|
600
|
+
yield {
|
|
601
|
+
traceEvent: {
|
|
602
|
+
type: "tool_call",
|
|
603
|
+
turn: turns,
|
|
604
|
+
startedAt: toolStartedAt,
|
|
605
|
+
durationMs: toolEndedAt - toolStartedAt,
|
|
606
|
+
toolCallId: toolCall.id,
|
|
607
|
+
name: toolCall.name,
|
|
608
|
+
arguments: toolCall.arguments,
|
|
609
|
+
result: resultStr,
|
|
610
|
+
error: hasError ? stringifyError(toolErr) : void 0
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
449
615
|
messages.push({
|
|
450
616
|
role: "tool",
|
|
451
617
|
content: resultStr,
|
|
@@ -496,7 +662,7 @@ var Agent = class _Agent {
|
|
|
496
662
|
*/
|
|
497
663
|
schemaCache = /* @__PURE__ */ new Map();
|
|
498
664
|
/**
|
|
499
|
-
* @param deps 运行时依赖(访问器 +
|
|
665
|
+
* @param deps 运行时依赖(访问器 + providers Map + defaultProvider + llms + config)
|
|
500
666
|
* @param depth 递归深度(默认 1 = 根 agent;sub-agent 递归时传入 depth+1)
|
|
501
667
|
*/
|
|
502
668
|
constructor(deps, depth = 1) {
|
|
@@ -506,27 +672,38 @@ var Agent = class _Agent {
|
|
|
506
672
|
/**
|
|
507
673
|
* 非流式执行——组装 config 调 [reactLoop](./reactLoop.md)
|
|
508
674
|
*
|
|
675
|
+
* reactLoop 不知 agent 名(只关心循环逻辑),返回的 `result.trace.agentName` 为空字符串。
|
|
676
|
+
* 本方法在 reactLoop 返回后填充 `this.deps.agentName`,让顶层 trace 标识"是哪个 agent 跑的"。
|
|
677
|
+
*
|
|
509
678
|
* @param input 用户输入
|
|
510
|
-
* @
|
|
679
|
+
* @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens / enableTracing
|
|
680
|
+
* (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
|
|
681
|
+
* @returns 最终结果(content + messages + turns + stopReason + usage + trace?)
|
|
511
682
|
* @throws {AgentError} agent 未注册
|
|
512
683
|
* @throws {ReactLoopError} 超出 maxTurns
|
|
513
684
|
* @throws {Error} provider.complete 抛错时立即传播
|
|
514
685
|
*/
|
|
515
|
-
async run(input) {
|
|
516
|
-
const config = await this.buildLoopConfig();
|
|
517
|
-
|
|
686
|
+
async run(input, options) {
|
|
687
|
+
const config = await this.buildLoopConfig(options);
|
|
688
|
+
const result = await reactLoop(input, config);
|
|
689
|
+
if (result.trace) {
|
|
690
|
+
result.trace.agentName = this.deps.agentName;
|
|
691
|
+
}
|
|
692
|
+
return result;
|
|
518
693
|
}
|
|
519
694
|
/**
|
|
520
695
|
* 流式执行——组装 config 调 [reactLoopStream](./reactLoop.md)
|
|
521
696
|
*
|
|
522
697
|
* @param input 用户输入
|
|
698
|
+
* @param options 临时覆盖本次调用的 model(字符串 key)/ temperature / maxTokens
|
|
699
|
+
* (不修改 agent 自身状态,详见 [agentHandle](./agentHandle.md))
|
|
523
700
|
* @yields 流式 chunk(deltaContent / toolCall / toolResult / done)
|
|
524
701
|
* @throws {AgentError} agent 未注册
|
|
525
702
|
* @throws {ReactLoopError} 超出 maxTurns
|
|
526
703
|
* @throws {Error} provider.stream 抛错时立即传播
|
|
527
704
|
*/
|
|
528
|
-
async *stream(input) {
|
|
529
|
-
const config = await this.buildLoopConfig();
|
|
705
|
+
async *stream(input, options) {
|
|
706
|
+
const config = await this.buildLoopConfig(options);
|
|
530
707
|
yield* reactLoopStream(input, config);
|
|
531
708
|
}
|
|
532
709
|
/**
|
|
@@ -570,32 +747,102 @@ var Agent = class _Agent {
|
|
|
570
747
|
/**
|
|
571
748
|
* 组装 ReactLoopConfig
|
|
572
749
|
*
|
|
573
|
-
* 1. 查 agent 元数据(未注册抛 AgentError
|
|
750
|
+
* 1. 查 agent 元数据(未注册抛 AgentError)——用 `getAgent` 拿 AgentCore
|
|
751
|
+
* (LLM-facing 字段:systemPrompt / model / maxTurns)
|
|
574
752
|
* 2. buildToolDefinitions 组装 tool 列表
|
|
575
|
-
* 3. config
|
|
753
|
+
* 3. config 字段优先级(高 → 低):`options` > agent 元数据 > 全局 AgentRuntimeConfig / deps.defaultProvider
|
|
754
|
+
*
|
|
755
|
+
* `options.model` 是字符串 key,由 {@link resolveModelKey} 解析为 provider + model
|
|
756
|
+
* (支持 llms key 精确匹配 / `provider/model` 一体化 / 纯 model 名模糊匹配)。
|
|
757
|
+
* 不传 `options.model` 时用 `deps.defaultProvider` + agent 元数据 `config.model`。
|
|
758
|
+
* 详见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」。
|
|
576
759
|
*/
|
|
577
|
-
async buildLoopConfig() {
|
|
760
|
+
async buildLoopConfig(options) {
|
|
578
761
|
const meta = this.deps.getAgent(this.deps.agentName);
|
|
579
762
|
if (!meta) {
|
|
580
763
|
throw new AgentError(`Agent "${this.deps.agentName}" is not registered`);
|
|
581
764
|
}
|
|
582
765
|
const tools = await this.buildToolDefinitions();
|
|
766
|
+
const { provider, model } = this.resolveModelKey(options?.model, meta);
|
|
767
|
+
const enableTracing = options?.enableTracing ?? this.deps.config?.enableTracing ?? true;
|
|
583
768
|
return {
|
|
584
|
-
provider
|
|
769
|
+
provider,
|
|
585
770
|
systemPrompt: meta.systemPrompt,
|
|
586
|
-
model
|
|
771
|
+
model,
|
|
772
|
+
temperature: options?.temperature,
|
|
773
|
+
maxTokens: options?.maxTokens,
|
|
587
774
|
maxTurns: meta.maxTurns ?? this.deps.config?.maxTurns,
|
|
588
775
|
tools,
|
|
589
|
-
|
|
776
|
+
enableTracing,
|
|
777
|
+
executeTool: async (name, args) => this.executeTool(name, args, enableTracing)
|
|
590
778
|
};
|
|
591
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* 解析 `options.model` 字符串 key → provider + model
|
|
782
|
+
*
|
|
783
|
+
* 规则见 [agentHandle.md](./agentHandle.md) 的「`options.model` 字符串 key 解析规则」:
|
|
784
|
+
* 1. `undefined` → `deps.defaultProvider` + `meta.model`
|
|
785
|
+
* 2. 精确匹配 `deps.providers` 的 key → 该 provider + 其 `models` 第一个 key
|
|
786
|
+
* 3. 含 `/` → `provider/model` 形式,`deps.providers.get(provider)` + 该 model
|
|
787
|
+
* (要求该 model 在 `deps.llms[provider].models` 里)
|
|
788
|
+
* 4. 不含 `/` 且非 provider key → 在所有 provider 的 `models` 里按 model 名查找
|
|
789
|
+
* - 唯一 → 该 provider + 该 model
|
|
790
|
+
* - 多个 → 抛 `AgentError`(要求用 `provider/model` 消歧)
|
|
791
|
+
* - 无 → 抛 `AgentError`
|
|
792
|
+
*
|
|
793
|
+
* @throws {AgentError} key 解析失败(provider/model 不存在或歧义)
|
|
794
|
+
*/
|
|
795
|
+
resolveModelKey(key, meta) {
|
|
796
|
+
if (key === void 0) {
|
|
797
|
+
return { provider: this.deps.defaultProvider, model: meta.model };
|
|
798
|
+
}
|
|
799
|
+
const byProviderKey = this.deps.providers.get(key);
|
|
800
|
+
if (byProviderKey) {
|
|
801
|
+
const llmConfig = this.deps.llms[key];
|
|
802
|
+
const firstModel = llmConfig ? Object.keys(llmConfig.models)[0] : void 0;
|
|
803
|
+
return { provider: byProviderKey, model: firstModel ?? meta.model };
|
|
804
|
+
}
|
|
805
|
+
if (key.includes("/")) {
|
|
806
|
+
const slashIdx = key.indexOf("/");
|
|
807
|
+
const providerName = key.slice(0, slashIdx);
|
|
808
|
+
const modelName = key.slice(slashIdx + 1);
|
|
809
|
+
const provider = this.deps.providers.get(providerName);
|
|
810
|
+
if (!provider) {
|
|
811
|
+
throw new AgentError(`Unknown provider "${providerName}" in model key "${key}"`);
|
|
812
|
+
}
|
|
813
|
+
const llmConfig = this.deps.llms[providerName];
|
|
814
|
+
if (!llmConfig || !llmConfig.models[modelName]) {
|
|
815
|
+
throw new AgentError(
|
|
816
|
+
`Model "${modelName}" not found in provider "${providerName}". Declare it in config.agent.llms.${providerName}.models.`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
return { provider, model: modelName };
|
|
820
|
+
}
|
|
821
|
+
const matches = [];
|
|
822
|
+
for (const [providerName, provider] of this.deps.providers) {
|
|
823
|
+
const llmConfig = this.deps.llms[providerName];
|
|
824
|
+
if (llmConfig && llmConfig.models[key]) {
|
|
825
|
+
matches.push({ provider, providerName });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (matches.length === 1) {
|
|
829
|
+
return { provider: matches[0].provider, model: key };
|
|
830
|
+
}
|
|
831
|
+
if (matches.length > 1) {
|
|
832
|
+
throw new AgentError(
|
|
833
|
+
`Model "${key}" is ambiguous (found in providers: ${matches.map((m) => m.providerName).join(", ")}). Use "provider/model" to disambiguate.`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
throw new AgentError(
|
|
837
|
+
`Model "${key}" not found in any provider. Declare it in config.agent.llms.*.models.`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
592
840
|
/**
|
|
593
841
|
* 组装 LLM 可见 tool 列表
|
|
594
842
|
*
|
|
595
|
-
*
|
|
843
|
+
* 合并两个来源(按 `name` 去重,先入者保留):
|
|
596
844
|
* 1. **resolveAgentTools** —— agent 显式声明的 `tools` 引用
|
|
597
|
-
* 2.
|
|
598
|
-
* 3. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
|
|
845
|
+
* 2. **sub-agent** —— `resolveSubAgents` 每个包装为 `agent.<name>`
|
|
599
846
|
*
|
|
600
847
|
* 每个常规 tool 的 `input`:
|
|
601
848
|
* - `resolveToolSchema` 提供 → 用其 `jsonSchema`
|
|
@@ -614,20 +861,6 @@ var Agent = class _Agent {
|
|
|
614
861
|
input: schemaRes?.jsonSchema ?? { type: "object" }
|
|
615
862
|
});
|
|
616
863
|
}
|
|
617
|
-
const defaultTools = this.deps.config?.defaultTools;
|
|
618
|
-
if (defaultTools) {
|
|
619
|
-
for (const toolName of defaultTools) {
|
|
620
|
-
if (definitions.has(toolName)) continue;
|
|
621
|
-
const tool = this.deps.getTool(toolName);
|
|
622
|
-
if (!tool) continue;
|
|
623
|
-
const schemaRes = await this.getToolSchema(tool);
|
|
624
|
-
definitions.set(tool.name, {
|
|
625
|
-
name: tool.name,
|
|
626
|
-
description: tool.description,
|
|
627
|
-
input: schemaRes?.jsonSchema ?? { type: "object" }
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
864
|
for (const subAgent of this.deps.resolveSubAgents(this.deps.agentName)) {
|
|
632
865
|
const name = `agent.${subAgent.name}`;
|
|
633
866
|
if (definitions.has(name)) continue;
|
|
@@ -642,17 +875,20 @@ var Agent = class _Agent {
|
|
|
642
875
|
/**
|
|
643
876
|
* tool 执行路由(由 reactLoop 调用)
|
|
644
877
|
*
|
|
645
|
-
* - `agent.` 前缀 → {@link executeSubAgent}
|
|
878
|
+
* - `agent.` 前缀 → {@link executeSubAgent} 递归(含 enableTracing + TracingToolResult 包装)
|
|
646
879
|
* - 常规 tool → `loadToolModule` 加载 handler + 可选 input 校验 → 调用
|
|
647
880
|
*
|
|
881
|
+
* `enableTracing` 由 [buildLoopConfig](#buildLoopConfig) 闭包捕获传入,用于 sub-agent
|
|
882
|
+
* 调用时决定是否包装 [TracingToolResult](./trace.md) 携带 sub-trace。
|
|
883
|
+
*
|
|
648
884
|
* **常规 tool 校验失败**:不抛错,返回 `{ error }` 对象——reactLoop stringify 后
|
|
649
885
|
* 作为 tool 结果回传 LLM,LLM 可据此修正参数重试。
|
|
650
886
|
*
|
|
651
887
|
* **tool 未找到 / 加载失败**:抛错,被 reactLoop catch 后同样回传 LLM。
|
|
652
888
|
*/
|
|
653
|
-
async executeTool(name, args) {
|
|
889
|
+
async executeTool(name, args, enableTracing) {
|
|
654
890
|
if (name.startsWith("agent.")) {
|
|
655
|
-
return this.executeSubAgent(name.slice(6), args);
|
|
891
|
+
return this.executeSubAgent(name.slice(6), args, enableTracing);
|
|
656
892
|
}
|
|
657
893
|
const tool = this.deps.getTool(name);
|
|
658
894
|
if (!tool) {
|
|
@@ -674,13 +910,26 @@ var Agent = class _Agent {
|
|
|
674
910
|
* sub-agent 递归执行
|
|
675
911
|
*
|
|
676
912
|
* 1. `maxAgentDepth` 防护——超限抛 {@link AgentRecursionError}
|
|
677
|
-
* 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)
|
|
678
|
-
* 3. 无 `run` 时调 `subAgent.run(
|
|
913
|
+
* 2. sub-agent handler 导出 `run` 时调自定义 `mod.run(args)`(无 trace,与常规 tool 一致)
|
|
914
|
+
* 3. 无 `run` 时调 `subAgent.run(stringify(args), { enableTracing })` 走默认 reactLoop
|
|
915
|
+
*
|
|
916
|
+
* **tracing 路径**:`enableTracing=true` 时,subAgent.run 返回的 `result.trace`(agentName
|
|
917
|
+
* 已被 `Agent.run` 填为 subName)被包装为 [TracingToolResult](./trace.md) 返回给 reactLoop。
|
|
918
|
+
* reactLoop 通过 `isTracingToolResult` 识别后发出 `subagent_call` 事件,嵌入 sub-trace
|
|
919
|
+
* (递归结构,业务方可还原完整调用树)。`enableTracing=false` 时返回 `result.content`
|
|
920
|
+
* (unknown,与常规 tool 一致,零开销)。
|
|
921
|
+
*
|
|
922
|
+
* **自定义 run 无 trace**:业务方导出 `run` 函数时直接返回业务结果,无法采集 sub-agent
|
|
923
|
+
* 内部明细——需 trace 时应让 sub-agent 走默认 reactLoop(不导出 `run`)。
|
|
679
924
|
*
|
|
680
925
|
* 自定义 run 接收原始 args 对象;默认 reactLoop 接收 stringify 后的 args
|
|
681
926
|
* 作为 user 消息(agent-as-tool input 为开放式 JSON)。
|
|
927
|
+
*
|
|
928
|
+
* 加载 handler.js 用 `getAgentEntry`(返回 AgentMetadata,含 filePath/hasRun),
|
|
929
|
+
* 而非 `getAgent`(返回 AgentCore,无代码加载细节)。DB skill 无文件,
|
|
930
|
+
* `getAgentEntry` 返回 `undefined`,走默认 reactLoop。
|
|
682
931
|
*/
|
|
683
|
-
async executeSubAgent(subName, args) {
|
|
932
|
+
async executeSubAgent(subName, args, enableTracing) {
|
|
684
933
|
const newDepth = this.depth + 1;
|
|
685
934
|
const maxDepth = this.deps.config?.maxAgentDepth ?? DEFAULT_MAX_AGENT_DEPTH;
|
|
686
935
|
if (newDepth > maxDepth) {
|
|
@@ -688,14 +937,23 @@ var Agent = class _Agent {
|
|
|
688
937
|
}
|
|
689
938
|
const subDeps = { ...this.deps, agentName: subName };
|
|
690
939
|
const subAgent = new _Agent(subDeps, newDepth);
|
|
691
|
-
const
|
|
692
|
-
if (
|
|
693
|
-
const mod = await this.deps.loadAgentModule(
|
|
940
|
+
const entry = this.deps.getAgentEntry(subName);
|
|
941
|
+
if (entry?.hasRun) {
|
|
942
|
+
const mod = await this.deps.loadAgentModule(entry.filePath, entry.hasRun);
|
|
694
943
|
if (mod.run) {
|
|
695
944
|
return await mod.run(args);
|
|
696
945
|
}
|
|
697
946
|
}
|
|
698
|
-
const result = await subAgent.run(typeof args === "string" ? args : JSON.stringify(args)
|
|
947
|
+
const result = await subAgent.run(typeof args === "string" ? args : JSON.stringify(args), {
|
|
948
|
+
enableTracing
|
|
949
|
+
});
|
|
950
|
+
if (enableTracing && result.trace) {
|
|
951
|
+
return {
|
|
952
|
+
__trace: true,
|
|
953
|
+
result: result.content,
|
|
954
|
+
trace: result.trace
|
|
955
|
+
};
|
|
956
|
+
}
|
|
699
957
|
return result.content;
|
|
700
958
|
}
|
|
701
959
|
};
|
|
@@ -704,6 +962,7 @@ var Agent = class _Agent {
|
|
|
704
962
|
import {
|
|
705
963
|
registerAgentHandleFactory,
|
|
706
964
|
getAgent,
|
|
965
|
+
getAgentEntry,
|
|
707
966
|
getTool,
|
|
708
967
|
resolveAgentTools,
|
|
709
968
|
resolveSubAgents,
|
|
@@ -736,9 +995,9 @@ var agentPlugin = {
|
|
|
736
995
|
name: "@faapi/agent",
|
|
737
996
|
setup(ctx) {
|
|
738
997
|
const agentConfig = readAgentConfig(ctx);
|
|
739
|
-
if (!agentConfig?.
|
|
998
|
+
if (!agentConfig?.llms) {
|
|
740
999
|
console.warn(
|
|
741
|
-
"! @faapi/agent: config.agent.
|
|
1000
|
+
"! @faapi/agent: config.agent.llms not configured, agent parameter injection disabled"
|
|
742
1001
|
);
|
|
743
1002
|
return;
|
|
744
1003
|
}
|
|
@@ -748,36 +1007,52 @@ var agentPlugin = {
|
|
|
748
1007
|
);
|
|
749
1008
|
return;
|
|
750
1009
|
}
|
|
751
|
-
const
|
|
1010
|
+
const llms = agentConfig.llms;
|
|
1011
|
+
const providers = /* @__PURE__ */ new Map();
|
|
1012
|
+
for (const [name, llmConfig] of Object.entries(llms)) {
|
|
1013
|
+
providers.set(name, createProvider(llmConfig));
|
|
1014
|
+
}
|
|
1015
|
+
const defaultLlm = agentConfig.defaultLlm ?? Object.keys(llms)[0];
|
|
1016
|
+
const defaultProvider = providers.get(defaultLlm);
|
|
1017
|
+
if (!defaultProvider) {
|
|
1018
|
+
console.warn(
|
|
1019
|
+
`! @faapi/agent: config.agent.defaultLlm "${defaultLlm}" not found in llms, agent parameter injection disabled`
|
|
1020
|
+
);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
752
1023
|
const runtimeConfig = {
|
|
753
1024
|
maxTurns: agentConfig.maxTurns,
|
|
754
|
-
maxAgentDepth: agentConfig.maxAgentDepth
|
|
755
|
-
defaultTools: agentConfig.defaultTools
|
|
1025
|
+
maxAgentDepth: agentConfig.maxAgentDepth
|
|
756
1026
|
};
|
|
757
1027
|
const rootDir = ctx.rootDir;
|
|
758
1028
|
const defaultAgent = agentConfig.defaultAgent;
|
|
759
1029
|
const resolveToolSchema = (tool) => resolveToolSchemaImpl(tool, rootDir);
|
|
760
1030
|
registerAgentHandleFactory(() => {
|
|
761
1031
|
return new Agent({
|
|
762
|
-
|
|
1032
|
+
providers,
|
|
1033
|
+
defaultProvider,
|
|
1034
|
+
llms,
|
|
1035
|
+
defaultLlm,
|
|
763
1036
|
agentName: defaultAgent,
|
|
764
1037
|
rootDir,
|
|
765
1038
|
config: runtimeConfig,
|
|
766
1039
|
// 注册表/加载器访问器——从 @faapi/faapi import 的单例模块
|
|
767
1040
|
// createAppBase 启动时已水合 agentRegistry / toolRegistry
|
|
1041
|
+
// getAgent 返回 AgentCore(LLM-facing);getAgentEntry 返回 AgentMetadata(含 filePath/hasRun,供加载 handler.js)
|
|
768
1042
|
getAgent,
|
|
1043
|
+
getAgentEntry,
|
|
769
1044
|
getTool,
|
|
770
1045
|
resolveAgentTools,
|
|
771
1046
|
resolveSubAgents,
|
|
772
1047
|
// 加载器包装:注入 rootDir 用于 dev 按需编译模式
|
|
773
1048
|
loadToolModule: (filePath, functionName) => loadToolModule(filePath, functionName, rootDir),
|
|
774
|
-
loadAgentModule: (filePath,
|
|
1049
|
+
loadAgentModule: (filePath, hasRun) => loadAgentModule(filePath, hasRun, rootDir),
|
|
775
1050
|
// tool schema 解析(zod.js → JSON Schema + safeParse 校验)
|
|
776
1051
|
resolveToolSchema
|
|
777
1052
|
});
|
|
778
1053
|
});
|
|
779
1054
|
console.log(
|
|
780
|
-
`- @faapi/agent: default agent "${defaultAgent}" available via agent parameter injection`
|
|
1055
|
+
`- @faapi/agent: default agent "${defaultAgent}" (provider: ${defaultLlm}) available via agent parameter injection`
|
|
781
1056
|
);
|
|
782
1057
|
}
|
|
783
1058
|
};
|
|
@@ -791,6 +1066,7 @@ export {
|
|
|
791
1066
|
createOpenAIProvider,
|
|
792
1067
|
createProvider,
|
|
793
1068
|
plugin_default as default,
|
|
1069
|
+
isTracingToolResult,
|
|
794
1070
|
reactLoop,
|
|
795
1071
|
reactLoopStream
|
|
796
1072
|
};
|