agent usage

This commit is contained in:
archer 2025-12-29 13:26:58 +08:00
parent fe8e6486d3
commit 249fa3e30d
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
6 changed files with 320 additions and 257 deletions

View File

@ -17,6 +17,7 @@ import { computedMaxToken } from '../../utils';
import { filterGPTMessageByMaxContext } from '../utils';
import { getLLMModel } from '../../model';
import { filterEmptyAssistantMessages } from './utils';
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
type RunAgentCallProps = {
maxRunAgentTimes: number;
@ -30,6 +31,7 @@ type RunAgentCallProps = {
stream?: boolean;
};
usagePush: (usages: ChatNodeUsageType[]) => void;
userKey?: CreateLLMResponseProps['userKey'];
isAborted?: CreateLLMResponseProps['isAborted'];
@ -85,6 +87,8 @@ type RunAgentResponse = {
export const runAgentCall = async ({
maxRunAgentTimes,
body: { model, messages, max_tokens, tools, ...body },
usagePush,
userKey,
isAborted,
@ -159,6 +163,7 @@ export const runAgentCall = async ({
// 只需要推送本轮产生的 assistantMessages
assistantMessages.push(...filterEmptyAssistantMessages(toolAssistantMessages));
subAppUsages.push(...usages);
usagePush(usages);
// 相同 tool 触发了多次交互, 调用的 toolId 认为是相同的
if (interactive) {
@ -247,6 +252,24 @@ export const runAgentCall = async ({
consecutiveRequestToolTimes = 0;
}
// Record usage
inputTokens += usage.inputTokens;
outputTokens += usage.outputTokens;
const { totalPoints, modelName } = formatModelChars2Points({
model: modelData.model,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
});
usagePush([
{
moduleName: 'Agent 调用',
model: modelName,
totalPoints,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
}
]);
// 3. 更新 messages
const cloneRequestMessages = requestMessages.slice();
// 推送 AI 生成后的 assistantMessages
@ -278,6 +301,7 @@ export const runAgentCall = async ({
assistantMessages.push(...filterEmptyAssistantMessages(toolAssistantMessages)); // 因为 toolAssistantMessages 也需要记录成 AI 响应,所以这里需要推送。
subAppUsages.push(...usages);
usagePush(usages);
if (interactive) {
interactiveResponse = {
@ -296,10 +320,6 @@ export const runAgentCall = async ({
}
}
// 6 Record usage
inputTokens += usage.inputTokens;
outputTokens += usage.outputTokens;
if (toolCalls.length === 0 || !!interactiveResponse || toolCallStep) {
break;
}

View File

@ -1,10 +1,12 @@
import type { AgentPlanStepType } from './sub/plan/type';
import type { AgentStepItemType } from '@fastgpt/global/core/ai/agent/type';
import { getLLMModel } from '../../../../ai/model';
import { countPromptTokens } from '../../../../../common/string/tiktoken/index';
import { createLLMResponse } from '../../../../ai/llm/request';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import { addLog } from '../../../../../common/system/log';
import { calculateCompressionThresholds } from '../../../../ai/llm/compress/constants';
import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { formatModelChars2Points } from '../../../../../support/wallet/usage/utils';
export const getMasterAgentSystemPrompt = async ({
steps,
@ -13,8 +15,8 @@ export const getMasterAgentSystemPrompt = async ({
background = '',
model
}: {
steps: AgentPlanStepType[];
step: AgentPlanStepType;
steps: AgentStepItemType[];
step: AgentStepItemType;
userInput: string;
background?: string;
model: string;
@ -27,18 +29,21 @@ export const getMasterAgentSystemPrompt = async ({
stepPrompt: string,
model: string,
currentDescription: string
): Promise<string> => {
if (!stepPrompt) return stepPrompt;
): Promise<{
stepPrompt: string;
usage?: ChatNodeUsageType;
}> => {
if (!stepPrompt) return { stepPrompt };
const modelData = getLLMModel(model);
if (!modelData) return stepPrompt;
if (!modelData) return { stepPrompt };
const tokenCount = await countPromptTokens(stepPrompt);
const thresholds = calculateCompressionThresholds(modelData.maxContext);
const maxTokenThreshold = thresholds.dependsOn.threshold;
if (tokenCount <= maxTokenThreshold) {
return stepPrompt;
return { stepPrompt: stepPrompt };
}
const targetTokens = thresholds.dependsOn.target;
@ -129,7 +134,7 @@ ${stepPrompt}
`;
try {
const { answerText } = await createLLMResponse({
const { answerText, usage } = await createLLMResponse({
body: {
model: modelData,
messages: [
@ -147,11 +152,26 @@ ${stepPrompt}
}
});
return answerText || stepPrompt;
const { totalPoints, modelName } = formatModelChars2Points({
model: modelData.model,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
});
return {
stepPrompt: answerText || stepPrompt,
usage: {
moduleName: '压缩步骤提示词',
model: modelName,
totalPoints,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
}
};
} catch (error) {
console.error('压缩 stepPrompt 失败:', error);
// 压缩失败时返回原始内容
return stepPrompt;
return { stepPrompt: stepPrompt };
}
};
@ -163,11 +183,18 @@ ${stepPrompt}
)
.filter(Boolean)
.join('\n');
addLog.debug(`Step call depends_on (LLM): ${step.id}`, step.depends_on);
addLog.debug(`Step call depends_on (LLM): ${step.id}, dependOn: ${step.depends_on}`);
// 压缩依赖的上下文
stepPrompt = await compressStepPrompt(stepPrompt, model, step.description || step.title);
const compressResult = await compressStepPrompt(
stepPrompt,
model,
step.description || step.title
);
stepPrompt = compressResult.stepPrompt;
return `请根据任务背景、之前步骤的执行结果和当前步骤要求选择并调用相应的工具。如果是一个总结性质的步骤,请整合之前步骤的结果进行总结。
return {
usage: compressResult.usage,
prompt: `请根据任务背景、之前步骤的执行结果和当前步骤要求选择并调用相应的工具。如果是一个总结性质的步骤,请整合之前步骤的结果进行总结。
目标: ${userInput}
前置信息: ${background}
@ -192,5 +219,6 @@ ${stepPrompt}
6.
7.
`;
`
};
};

View File

@ -9,7 +9,6 @@ import type { GetSubAppInfoFnType, SubAppRuntimeType } from '../type';
import { getMasterAgentSystemPrompt } from '../constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { getWorkflowChildResponseWrite } from '../../../utils';
import { SubAppIds } from '../sub/constants';
import { parseJsonArgs } from '../../../../../ai/utils';
import { dispatchFileRead } from '../sub/file';
@ -23,6 +22,7 @@ import type { DispatchPlanAgentResponse } from '../sub/plan';
import { dispatchPlanAgent } from '../sub/plan';
import { addLog } from '../../../../../../common/system/log';
import type { WorkflowResponseItemType } from '../../../type';
import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
type Response = {
stepResponse?: {
@ -32,6 +32,10 @@ type Response = {
planResponse?: DispatchPlanAgentResponse;
completeMessages: ChatCompletionMessageParam[];
assistantMessages: ChatCompletionMessageParam[];
inputTokens: number;
outputTokens: number;
subAppUsages: ChatNodeUsageType[];
};
export const masterCall = async ({
@ -96,14 +100,16 @@ export const masterCall = async ({
step.depends_on = depends;
}
// Step call system prompt
// TODO: 需要把压缩的 usage 返回
const systemPromptContent = await getMasterAgentSystemPrompt({
const compressResult = await getMasterAgentSystemPrompt({
steps,
step,
userInput: userChatInput,
model,
background: systemPrompt
});
if (compressResult.usage) {
usagePush([compressResult.usage]);
}
const requestMessages = chats2GPTMessages({
messages: [
@ -112,7 +118,7 @@ export const masterCall = async ({
value: [
{
text: {
content: systemPromptContent
content: compressResult.prompt
}
}
]
@ -160,244 +166,241 @@ export const masterCall = async ({
let planResult: DispatchPlanAgentResponse | undefined;
const {
assistantMessages,
completeMessages,
inputTokens,
outputTokens,
subAppUsages,
interactiveResponse
} = await runAgentCall({
maxRunAgentTimes: 100,
body: {
messages: requestMessages,
model: getLLMModel(model),
temperature,
stream,
top_p: aiChatTopP,
tools: completionTools
},
const { assistantMessages, completeMessages, inputTokens, outputTokens, subAppUsages } =
await runAgentCall({
maxRunAgentTimes: 100,
body: {
messages: requestMessages,
model: getLLMModel(model),
temperature,
stream,
top_p: aiChatTopP,
tools: completionTools
},
userKey: externalProvider.openaiAccount,
isAborted: checkIsStopping,
// childrenInteractiveParams
userKey: externalProvider.openaiAccount,
usagePush,
isAborted: checkIsStopping,
// childrenInteractiveParams
onReasoning({ text }) {
stepStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
reasoning_content: text
})
});
},
onStreaming({ text }) {
stepStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
})
});
},
onToolCall({ call }) {
const subApp = getSubAppInfo(call.function.name);
onReasoning({ text }) {
stepStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
reasoning_content: text
})
});
},
onStreaming({ text }) {
stepStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
})
});
},
onToolCall({ call }) {
const subApp = getSubAppInfo(call.function.name);
if (call.function.name === SubAppIds.plan) {
return;
}
stepStreamResponse?.({
id: call.id,
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: call.id,
toolName: subApp?.name || call.function.name,
toolAvatar: subApp?.avatar || '',
functionName: call.function.name,
params: call.function.arguments ?? ''
}
if (call.function.name === SubAppIds.plan) {
return;
}
});
},
onToolParam({ tool, params }) {
stepStreamResponse?.({
id: tool.id,
event: SseResponseEventEnum.toolParams,
data: {
tool: {
params
stepStreamResponse?.({
id: call.id,
event: SseResponseEventEnum.toolCall,
data: {
tool: {
id: call.id,
toolName: subApp?.name || call.function.name,
toolAvatar: subApp?.avatar || '',
functionName: call.function.name,
params: call.function.arguments ?? ''
}
}
}
});
},
handleToolResponse: async ({ call, messages }) => {
addLog.debug('handleToolResponse', { toolName: call.function.name });
const toolId = call.function.name;
});
},
onToolParam({ tool, params }) {
stepStreamResponse?.({
id: tool.id,
event: SseResponseEventEnum.toolParams,
data: {
tool: {
params
}
}
});
},
handleToolResponse: async ({ call, messages }) => {
addLog.debug('handleToolResponse', { toolName: call.function.name });
const toolId = call.function.name;
const {
response,
usages = [],
stop = false
} = await (async () => {
try {
if (toolId === SubAppIds.fileRead) {
const toolParams = ReadFileToolSchema.safeParse(parseJsonArgs(call.function.arguments));
const {
response,
usages = [],
stop = false
} = await (async () => {
try {
if (toolId === SubAppIds.fileRead) {
const toolParams = ReadFileToolSchema.safeParse(
parseJsonArgs(call.function.arguments)
);
if (!toolParams.success) {
if (!toolParams.success) {
return {
response: toolParams.error.message,
usages: []
};
}
const params = toolParams.data;
const files = params.file_indexes.map((index) => ({
index,
url: filesMap[index]
}));
const result = await dispatchFileRead({
files,
teamId: runningUserInfo.teamId,
tmbId: runningUserInfo.tmbId,
customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse
});
return {
response: toolParams.error.message,
usages: []
response: result.response,
usages: result.usages
};
}
const params = toolParams.data;
if (toolId === SubAppIds.plan) {
try {
planResult = await dispatchPlanAgent({
checkIsStopping,
historyMessages: historiesMessages,
userInput: userChatInput,
completionTools,
getSubAppInfo,
systemPrompt: systemPrompt,
model,
stream
});
const files = params.file_indexes.map((index) => ({
index,
url: filesMap[index]
}));
const result = await dispatchFileRead({
files,
teamId: runningUserInfo.teamId,
tmbId: runningUserInfo.tmbId,
customPdfParse: chatConfig?.fileSelectConfig?.customPdfParse
});
return {
response: '',
stop: true
};
} catch (error) {
console.log(error, 111);
return {
response: getErrText(error),
stop: false
};
}
}
// User Sub App
else {
const tool = subAppsMap.get(toolId);
if (!tool) {
return {
response: 'Can not find the tool',
usages: []
};
}
const toolCallParams = parseJsonArgs(call.function.arguments);
if (!toolCallParams) {
return {
response: 'params is not object',
usages: []
};
}
// Get params
const requestParams = {
...tool.params,
...toolCallParams
};
if (tool.type === 'tool') {
const { response, usages } = await dispatchTool({
tool: {
name: tool.name,
version: tool.version,
toolConfig: tool.toolConfig
},
params: requestParams,
runningUserInfo,
runningAppInfo,
variables,
workflowStreamResponse: stepStreamResponse
});
return {
response,
usages
};
} else if (tool.type === 'workflow' || tool.type === 'toolWorkflow') {
// const fn = tool.type === 'workflow' ? dispatchApp : dispatchPlugin;
// const { response, usages } = await fn({
// ...props,
// node,
// workflowStreamResponse:stepStreamResponse,
// callParams: {
// appId: node.pluginId,
// version: node.version,
// ...requestParams
// }
// });
// return {
// response,
// usages
// };
return {
response: 'Can not find the tool',
usages: []
};
} else {
return {
response: 'Can not find the tool',
usages: []
};
}
}
} catch (error) {
return {
response: result.response,
usages: result.usages
response: getErrText(error),
usages: []
};
}
if (toolId === SubAppIds.plan) {
try {
planResult = await dispatchPlanAgent({
checkIsStopping,
historyMessages: historiesMessages,
userInput: userChatInput,
completionTools,
getSubAppInfo,
systemPrompt: systemPrompt,
model,
stream
});
})();
return {
response: '',
stop: true
};
} catch (error) {
console.log(error, 111);
return {
response: getErrText(error),
stop: false
};
// Push stream response
stepStreamResponse?.({
id: call.id,
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
response
}
}
// User Sub App
else {
const tool = subAppsMap.get(toolId);
if (!tool) {
return {
response: 'Can not find the tool',
usages: []
};
}
});
const toolCallParams = parseJsonArgs(call.function.arguments);
// TODO: 推送账单
if (!toolCallParams) {
return {
response: 'params is not object',
usages: []
};
}
// Get params
const requestParams = {
...tool.params,
...toolCallParams
};
if (tool.type === 'tool') {
const { response, usages } = await dispatchTool({
tool: {
name: tool.name,
version: tool.version,
toolConfig: tool.toolConfig
},
params: requestParams,
runningUserInfo,
runningAppInfo,
variables,
workflowStreamResponse: stepStreamResponse
});
return {
response,
usages
};
} else if (tool.type === 'workflow' || tool.type === 'toolWorkflow') {
// const fn = tool.type === 'workflow' ? dispatchApp : dispatchPlugin;
// const { response, usages } = await fn({
// ...props,
// node,
// workflowStreamResponse:stepStreamResponse,
// callParams: {
// appId: node.pluginId,
// version: node.version,
// ...requestParams
// }
// });
// return {
// response,
// usages
// };
return {
response: 'Can not find the tool',
usages: []
};
} else {
return {
response: 'Can not find the tool',
usages: []
};
}
}
} catch (error) {
return {
response: getErrText(error),
usages: []
};
}
})();
// Push stream response
stepStreamResponse?.({
id: call.id,
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
response
}
}
});
// TODO: 推送账单
return {
response,
assistantMessages: [], // TODO
usages,
stop
};
},
handleInteractiveTool: async ({ toolParams }) => {
return {
response: 'Interactive tool not supported',
assistantMessages: [], // TODO
usages: []
};
}
});
return {
response,
assistantMessages: [], // TODO
usages,
stop
};
},
handleInteractiveTool: async ({ toolParams }) => {
return {
response: 'Interactive tool not supported',
assistantMessages: [], // TODO
usages: []
};
}
});
// Step call
if (isStepCall) {
@ -431,7 +434,10 @@ export const masterCall = async ({
summary
},
completeMessages,
assistantMessages
assistantMessages,
inputTokens,
outputTokens,
subAppUsages
};
}
@ -439,6 +445,9 @@ export const masterCall = async ({
return {
planResponse: planResult,
completeMessages,
assistantMessages
assistantMessages,
inputTokens,
outputTokens,
subAppUsages
};
};

View File

@ -1,5 +1,5 @@
import { getLLMModel } from '../../../../../ai/model';
import type { AgentPlanStepType } from '../sub/plan/type';
import type { AgentStepItemType } from '@fastgpt/global/core/ai/agent/type';
import { addLog } from '../../../../../../common/system/log';
import { createLLMResponse } from '../../../../../ai/llm/request';
import { parseJsonArgs } from '../../../../../ai/utils';
@ -12,8 +12,8 @@ export const getStepDependon = async ({
step
}: {
model: string;
steps: AgentPlanStepType[];
step: AgentPlanStepType;
steps: AgentStepItemType[];
step: AgentStepItemType;
}): Promise<{
depends: string[];
usage?: ChatNodeUsageType;

View File

@ -251,6 +251,7 @@ export const dispatchReplanAgent = async ({
}: DispatchPlanAgentProps & {
plan: AgentPlanType;
}): Promise<DispatchPlanAgentResponse> => {
const usages: ChatNodeUsageType[] = [];
const modelData = getLLMModel(model);
const requestMessages: ChatCompletionMessageParam[] = [
@ -292,7 +293,10 @@ export const dispatchReplanAgent = async ({
description: '本步骤分析先前的执行结果,以确定重新规划时需要依赖哪些特定步骤。'
}
});
// TODO: 推送
if (dependsUsage) {
usages.push(dependsUsage);
}
const replanSteps = plan.steps.filter((step) => depends.includes(step.id));
requestMessages.push({
@ -347,19 +351,18 @@ export const dispatchReplanAgent = async ({
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
});
usages.push({
moduleName: '重新规划',
model: modelName,
totalPoints,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
});
return {
askInteractive,
plan: rePlan,
completeMessages,
usages: [
{
moduleName: '重新规划',
model: modelName,
totalPoints,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens
}
]
usages
};
};

View File

@ -125,7 +125,10 @@ export const dispatchTool = async ({
}
const usagePoints = (() => {
if (params.system_input_config?.type !== SystemToolSecretInputTypeEnum.system) {
if (
params.system_input_config?.type === SystemToolSecretInputTypeEnum.team ||
params.system_input_config?.type === SystemToolSecretInputTypeEnum.manual
) {
return 0;
}
return (tool.systemKeyCost ?? 0) + (tool.currentCost ?? 0);