Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit 3a6a4be

Browse files
committed
add langfuse observability
1 parent 78190d1 commit 3a6a4be

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

src/observability/langfuse.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Langfuse observability integration for MCP-use.
3+
*
4+
* This module provides automatic instrumentation and callback handler
5+
* for Langfuse observability platform.
6+
*/
7+
8+
/// <reference path="./types.d.ts" />
9+
10+
import type { BaseCallbackHandler } from '@langchain/core/callbacks/base'
11+
import { config } from 'dotenv'
12+
import { logger } from '../logging.js'
13+
14+
config()
15+
16+
// Check if Langfuse is disabled via environment variable
17+
const langfuseDisabled = process.env.MCP_USE_LANGFUSE?.toLowerCase() === 'false'
18+
19+
// Initialize variables - using const with object to avoid linter issues with mutable exports
20+
const langfuseState = {
21+
handler: null as BaseCallbackHandler | null,
22+
client: null as any,
23+
initPromise: null as Promise<void> | null,
24+
}
25+
26+
async function initializeLangfuse(): Promise<void> {
27+
try {
28+
// Dynamically import to avoid errors if package not installed
29+
const langfuseModule = await import('langfuse-langchain').catch(() => null)
30+
if (!langfuseModule) {
31+
logger.debug('Langfuse package not installed - tracing disabled. Install with: npm install langfuse-langchain')
32+
return
33+
}
34+
35+
const { CallbackHandler } = langfuseModule as any
36+
// Create a custom CallbackHandler wrapper to add logging
37+
class LoggingCallbackHandler extends CallbackHandler {
38+
constructor(config?: any) {
39+
super(config)
40+
}
41+
42+
async handleLLMStart(...args: any[]): Promise<void> {
43+
logger.debug('Langfuse: LLM start intercepted')
44+
if (this.verbose) {
45+
logger.debug(`Langfuse: LLM start args: ${JSON.stringify(args)}`)
46+
}
47+
return super.handleLLMStart(...args)
48+
}
49+
50+
async handleChainStart(...args: any[]): Promise<void> {
51+
logger.debug('Langfuse: Chain start intercepted')
52+
if (this.verbose) {
53+
logger.debug(`Langfuse: Chain start args: ${JSON.stringify(args)}`)
54+
}
55+
return super.handleChainStart(...args)
56+
}
57+
58+
async handleToolStart(...args: any[]): Promise<void> {
59+
logger.debug('Langfuse: Tool start intercepted')
60+
if (this.verbose) {
61+
logger.debug(`Langfuse: Tool start args: ${JSON.stringify(args)}`)
62+
}
63+
return super.handleToolStart(...args)
64+
}
65+
66+
async handleRetrieverStart(...args: any[]): Promise<void> {
67+
logger.debug('Langfuse: Retriever start intercepted')
68+
if (this.verbose) {
69+
logger.debug(`Langfuse: Retriever start args: ${JSON.stringify(args)}`)
70+
}
71+
return super.handleRetrieverStart(...args)
72+
}
73+
74+
async handleAgentAction(...args: any[]): Promise<void> {
75+
logger.debug('Langfuse: Agent action intercepted')
76+
if (this.verbose) {
77+
logger.debug(`Langfuse: Agent action args: ${JSON.stringify(args)}`)
78+
}
79+
return super.handleAgentAction(...args)
80+
}
81+
82+
async handleAgentEnd(...args: any[]): Promise<void> {
83+
logger.debug('Langfuse: Agent end intercepted')
84+
if (this.verbose) {
85+
logger.debug(`Langfuse: Agent end args: ${JSON.stringify(args)}`)
86+
}
87+
return super.handleAgentEnd(...args)
88+
}
89+
}
90+
91+
// Create the handler with configuration
92+
const config = {
93+
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
94+
secretKey: process.env.LANGFUSE_SECRET_KEY,
95+
baseUrl: process.env.LANGFUSE_HOST || process.env.LANGFUSE_BASEURL || 'https://cloud.langfuse.com',
96+
flushAt: Number.parseInt(process.env.LANGFUSE_FLUSH_AT || '15'),
97+
flushInterval: Number.parseInt(process.env.LANGFUSE_FLUSH_INTERVAL || '10000'),
98+
release: process.env.LANGFUSE_RELEASE,
99+
requestTimeout: Number.parseInt(process.env.LANGFUSE_REQUEST_TIMEOUT || '10000'),
100+
enabled: process.env.LANGFUSE_ENABLED !== 'false',
101+
}
102+
103+
langfuseState.handler = new LoggingCallbackHandler(config) as BaseCallbackHandler
104+
logger.debug('Langfuse observability initialized successfully with logging enabled')
105+
106+
// Also initialize the client for direct usage if needed
107+
try {
108+
const langfuseCore = await import('langfuse').catch(() => null)
109+
if (langfuseCore) {
110+
const { Langfuse } = langfuseCore as any
111+
langfuseState.client = new Langfuse({
112+
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
113+
secretKey: process.env.LANGFUSE_SECRET_KEY,
114+
baseUrl: process.env.LANGFUSE_HOST || 'https://cloud.langfuse.com',
115+
})
116+
logger.debug('Langfuse client initialized')
117+
}
118+
}
119+
catch (error) {
120+
logger.debug(`Langfuse client initialization failed: ${error}`)
121+
}
122+
}
123+
catch (error) {
124+
logger.debug(`Langfuse initialization error: ${error}`)
125+
}
126+
}
127+
128+
// Only initialize if not disabled and required keys are present
129+
if (langfuseDisabled) {
130+
logger.debug('Langfuse tracing disabled via MCP_USE_LANGFUSE environment variable')
131+
}
132+
else if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) {
133+
logger.debug(
134+
'Langfuse API keys not found - tracing disabled. Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY to enable',
135+
)
136+
}
137+
else {
138+
// Create initialization promise to ensure handlers are ready when needed
139+
langfuseState.initPromise = initializeLangfuse()
140+
}
141+
142+
// Export getters to access the state
143+
export const langfuseHandler = () => langfuseState.handler
144+
export const langfuseClient = () => langfuseState.client
145+
export const langfuseInitPromise = () => langfuseState.initPromise

0 commit comments

Comments
 (0)