Tool execution metrics (#21587)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21587?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Abdul Rahman
2026-06-18 14:28:47 +05:30
committed by GitHub
parent 8034c7725f
commit ccc77932a0
15 changed files with 310 additions and 28 deletions
@@ -4,6 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { generateText } from 'ai';
import { BillingUsageService } from 'src/engine/core-modules/billing/services/billing-usage.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { ToolRegistryService } from 'src/engine/core-modules/tool-provider/services/tool-registry.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentAsyncExecutorService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service';
@@ -111,6 +112,12 @@ describe('AgentAsyncExecutorService — workflow agent role-scoped tool resoluti
hasAvailableCreditsOrThrow: jest.fn().mockResolvedValue(undefined),
},
},
{
provide: MetricsService,
useValue: {
incrementCounterForEvent: jest.fn(),
},
},
{
provide: getWorkspaceScopedRepositoryToken(RoleTargetEntity),
useValue: roleTargetRepository,
@@ -18,8 +18,13 @@ import { type Repository } from 'typeorm';
import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard';
import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
import { BillingUsageService } from 'src/engine/core-modules/billing/services/billing-usage.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type';
import { ToolRegistryService } from 'src/engine/core-modules/tool-provider/services/tool-registry.service';
import { estimateToolOutputTokens } from 'src/engine/core-modules/tool-provider/utils/estimate-tool-output-tokens.util';
import { getToolMetricName } from 'src/engine/core-modules/tool-provider/utils/get-tool-metric-name.util';
import { isToolOutputSuccessful } from 'src/engine/core-modules/tool-provider/utils/is-tool-output-successful.util';
import { UsageOperationType } from 'src/engine/core-modules/usage/enums/usage-operation-type.enum';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WORKFLOW_AGENT_REGISTRY_TOOL_CATEGORIES } from 'src/engine/metadata-modules/ai/ai-agent-execution/constants/workflow-agent-registry-tool-categories.const';
@@ -80,6 +85,7 @@ export class AgentAsyncExecutorService {
private readonly nativeToolBinder: NativeToolBinderService,
private readonly aiBillingService: AiBillingService,
private readonly billingUsageService: BillingUsageService,
private readonly metricsService: MetricsService,
@InjectWorkspaceScopedRepository(RoleTargetEntity)
private readonly roleTargetRepository: WorkspaceScopedRepository<RoleTargetEntity>,
@InjectRepository(WorkspaceEntity)
@@ -236,6 +242,38 @@ export class AgentAsyncExecutorService {
if (stepHasNoMoreAvailableCredits) {
hasNoMoreAvailableCredits = true;
}
for (const part of step.content) {
if (part.type !== 'tool-result' && part.type !== 'tool-error') {
continue;
}
const succeeded =
part.type === 'tool-result' &&
isToolOutputSuccessful(part.output);
const toolAttributes = {
model: registeredModel.modelId,
tool: getToolMetricName(part.toolName),
};
this.metricsService.incrementCounterBy({
key: succeeded
? MetricsKeys.WorkflowAgentToolExecutionSucceeded
: MetricsKeys.WorkflowAgentToolExecutionFailed,
amount: 1,
attributes: toolAttributes,
});
this.metricsService.recordHistogram({
key: MetricsKeys.WorkflowAgentToolOutputTokens,
value: estimateToolOutputTokens(
part.type === 'tool-result' ? part.output : part.error,
),
unit: 'token',
attributes: toolAttributes,
});
}
},
experimental_repairToolCall: async ({
toolCall,