ai-query对接新版freesun-agent接口的分支
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

81 lines
3.6 KiB

'use strict';
const toTokenNumber = (value) => Math.max(0, Number.parseInt(value, 10) || 0);
const toText = (value, max = 128) => String(value ?? '').trim().slice(0, max);
const unwrapResponseData = (value = {}) => {
const body = value?._body && typeof value._body === 'object' ? value._body : value;
if (body && typeof body === 'object' && Object.prototype.hasOwnProperty.call(body, 'responseData')) {
return body.responseData;
}
return body;
};
const collectResponseDataNodes = (value) => {
const root = unwrapResponseData(value);
if (!root || typeof root !== 'object') return [];
const nodes = [];
const seen = new WeakSet();
const visit = (item, path = 'root', depth = 0) => {
if (!item || typeof item !== 'object' || depth > 12 || seen.has(item)) return;
seen.add(item);
const inputTokens = toTokenNumber(item.inputTokens ?? item.input_tokens ?? item.toolCallInputTokens);
const outputTokens = toTokenNumber(item.outputTokens ?? item.output_tokens ?? item.toolCallOutputTokens);
const totalTokens = toTokenNumber(item.totalTokens ?? item.total_tokens) || inputTokens + outputTokens;
const model = toText(item.model || item.modelName || item.aiModel);
const embeddingTokens = toTokenNumber(item.embeddingTokens ?? item.embedding_tokens);
const embeddingModel = toText(item.embeddingModel);
const quotes = Array.isArray(item.quoteList) ? item.quoteList : (Array.isArray(item.cites) ? item.cites : []);
const quote = quotes[0] || {};
const knowledgeBaseId = toText(item.datasetId || item.knowledgeBaseId || item.collectionId || quote.datasetId);
const knowledgeBaseName = toText(item.datasetName || item.knowledgeBaseName || quote.datasetName || quote.collection?.name);
const moduleType = toText(item.moduleType).toLowerCase();
const isDatasetSearchNode = moduleType === 'datasetsearchnode' || moduleType === 'datasetsearch';
const ragQueryCount = isDatasetSearchNode ? 1 : 0;
const ragHitCount = toTokenNumber(item.hitCount ?? item.matchCount) || quotes.length;
const nodeId = toText(item.id || item.nodeId || path);
if (totalTokens || model) {
nodes.push({
kind: 'model', nodeId, path, model, inputTokens, outputTokens, totalTokens,
knowledgeBaseId, knowledgeBaseName, ragQueryCount, ragHitCount,
});
} else if (ragQueryCount) {
nodes.push({ kind: 'rag', nodeId, path, knowledgeBaseId, knowledgeBaseName, ragQueryCount, ragHitCount });
}
if (embeddingTokens) {
nodes.push({
kind: 'embedding', nodeId: `${nodeId}:embedding`, path, model: embeddingModel,
inputTokens: embeddingTokens, outputTokens: 0, totalTokens: embeddingTokens,
});
}
Object.entries(item).forEach(([key, child]) => {
if (child && typeof child === 'object') visit(child, `${path}.${key}`, depth + 1);
});
};
visit(root);
return nodes;
};
const summarizeResponseDataUsage = (value) => collectResponseDataNodes(value).reduce((usage, node) => {
if (node.kind === 'embedding') usage.embeddingTokens += node.totalTokens;
if (node.kind === 'model') {
usage.inputTokens += node.inputTokens;
usage.outputTokens += node.outputTokens;
}
return usage;
}, { inputTokens: 0, outputTokens: 0, embeddingTokens: 0, totalTokens: 0 });
const getResponseDataUsage = (value) => {
const usage = summarizeResponseDataUsage(value);
return { ...usage, totalTokens: usage.inputTokens + usage.outputTokens + usage.embeddingTokens };
};
module.exports = {
collectResponseDataNodes,
getResponseDataUsage,
unwrapResponseData,
};