UNPKG

langsmith

Version:

Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.

222 lines (221 loc) 9.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.populateToolCallsForTracing = void 0; exports.LangSmithMiddleware = LangSmithMiddleware; const traceable_js_1 = require("../../traceable.cjs"); const vercel_js_1 = require("../../utils/vercel.cjs"); const populateToolCallsForTracing = (message) => { const formattedMessage = { ...message, }; if (formattedMessage.role !== "assistant") { return formattedMessage; } if (Array.isArray(formattedMessage.content)) { const toolCalls = formattedMessage.content .filter((block) => { return (block != null && typeof block === "object" && block.type == "tool-call"); }) .map((block) => { return { id: block.toolCallId, type: "function", function: { name: block.toolName, arguments: typeof block.input !== "string" ? JSON.stringify(block.input) : block.input, }, }; }); if (toolCalls.length > 0) { formattedMessage.tool_calls = toolCalls; } } return formattedMessage; }; exports.populateToolCallsForTracing = populateToolCallsForTracing; // eslint-disable-next-line @typescript-eslint/no-explicit-any const _formatTracedInputs = (params) => { const { prompt, ...rest } = params; if (prompt == null) { return params; } if (Array.isArray(prompt)) { return { ...rest, messages: prompt.map(exports.populateToolCallsForTracing) }; } return rest; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const _formatTracedOutputs = (outputs) => { const { request, response, ...rest } = outputs; const formattedOutputs = { ...rest }; if (formattedOutputs.role == null) { formattedOutputs.role = formattedOutputs.type ?? "assistant"; } return (0, exports.populateToolCallsForTracing)(formattedOutputs); }; const setUsageMetadataOnRunTree = (result, runTree) => { if (result.usage == null || typeof result.usage !== "object") { return; } const langsmithUsage = { input_tokens: result.usage?.inputTokens, output_tokens: result.usage?.outputTokens, total_tokens: result.usage?.totalTokens, }; const inputTokenDetails = (0, vercel_js_1.extractInputTokenDetails)(result.providerMetadata ?? {}, result.usage?.cachedInputTokens); const outputTokenDetails = (0, vercel_js_1.extractOutputTokenDetails)(result.usage?.reasoningTokens); runTree.extra = { ...runTree.extra, metadata: { ...runTree.extra?.metadata, usage_metadata: { ...langsmithUsage, input_token_details: { ...inputTokenDetails, }, output_token_details: { ...outputTokenDetails, }, }, }, }; }; /** * AI SDK middleware that wraps an AI SDK 5 model and adds LangSmith tracing. */ function LangSmithMiddleware(config) { const { name, modelId, lsConfig } = config ?? {}; return { wrapGenerate: async ({ doGenerate, params }) => { const traceableFunc = (0, traceable_js_1.traceable)(async (_params) => { const result = await doGenerate(); const currentRunTree = (0, traceable_js_1.getCurrentRunTree)(true); if (currentRunTree !== undefined) { setUsageMetadataOnRunTree(result, currentRunTree); } return result; }, { ...lsConfig, name: name ?? "ai.doGenerate", run_type: "llm", metadata: { ls_model_name: modelId, ai_sdk_method: "ai.doGenerate", ...lsConfig?.metadata, }, processInputs: (inputs) => { const typedInputs = inputs; return _formatTracedInputs(typedInputs); }, processOutputs: (outputs) => { const typedOutputs = outputs; return _formatTracedOutputs(typedOutputs); }, }); return traceableFunc(params); }, wrapStream: async ({ doStream, params }) => { const parentRunTree = (0, traceable_js_1.getCurrentRunTree)(true); let runTree; if (parentRunTree != null && typeof parentRunTree === "object" && typeof parentRunTree.createChild === "function") { runTree = parentRunTree?.createChild({ ...lsConfig, name: name ?? "ai.doStream", run_type: "llm", metadata: { ls_model_name: modelId, ai_sdk_method: "ai.doStream", ...lsConfig?.metadata, }, inputs: _formatTracedInputs(params), }); } await runTree?.postRun(); try { const { stream, ...rest } = await doStream(); const chunks = []; const transformStream = new TransformStream({ async transform(chunk, controller) { chunks.push(chunk); controller.enqueue(chunk); }, async flush() { try { const output = chunks.reduce((aggregated, chunk) => { if (chunk.type === "text-delta") { if (chunk.delta == null) { return aggregated; } return { ...aggregated, content: aggregated.content + chunk.delta, }; } else if (chunk.type === "tool-call") { const matchingToolCall = aggregated.tool_calls.find((call) => call.id === chunk.toolCallId); if (matchingToolCall != null) { return aggregated; } return { ...aggregated, tool_calls: [ ...aggregated.tool_calls, { id: chunk.toolCallId, type: "function", function: { name: chunk.toolName, arguments: chunk.input, }, }, ], }; } else if (chunk.type === "finish") { if (runTree != null) { setUsageMetadataOnRunTree(chunk, runTree); } return { ...aggregated, providerMetadata: chunk.providerMetadata, finishReason: chunk.finishReason, }; } else { return aggregated; } }, { content: "", // eslint-disable-next-line @typescript-eslint/no-explicit-any tool_calls: [], }); await runTree?.end(_formatTracedOutputs(output)); } catch (error) { await runTree?.end(undefined, error.message ?? String(error)); throw error; } finally { await runTree?.patchRun(); } }, }); return { stream: stream.pipeThrough(transformStream), ...rest, }; } catch (error) { await runTree?.end(undefined, error.message ?? String(error)); await runTree?.patchRun(); throw error; } }, }; }