mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-21 15:55:50 +00:00
feat: 为sideQuery添加Langfuse追踪
sideQuery 绕过了 claude.ts 的主 API 路径,导致所有走 sideQuery 的调用 (auto mode classifier、permission explainer、session search 等)都没有 Langfuse 记录。现在为每次 sideQuery 调用创建独立 trace 并记录 LLM observation, 未配置 Langfuse 时全部 no-op。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import type Anthropic from '@anthropic-ai/sdk'
|
|||||||
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages.js'
|
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages.js'
|
||||||
import {
|
import {
|
||||||
getLastApiCompletionTimestamp,
|
getLastApiCompletionTimestamp,
|
||||||
|
getSessionId,
|
||||||
setLastApiCompletionTimestamp,
|
setLastApiCompletionTimestamp,
|
||||||
} from '../bootstrap/state.js'
|
} from '../bootstrap/state.js'
|
||||||
import { STRUCTURED_OUTPUTS_BETA_HEADER } from '../constants/betas.js'
|
import { STRUCTURED_OUTPUTS_BETA_HEADER } from '../constants/betas.js'
|
||||||
@@ -14,8 +15,10 @@ import { logEvent } from '../services/analytics/index.js'
|
|||||||
import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from '../services/analytics/metadata.js'
|
import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from '../services/analytics/metadata.js'
|
||||||
import { getAPIMetadata } from '../services/api/claude.js'
|
import { getAPIMetadata } from '../services/api/claude.js'
|
||||||
import { getAnthropicClient } from '../services/api/client.js'
|
import { getAnthropicClient } from '../services/api/client.js'
|
||||||
|
import { createTrace, endTrace, recordLLMObservation } from '../services/langfuse/index.js'
|
||||||
import { getModelBetas, modelSupportsStructuredOutputs } from './betas.js'
|
import { getModelBetas, modelSupportsStructuredOutputs } from './betas.js'
|
||||||
import { computeFingerprint } from './fingerprint.js'
|
import { computeFingerprint } from './fingerprint.js'
|
||||||
|
import { getAPIProvider } from './model/providers.js'
|
||||||
import { normalizeModelStringForAPI } from './model/model.js'
|
import { normalizeModelStringForAPI } from './model/model.js'
|
||||||
|
|
||||||
type MessageParam = Anthropic.MessageParam
|
type MessageParam = Anthropic.MessageParam
|
||||||
@@ -177,25 +180,39 @@ export async function sideQuery(opts: SideQueryOptions): Promise<BetaMessage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedModel = normalizeModelStringForAPI(model)
|
const normalizedModel = normalizeModelStringForAPI(model)
|
||||||
|
const provider = getAPIProvider()
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
// biome-ignore lint/plugin: this IS the wrapper that handles OAuth attribution
|
const langfuseTrace = createTrace({
|
||||||
const response = await client.beta.messages.create(
|
sessionId: getSessionId(),
|
||||||
{
|
model: normalizedModel,
|
||||||
model: normalizedModel,
|
provider,
|
||||||
max_tokens,
|
name: `side-query:${opts.querySource}`,
|
||||||
system: systemBlocks,
|
querySource: opts.querySource,
|
||||||
messages,
|
})
|
||||||
...(tools && { tools }),
|
|
||||||
...(tool_choice && { tool_choice }),
|
let response: BetaMessage
|
||||||
...(output_format && { output_config: { format: output_format } }),
|
try {
|
||||||
...(temperature !== undefined && { temperature }),
|
response = await client.beta.messages.create(
|
||||||
...(stop_sequences && { stop_sequences }),
|
{
|
||||||
...(thinkingConfig && { thinking: thinkingConfig }),
|
model: normalizedModel,
|
||||||
...(betas.length > 0 && { betas }),
|
max_tokens,
|
||||||
metadata: getAPIMetadata(),
|
system: systemBlocks,
|
||||||
},
|
messages,
|
||||||
{ signal },
|
...(tools && { tools }),
|
||||||
)
|
...(tool_choice && { tool_choice }),
|
||||||
|
...(output_format && { output_config: { format: output_format } }),
|
||||||
|
...(temperature !== undefined && { temperature }),
|
||||||
|
...(stop_sequences && { stop_sequences }),
|
||||||
|
...(thinkingConfig && { thinking: thinkingConfig }),
|
||||||
|
...(betas.length > 0 && { betas }),
|
||||||
|
metadata: getAPIMetadata(),
|
||||||
|
},
|
||||||
|
{ signal },
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
endTrace(langfuseTrace, undefined, 'error')
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
const requestId =
|
const requestId =
|
||||||
(response as { _request_id?: string | null })._request_id ?? undefined
|
(response as { _request_id?: string | null })._request_id ?? undefined
|
||||||
@@ -218,5 +235,22 @@ export async function sideQuery(opts: SideQueryOptions): Promise<BetaMessage> {
|
|||||||
})
|
})
|
||||||
setLastApiCompletionTimestamp(now)
|
setLastApiCompletionTimestamp(now)
|
||||||
|
|
||||||
|
// Record LLM observation in Langfuse (no-op if not configured)
|
||||||
|
recordLLMObservation(langfuseTrace, {
|
||||||
|
model: normalizedModel,
|
||||||
|
provider,
|
||||||
|
input: messages,
|
||||||
|
output: response.content,
|
||||||
|
usage: {
|
||||||
|
input_tokens: response.usage.input_tokens,
|
||||||
|
output_tokens: response.usage.output_tokens,
|
||||||
|
cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? undefined,
|
||||||
|
cache_read_input_tokens: response.usage.cache_read_input_tokens ?? undefined,
|
||||||
|
},
|
||||||
|
startTime: new Date(start),
|
||||||
|
endTime: new Date(),
|
||||||
|
})
|
||||||
|
endTrace(langfuseTrace)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user