refactor: minimize llmRequester.request

This commit is contained in:
_Kerman 2026-07-03 13:51:19 +08:00
parent 70f936acad
commit 3c62637b67
8 changed files with 79 additions and 67 deletions

View file

@ -202,6 +202,7 @@ llmRequester --> usage #34495E
llmRequester --> modelProvider #34495E
llmRequester --> config #34495E
toolExecutor --> toolRegistry #34495E
toolExecutor --> record #34495E
toolExecutor --> telemetry #34495E
toolStore --> wireRecord #34495E
toolDedup --> telemetry #34495E

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Before After
Before After

View file

@ -1,7 +1,5 @@
import { randomUUID } from 'node:crypto';
import type { AgentEvent } from '@moonshot-ai/protocol';
import { InstantiationType } from '#/_base/di/extensions';
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
import { IAgentLLMRequesterService, type LLMRequestFinish } from '#/agent/llmRequester';
@ -151,11 +149,8 @@ export class AgentLoopService implements IAgentLoopService {
const stepUuid = randomUUID();
const turnStep = `${turnId}.${String(currentStep)}`;
const emit = (event: AgentEvent): void => {
this.record.signal(event);
};
emit({ type: 'turn.step.started', turnId, step: currentStep, stepId: stepUuid });
this.record.signal({ type: 'turn.step.started', turnId, step: currentStep, stepId: stepUuid });
const emitStreamPart = this.createStreamPartHandler(turnId);
const response = await this.llmRequester.request(
@ -164,7 +159,7 @@ export class AgentLoopService implements IAgentLoopService {
retry: {
maxAttempts: this.config.get<LoopControl>(LOOP_CONTROL_SECTION)?.maxRetriesPerStep,
onRetry: (retry) => {
emit({
this.record.signal({
type: 'turn.step.retrying',
turnId,
step: currentStep,
@ -201,7 +196,6 @@ export class AgentLoopService implements IAgentLoopService {
const toolResults = await this.toolExecutor.execute(response.message.toolCalls, {
signal,
turnId,
dispatchProtocolEvent: emit,
onToolResult: (toolCallId, result) => {
this.append({
...createToolMessage(toolCallId, toolResultOutputForModel(result)),
@ -209,9 +203,6 @@ export class AgentLoopService implements IAgentLoopService {
isError: result.isError,
});
},
onProgress: (toolCallId, update) => {
emit({ type: 'tool.progress', turnId, toolCallId, update });
},
});
if (toolResults.some((r) => r.stopTurn === true)) {
finishReason = 'completed';

View file

@ -1,11 +1,9 @@
import { createDecorator } from '#/_base/di';
import type {
ToolResult,
ToolUpdate,
ToolDidExecuteContext,
ToolWillExecuteContext,
} from '#/agent/tool';
import type { AgentEvent } from '@moonshot-ai/protocol';
import type { ToolCall } from '#/app/llmProtocol';
import type { OrderedHookSlot } from '#/hooks';
@ -13,8 +11,6 @@ export interface ToolExecutorExecuteOptions {
readonly signal: AbortSignal;
readonly turnId: number;
readonly onToolResult?: (toolCallId: string, result: ToolResult) => void | Promise<void>;
readonly dispatchProtocolEvent?: (event: AgentEvent) => void;
readonly onProgress?: (toolCallId: string, update: ToolUpdate) => void;
}
export interface IAgentToolExecutorService {

View file

@ -12,6 +12,7 @@ import {
import { PathSecurityError } from '#/_base/tools/policies/path-access';
import { isUserCancellation } from "#/_base/utils/abort";
import { isAbortError } from '#/agent/loop/errors';
import { IAgentRecordService } from '#/agent/record';
import {
ToolAccesses,
type ExecutableTool,
@ -20,6 +21,7 @@ import {
type ToolDidExecuteContext,
type ToolExecution,
type ToolResult,
type ToolUpdate,
type ToolWillExecuteContext,
} from '#/agent/tool';
import { IAgentToolRegistryService } from '#/agent/toolRegistry';
@ -58,6 +60,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
constructor(
@IAgentToolRegistryService private readonly toolRegistry: IAgentToolRegistryService,
@IAgentRecordService private readonly record: IAgentRecordService,
@ITelemetryService private readonly telemetry: ITelemetryService,
@ILogService private readonly log?: ILogService,
) {}
@ -107,7 +110,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
const finalized = await this.finalizeToolResult(call, rawResult, options);
results.push(finalized);
await dispatchToolResult(call, finalized, options);
await this.dispatchToolResult(call, finalized, options);
this.trackToolCall(call, finalized, timedResult.durationMs, options.turnId);
}
@ -145,7 +148,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
output: string,
displayFields?: ToolCallDisplayFields,
): { task: ToolExecutionTask } => {
dispatchToolCall(call, args, options, displayFields);
this.dispatchToolCall(call, args, options, displayFields);
return {
task: makeResolvedTask(makeErrorToolResult(call, args, output)),
};
@ -160,7 +163,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
stopBatchAfterThis?: boolean;
} => {
const toolResult = this.normalizeAndMergeResult(result, call.toolName, undefined);
dispatchToolCall(call, args, options, displayFields);
this.dispatchToolCall(call, args, options, displayFields);
return {
task: makeResolvedTask({
toolCall: call.toolCall,
@ -223,7 +226,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
const executionMetadata = decision?.executionMetadata;
dispatchToolCall(call, call.args, options, displayFields);
this.dispatchToolCall(call, call.args, options, displayFields);
return {
task: {
@ -240,7 +243,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
options: ToolExecutorExecuteOptions,
): ToolExecutionTask {
const output = 'Tool skipped because a previous tool call stopped the turn.';
dispatchToolCall(call, call.args, options);
this.dispatchToolCall(call, call.args, options);
return makeResolvedTask(makeErrorToolResult(call, call.args, output));
}
@ -295,7 +298,7 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
signal,
onUpdate: (update) => {
if (signal.aborted) return;
options.onProgress?.(call.toolCall.id, update);
this.dispatchToolProgress(call, update, options);
},
});
rawResult = await raceWithGraceTimeout(executePromise, signal, call.toolName);
@ -326,6 +329,51 @@ export class AgentToolExecutorService implements IAgentToolExecutorService {
};
}
private dispatchToolCall(
call: PreflightedToolCall,
args: unknown,
options: ToolExecutorExecuteOptions,
displayFields?: ToolCallDisplayFields,
): void {
this.record.signal({
type: 'tool.call.started',
turnId: options.turnId,
toolCallId: call.toolCall.id,
name: call.toolName,
args,
description: displayFields?.description,
display: displayFields?.display,
});
}
private async dispatchToolResult(
call: PreflightedToolCall,
result: ToolResult,
options: ToolExecutorExecuteOptions,
): Promise<void> {
await options.onToolResult?.(call.toolCall.id, result);
this.record.signal({
type: 'tool.result',
turnId: options.turnId,
toolCallId: call.toolCall.id,
output: result.output,
isError: result.isError,
});
}
private dispatchToolProgress(
call: RunnableToolCall,
update: ToolUpdate,
options: ToolExecutorExecuteOptions,
): void {
this.record.signal({
type: 'tool.progress',
turnId: options.turnId,
toolCallId: call.toolCall.id,
update,
});
}
private async finalizeToolResult(
call: PreflightedToolCall,
result: ToolResult,
@ -504,38 +552,6 @@ function toolCallDisplayFieldsFromExecution(
};
}
function dispatchToolCall(
call: PreflightedToolCall,
args: unknown,
options: ToolExecutorExecuteOptions,
displayFields?: ToolCallDisplayFields,
): void {
options.dispatchProtocolEvent?.({
type: 'tool.call.started',
turnId: options.turnId,
toolCallId: call.toolCall.id,
name: call.toolName,
args,
description: displayFields?.description,
display: displayFields?.display,
});
}
async function dispatchToolResult(
call: PreflightedToolCall,
result: ToolResult,
options: ToolExecutorExecuteOptions,
): Promise<void> {
await options.onToolResult?.(call.toolCall.id, result);
options.dispatchProtocolEvent?.({
type: 'tool.result',
turnId: options.turnId,
toolCallId: call.toolCall.id,
output: result.output,
isError: result.isError,
});
}
function makeResolvedTask(result: PreparedToolResult): ToolExecutionTask {
return {
accesses: ToolAccesses.none(),

View file

@ -16,8 +16,10 @@ import { IAgentToolRegistryService } from '#/agent/toolRegistry';
import { AgentToolRegistryService } from '#/agent/toolRegistry/toolRegistryService';
import { IAgentTurnService } from '#/agent/turn';
import { IAgentProfileService } from '#/agent/profile';
import { IAgentRecordService } from '#/agent/record';
import { createTestAgent, mcpServices, type TestAgentContext } from '../harness';
import { stubRecord } from '../contextMemory/stubs';
import { stubTurnWithHooks } from '../turn/stubs';
import { discoverTools, executeTool, fakeMcpClient } from './stubs';
@ -139,6 +141,7 @@ describe('AgentMcpService', () => {
on: () => toDisposable(() => {}),
});
ix.set(IAgentToolRegistryService, new SyncDescriptor(AgentToolRegistryService));
ix.stub(IAgentRecordService, stubRecord());
ix.set(IAgentToolExecutorService, new SyncDescriptor(AgentToolExecutorService));
ix.stub(IAgentTurnService, stubTurnWithHooks());
});

View file

@ -5,6 +5,7 @@ import { createServices } from '#/_base/di/test';
import { ITelemetryService } from '#/app/telemetry';
import type { ToolCall } from '#/app/llmProtocol';
import { IAgentLoopService } from '#/agent/loop';
import { IAgentRecordService } from '#/agent/record';
import type { ExecutableTool, ExecutableToolContext, ToolExecution } from '#/agent/tool';
import {
IAgentToolDedupeService,
@ -15,6 +16,7 @@ import type { ToolDedupResult } from '#/agent/toolDedupe';
import { AgentToolExecutorService, IAgentToolExecutorService } from '#/agent/toolExecutor';
import { AgentToolRegistryService, IAgentToolRegistryService } from '#/agent/toolRegistry';
import { IAgentTurnService } from '#/agent/turn';
import { stubRecord } from '../contextMemory/stubs';
import { registerLogServices } from '../log/stubs';
import { recordingTelemetry, type TelemetryRecord } from '../telemetry/stubs';
import { stubLoopWithHooks, stubToolExecutor, stubTurnWithHooks } from '../turn/stubs';
@ -159,6 +161,7 @@ describe('AgentToolDedupeService', () => {
additionalServices: (reg) => {
reg.defineInstance(ITelemetryService, recordingTelemetry(telemetryEvents));
reg.defineInstance(IAgentLoopService, loop);
reg.defineInstance(IAgentRecordService, stubRecord());
reg.defineInstance(IAgentTurnService, stubTurnWithHooks());
reg.define(IAgentToolRegistryService, AgentToolRegistryService);
reg.define(IAgentToolExecutorService, AgentToolExecutorService);

View file

@ -2,18 +2,21 @@ import type { ToolCall } from '#/app/llmProtocol/kosong';
import type { AgentEvent, ToolInputDisplay } from '@moonshot-ai/protocol';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { SyncDescriptor } from '#/_base/di/descriptors';
import { DisposableStore } from '#/_base/di/lifecycle';
import { createServices, type TestInstantiationService } from '#/_base/di/test';
import { AgentRecordService, IAgentRecordService } from '#/agent/record';
import { ToolAccesses, type ExecutableTool, type ExecutableToolContext, type ExecutableToolResult, type ToolExecution, type ToolResult, type ToolUpdate } from '#/agent/tool';
import { IAgentToolExecutorService, AgentToolExecutorService, parseToolCallArguments } from '#/agent/toolExecutor';
import { IAgentToolRegistryService, AgentToolRegistryService } from '#/agent/toolRegistry';
import { IAgentWireRecordService } from '#/agent/wireRecord';
import { ITelemetryService } from '#/app/telemetry';
import { stubWireRecord } from '../contextMemory/stubs';
import { registerLogServices } from '../log/stubs';
import { recordingTelemetry, type TelemetryRecord } from '../telemetry/stubs';
type ToolExecutorEvent =
| { readonly type: 'tool.result'; readonly toolCallId: string; readonly result: ToolResult }
| { readonly type: 'tool.progress'; readonly toolCallId: string; readonly update: ToolUpdate };
| { readonly type: 'tool.result'; readonly toolCallId: string; readonly result: ToolResult };
let disposables: DisposableStore;
let ix: TestInstantiationService;
@ -32,11 +35,16 @@ beforeEach(() => {
additionalServices: (reg) => {
reg.define(IAgentToolRegistryService, AgentToolRegistryService);
reg.define(IAgentToolExecutorService, AgentToolExecutorService);
reg.defineInstance(IAgentWireRecordService, stubWireRecord());
reg.defineInstance(ITelemetryService, recordingTelemetry(telemetryEvents));
registerLogServices(reg);
},
strict: true,
});
ix.set(IAgentRecordService, new SyncDescriptor(AgentRecordService, [{}]));
ix.get(IAgentRecordService).on((event) => {
protocolEvents.push(event);
});
executor = ix.get(IAgentToolExecutorService);
registry = ix.get(IAgentToolRegistryService);
});
@ -359,9 +367,9 @@ describe('AgentToolExecutorService', () => {
await execute([toolCall('call_progress', 'progress', {})]);
expect(events.filter((event) => event.type === 'tool.progress')).toEqual([
{ type: 'tool.progress', toolCallId: 'call_progress', update: updates[0] },
{ type: 'tool.progress', toolCallId: 'call_progress', update: updates[1] },
expect(protocolEvents.filter((event) => event.type === 'tool.progress')).toEqual([
{ type: 'tool.progress', turnId: 0, toolCallId: 'call_progress', update: updates[0] },
{ type: 'tool.progress', turnId: 0, toolCallId: 'call_progress', update: updates[1] },
]);
});
@ -534,12 +542,6 @@ function execute(calls: ToolCall[], signal?: AbortSignal): Promise<ToolResult[]>
onToolResult: (toolCallId, result) => {
events.push({ type: 'tool.result', toolCallId, result });
},
dispatchProtocolEvent: (event) => {
protocolEvents.push(event);
},
onProgress: (toolCallId, update) => {
events.push({ type: 'tool.progress', toolCallId, update });
},
});
}
@ -568,9 +570,9 @@ function pairedToolCallIds(): { readonly calls: string[]; readonly results: stri
event.type === 'tool.call.started',
)
.map((event) => event.toolCallId),
results: events
results: protocolEvents
.filter(
(event): event is Extract<ToolExecutorEvent, { type: 'tool.result' }> =>
(event): event is Extract<AgentEvent, { type: 'tool.result' }> =>
event.type === 'tool.result',
)
.map((event) => event.toolCallId),