Skip to content

Commit 6f500bf

Browse files
committed
feat(component): add langfuse support
1 parent 253be98 commit 6f500bf

File tree

8 files changed

+208
-4
lines changed

8 files changed

+208
-4
lines changed

README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,118 @@ export class AddTool implements IGraphTool {
492492
}
493493
```
494494

495+
# Observability
496+
497+
## With Langsmith
498+
499+
You can enable langsmith observability by simply adding the Langsmith env variables. Refer [this](https://docs.langchain.com/langsmith/observability-quickstart) for more details.
500+
501+
## With Langfuse
502+
503+
You can enable Langfuse based tracing with the following steps -
504+
505+
- install the following packages -
506+
507+
```sh
508+
npm i @langfuse/core @langfuse/langchain @langfuse/otel
509+
```
510+
511+
- adding the following component in your LB4 application -
512+
513+
```ts
514+
import {LangfuseComponent} from 'lb4-llm-chat-component/langfuse';
515+
...
516+
517+
this.component(LangfuseComponent);
518+
```
519+
520+
- set up langfuseSpanProcessor -
521+
522+
```ts
523+
import {LangfuseSpanProcessor} from '@langfuse/otel';
524+
import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-http';
525+
import {DnsInstrumentation} from '@opentelemetry/instrumentation-dns';
526+
import {HttpInstrumentation} from '@opentelemetry/instrumentation-http';
527+
import {PgInstrumentation} from '@opentelemetry/instrumentation-pg';
528+
import {
529+
defaultResource,
530+
resourceFromAttributes,
531+
} from '@opentelemetry/resources';
532+
import {NodeSDK} from '@opentelemetry/sdk-node';
533+
import {BatchSpanProcessor, SpanProcessor} from '@opentelemetry/sdk-trace-base';
534+
import {
535+
ATTR_SERVICE_NAME,
536+
ATTR_SERVICE_VERSION,
537+
} from '@opentelemetry/semantic-conventions';
538+
import * as dotenv from 'dotenv';
539+
import * as dotenvExt from 'dotenv-extended';
540+
541+
dotenv.config();
542+
dotenvExt.load({
543+
schema: '.env.example',
544+
errorOnMissing: true,
545+
includeProcessEnv: true,
546+
});
547+
548+
if (!!+(process.env.ENABLE_TRACING ?? 0)) {
549+
const resource = defaultResource().merge(
550+
resourceFromAttributes({
551+
[ATTR_SERVICE_NAME]: process.env.SERVICE_NAME ?? 'reporting-service',
552+
[ATTR_SERVICE_VERSION]: process.env.SERVICE_VERSION ?? '1.0.0',
553+
}),
554+
);
555+
556+
const spanProcessors: SpanProcessor[] = [];
557+
const instrumentations = [];
558+
559+
// Add OTLP exporter if Jaeger endpoint is configured
560+
if (process.env.OPENTELEMETRY_HOST && process.env.OPENTELEMETRY_PORT) {
561+
const otlpExporter = new OTLPTraceExporter({
562+
url: `http://${process.env.OPENTELEMETRY_HOST}:${process.env.OPENTELEMETRY_PORT}/v1/traces`,
563+
});
564+
spanProcessors.push(new BatchSpanProcessor(otlpExporter));
565+
instrumentations.push(
566+
new HttpInstrumentation(),
567+
new DnsInstrumentation(),
568+
new PgInstrumentation(),
569+
);
570+
}
571+
572+
if (process.env.LANGFUSE_BASE_URL) {
573+
console.log('Langfuse tracing enabled');
574+
spanProcessors.push(new LangfuseSpanProcessor());
575+
}
576+
577+
const sdk = new NodeSDK({
578+
resource: resource,
579+
spanProcessors: spanProcessors,
580+
instrumentations,
581+
});
582+
583+
// Initialize the SDK
584+
console.log('Starting OpenTelemetry SDK');
585+
sdk.start();
586+
587+
// Graceful shutdown
588+
process.on('SIGTERM', () => {
589+
sdk
590+
.shutdown()
591+
.then(() => console.log('Tracing terminated'))
592+
.catch(error => console.log('Error terminating tracing', error))
593+
.finally(() => process.exit(0));
594+
});
595+
}
596+
```
597+
598+
- setup env -
599+
600+
```sh
601+
export ENABLE_TRACING=1
602+
export LANGFUSE_SECRET_KEY="sk-lf-..."
603+
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
604+
export LANGFUSE_BASE_URL="https://cloud.langfuse.com"
605+
```
606+
495607
# Testing
496608

497609
## Generation Acceptance Builder

package-lock.json

Lines changed: 58 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
"./db-query/testing": {
4949
"type": "./dist/components/db-query/testing/index.d.ts",
5050
"default": "./dist/components/db-query/testing/index.js"
51+
},
52+
"./langfuse": {
53+
"type": "./dist/sub-modules/obf/langfuse/index.d.ts",
54+
"default": "./dist/sub-modules/obf/langfuse/index.js"
5155
}
5256
},
5357
"typesVersions": {
@@ -78,6 +82,9 @@
7882
],
7983
"db-query/testing": [
8084
"dist/components/db-query/testing/index.d.ts"
85+
],
86+
"langfuse": [
87+
"dist/sub-modules/obf/langfuse/index.d.ts"
8188
]
8289
}
8390
},
@@ -144,6 +151,8 @@
144151
"@langchain/groq": "^0.2.3",
145152
"@langchain/ollama": "^0.2.3",
146153
"@langchain/openai": "^0.6.11",
154+
"@langfuse/core": "^4.4.2",
155+
"@langfuse/langchain": "^4.4.2",
147156
"@loopback/build": "^11.0.9",
148157
"@loopback/eslint-config": "^15.0.5",
149158
"@loopback/testlab": "^7.0.9",

src/graphs/chat/chat.graph.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {ToolStore} from '../../types';
77
import {BaseGraph} from '../base.graph';
88
import {ChatGraphAnnotation, ChatState} from '../state';
99
import {ChatNodes} from './nodes.enum';
10+
import {AnyObject} from '@loopback/repository';
1011

1112
@injectable({scope: BindingScope.REQUEST})
1213
export class ChatGraph extends BaseGraph<ChatState> {
@@ -15,6 +16,8 @@ export class ChatGraph extends BaseGraph<ChatState> {
1516
private readonly tools: ToolStore,
1617
@inject('services.TokenCounter')
1718
private readonly tokenCounter: TokenCounter,
19+
@inject(AiIntegrationBindings.ObfHandler, {optional: true})
20+
protected readonly obfHandler?: AnyObject[string],
1821
) {
1922
super();
2023
}
@@ -71,6 +74,7 @@ export class ChatGraph extends BaseGraph<ChatState> {
7174
this.tokenCounter.handleLlmEnd(runId, output);
7275
},
7376
},
77+
this.obfHandler ? this.obfHandler : {},
7478
],
7579
});
7680
}

src/keys.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export namespace AiIntegrationBindings {
4343
export const LimitStrategy = BindingKey.create<ILimitStrategy>(
4444
'services.ai-reporting.limit-strategy',
4545
);
46+
export const ObfHandler = BindingKey.create<Function>(
47+
'services.ai-reporting.obf-handler',
48+
);
4649
}
4750
export const WriterDB = 'writerdb';
4851
export const ReaderDB = 'readerdb';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './langfuse.component';
2+
export * from './langfuse.provider';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {Component, ProviderMap} from '@loopback/core';
2+
import {AiIntegrationBindings} from '../../../keys';
3+
import {LangfuseObfProvider} from './langfuse.provider';
4+
5+
export class LangfuseComponent implements Component {
6+
providers?: ProviderMap | undefined;
7+
constructor() {
8+
this.providers = {
9+
[AiIntegrationBindings.ObfHandler.key]: LangfuseObfProvider,
10+
};
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {Provider, ValueOrPromise} from '@loopback/core';
2+
import {CallbackHandler} from '@langfuse/langchain';
3+
4+
export class LangfuseObfProvider implements Provider<CallbackHandler> {
5+
value(): ValueOrPromise<CallbackHandler> {
6+
return new CallbackHandler();
7+
}
8+
}

0 commit comments

Comments
 (0)