UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

196 lines (156 loc) 4.97 kB
'use strict' const { extractModel, extractSystemInstructions } = require('../../../../datadog-plugin-google-cloud-vertexai/src/utils') const LLMObsPlugin = require('./base') class VertexAILLMObsPlugin extends LLMObsPlugin { static integration = 'vertexai' // used for llmobs telemetry static id = 'vertexai' static prefix = 'tracing:apm:vertexai:request' getLLMObsSpanRegisterOptions (ctx) { const history = ctx.instance?.historyInternal || [] ctx.history = history return { kind: 'llm', modelName: extractModel(ctx.instance), modelProvider: 'google', name: ctx.resource } } setLLMObsTags (ctx) { const span = ctx.currentStore?.span if (!span) return const { instance, result, request } = ctx const history = ctx.history || [] const systemInstructions = extractSystemInstructions(instance) const metadata = getMetadata(instance) const inputMessages = extractInputMessages(request, history, systemInstructions) const outputMessages = extractOutputMessages(result) const metrics = extractMetrics(result) this._tagger.tagLLMIO(span, inputMessages, outputMessages) this._tagger.tagMetadata(span, metadata) this._tagger.tagMetrics(span, metrics) } } function getMetadata (instance) { const metadata = {} const modelConfig = instance.generationConfig if (!modelConfig) return metadata for (const [parameter, parameterKey] of [ ['temperature', 'temperature'], ['maxOutputTokens', 'max_output_tokens'], ['candidateCount', 'candidate_count'], ['topP', 'top_p'], ['topK', 'top_k'] ]) { if (modelConfig[parameter]) { metadata[parameterKey] = modelConfig[parameter] } } return metadata } function extractInputMessages (request, history, systemInstructions) { const contents = typeof request === 'string' || Array.isArray(request) ? request : request.contents const messages = [] if (systemInstructions) { for (const instruction of systemInstructions) { messages.push({ content: instruction || '', role: 'system' }) } } for (const content of history) { messages.push(...extractMessagesFromContent(content)) } if (typeof contents === 'string') { messages.push({ content: contents }) return messages } if (isPart(contents)) { messages.push(extractMessageFromPart(contents)) return messages } if (!Array.isArray(contents)) { messages.push({ content: '[Non-array content object: ' + `${(typeof contents.toString === 'function' ? contents.toString() : String(contents))}]` }) return messages } for (const content of contents) { if (typeof content === 'string') { messages.push({ content }) continue } if (isPart(content)) { messages.push(extractMessageFromPart(content)) continue } messages.push(...extractMessagesFromContent(content)) } return messages } function extractOutputMessages (result) { if (!result) return [{ content: '' }] const { response } = result if (!response) return [{ content: '' }] const outputMessages = [] const candidates = response.candidates || [] for (const candidate of candidates) { const content = candidate.content || '' outputMessages.push(...extractMessagesFromContent(content)) } return outputMessages } function extractMessagesFromContent (content) { const messages = [] const role = content.role || '' const parts = content.parts || [] if (parts == null || parts.length === 0 || !Array.isArray(parts)) { const message = { content: `[Non-text content object: ${(typeof content.toString === 'function' ? content.toString() : String(content))}]` } if (role) message.role = role messages.push(message) return messages } for (const part of parts) { const message = extractMessageFromPart(part, role) messages.push(message) } return messages } function extractMessageFromPart (part, role) { const text = part.text || '' const functionCall = part.functionCall const functionResponse = part.functionResponse const message = { content: text } if (role) message.role = role if (functionCall) { message.toolCalls = [{ name: functionCall.name, arguments: functionCall.args }] } if (functionResponse) { message.content = `[tool result: ${functionResponse.response}]` } return message } function extractMetrics (result) { if (!result) return {} const { response } = result if (!response) return {} const tokenCounts = response.usageMetadata const metrics = {} if (tokenCounts) { metrics.inputTokens = tokenCounts.promptTokenCount metrics.outputTokens = tokenCounts.candidatesTokenCount metrics.totalTokens = tokenCounts.totalTokenCount } return metrics } function isPart (part) { return part.text || part.functionCall || part.functionResponse } module.exports = VertexAILLMObsPlugin