Skip to content

Commit 98221c8

Browse files
committed
WIP langsmith & AI SDK integration
1 parent 46a37fd commit 98221c8

File tree

7 files changed

+94
-4
lines changed

7 files changed

+94
-4
lines changed

packages/cli-v3/src/entryPoints/dev-run-worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ async function bootstrap() {
129129
const tracingSDK = new TracingSDK({
130130
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
131131
instrumentations: config.instrumentations ?? [],
132+
exporters: config.exporters ?? [],
132133
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
133134
forceFlushTimeoutMillis: 30_000,
134135
});

packages/core/src/v3/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export type TriggerConfig = {
5656
*/
5757
instrumentations?: Array<Instrumentation>;
5858

59+
exporters?: Array<any>;
60+
5961
/**
6062
* Specify a custom path to your tsconfig file. This is useful if you have a custom tsconfig file that you want to use.
6163
*/

packages/core/src/v3/otel/tracingSDK.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export type TracingSDKConfig = {
8585
forceFlushTimeoutMillis?: number;
8686
resource?: IResource;
8787
instrumentations?: Instrumentation[];
88+
exporters?: SpanExporter[];
8889
diagLogLevel?: TracingDiagnosticLogLevel;
8990
};
9091

@@ -153,6 +154,10 @@ export class TracingSDK {
153154
)
154155
);
155156

157+
for (const exporter of config.exporters ?? []) {
158+
traceProvider.addSpanProcessor(new SimpleSpanProcessor(exporter));
159+
}
160+
156161
traceProvider.register();
157162

158163
registerInstrumentations({

pnpm-lock.yaml

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

references/nextjs-realtime/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"class-variance-authority": "^0.7.0",
2828
"clsx": "^2.1.1",
2929
"date-fns": "^4.1.0",
30+
"langsmith": "^0.2.15",
3031
"lucide-react": "^0.451.0",
3132
"next": "14.2.15",
3233
"openai": "^4.68.4",

references/nextjs-realtime/src/trigger/ai.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { streamText, type TextStreamPart } from "ai";
44
import { setTimeout } from "node:timers/promises";
55
import { z } from "zod";
66
import OpenAI from "openai";
7+
import { AISDKExporter } from "langsmith/vercel";
78

89
const openaiSDK = new OpenAI({
910
apiKey: process.env.OPENAI_API_KEY,
@@ -98,13 +99,14 @@ export const openaiStreaming = schemaTask({
9899
run: async ({ model, prompt }) => {
99100
logger.info("Running OpenAI model", { model, prompt });
100101

102+
const telemetrySettings = AISDKExporter.getSettings();
103+
104+
logger.info("Telemetry settings", { telemetrySettings });
105+
101106
const result = streamText({
102107
model: openai(model),
103108
prompt,
104-
tools: {
105-
getWeather: weatherTask.tool,
106-
},
107-
maxSteps: 10,
109+
experimental_telemetry: telemetrySettings,
108110
});
109111

110112
const stream = await metadata.stream("openai", result.fullStream);

references/nextjs-realtime/trigger.config.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
11
import { defineConfig } from "@trigger.dev/sdk/v3";
22
import { rscExtension } from "@trigger.dev/rsc";
3+
import { AISDKExporter } from "langsmith/vercel";
4+
import { Client } from "langsmith";
5+
6+
class LangsmithSpanExporterWrapper {
7+
constructor(
8+
private underlyingExporter: any,
9+
private transformSpan: (span: any) => any | undefined = (span) => {
10+
if (span.attributes["$span.partial"]) {
11+
// Skip partial spans
12+
return;
13+
}
14+
15+
// Check if this is an attempt span
16+
if (span.name.startsWith("Attempt ")) {
17+
// Create a new span that wraps the original but modifies spanContext
18+
const spanContext = span.spanContext();
19+
20+
return {
21+
...span,
22+
spanContext: () => spanContext,
23+
parentSpanId: undefined,
24+
};
25+
}
26+
return span;
27+
}
28+
) {}
29+
30+
export(spans: any[], resultCallback: (result: any) => void): void {
31+
const modifiedSpans = spans.map(this.transformSpan);
32+
this.underlyingExporter.export(modifiedSpans.filter(Boolean), resultCallback);
33+
}
34+
35+
shutdown(): Promise<void> {
36+
return this.underlyingExporter.shutdown();
37+
}
38+
39+
forceFlush?(): Promise<void> {
40+
return this.underlyingExporter.forceFlush
41+
? this.underlyingExporter.forceFlush()
42+
: Promise.resolve();
43+
}
44+
}
45+
46+
const client = new Client();
47+
48+
const exporter = new AISDKExporter({
49+
debug: true,
50+
client,
51+
});
352

453
export default defineConfig({
554
project: "proj_bzhdaqhlymtuhlrcgbqy",
655
dirs: ["./src/trigger"],
56+
exporters: [new LangsmithSpanExporterWrapper(exporter)],
757
build: {
858
extensions: [rscExtension({ reactDomEnvironment: "worker" })],
959
},

0 commit comments

Comments
 (0)