Skip to content

Commit ab2e577

Browse files
committed
missing files
1 parent 4802f7c commit ab2e577

File tree

3 files changed

+259
-0
lines changed

3 files changed

+259
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ChatBedrockConverse } from "@langchain/aws";
2+
import * as traceloop from "@traceloop/node-server-sdk";
3+
4+
traceloop.initialize({
5+
appName: "sample_langchain_bedrock",
6+
apiKey: process.env.TRACELOOP_API_KEY,
7+
disableBatch: true,
8+
});
9+
10+
async function main() {
11+
const model = new ChatBedrockConverse({
12+
model: "anthropic.claude-3-haiku-20240307-v1:0",
13+
});
14+
15+
const response = await model.invoke("Tell me a joke about opentelemetry");
16+
console.log(response);
17+
}
18+
19+
void main().then(() => {
20+
console.log("Done");
21+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { NodeSDK } from "@opentelemetry/sdk-node";
2+
import { Resource } from "@opentelemetry/resources";
3+
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
4+
import { createSpanProcessor } from "@traceloop/node-server-sdk";
5+
import { trace } from "@opentelemetry/api";
6+
7+
// Initialize the OpenTelemetry SDK with Traceloop's span processor
8+
const sdk = new NodeSDK({
9+
resource: new Resource({
10+
[SemanticResourceAttributes.SERVICE_NAME]: "my-sample-app",
11+
}),
12+
spanProcessors: [
13+
createSpanProcessor({
14+
apiKey: process.env.TRACELOOP_API_KEY,
15+
baseUrl: process.env.TRACELOOP_BASE_URL,
16+
// Optional: disable batching for development
17+
disableBatch: process.env.NODE_ENV === "development",
18+
}),
19+
],
20+
});
21+
22+
// Start the SDK
23+
sdk.start();
24+
25+
// Your application code here
26+
async function main() {
27+
// Example: Create a trace
28+
const tracer = trace.getTracer("my-sample-app");
29+
30+
const span = tracer.startSpan("main");
31+
try {
32+
// Simulate some work
33+
await new Promise(resolve => setTimeout(resolve, 1000));
34+
35+
// Add some attributes that Traceloop's span processor will handle
36+
span.setAttribute("ai.prompt.messages", JSON.stringify([
37+
{ role: "user", content: "Hello, AI!" }
38+
]));
39+
40+
span.end();
41+
} catch (error) {
42+
span.recordException(error);
43+
span.end();
44+
}
45+
}
46+
47+
// Run the app
48+
main().then(() => {
49+
// Gracefully shut down the SDK
50+
sdk.shutdown()
51+
.then(() => console.log("Tracing terminated"))
52+
.catch((error) => console.log("Error terminating tracing", error))
53+
.finally(() => process.exit(0));
54+
}).catch((error) => {
55+
console.error(error);
56+
process.exit(1);
57+
});
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import {
2+
SimpleSpanProcessor,
3+
BatchSpanProcessor,
4+
SpanProcessor,
5+
ReadableSpan,
6+
} from "@opentelemetry/sdk-trace-node";
7+
import { baggageUtils } from "@opentelemetry/core";
8+
import { Span, context } from "@opentelemetry/api";
9+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
10+
import { SpanExporter } from "@opentelemetry/sdk-trace-base";
11+
import {
12+
ASSOCATION_PROPERTIES_KEY,
13+
ENTITY_NAME_KEY,
14+
WORKFLOW_NAME_KEY,
15+
} from "./tracing";
16+
import { SpanAttributes } from "@traceloop/ai-semantic-conventions";
17+
18+
export interface SpanProcessorOptions {
19+
/**
20+
* The API Key for sending traces data. Optional.
21+
* Defaults to the TRACELOOP_API_KEY environment variable.
22+
*/
23+
apiKey?: string;
24+
25+
/**
26+
* The OTLP endpoint for sending traces data. Optional.
27+
* Defaults to TRACELOOP_BASE_URL environment variable or https://api.traceloop.com/
28+
*/
29+
baseUrl?: string;
30+
31+
/**
32+
* Sends traces and spans without batching, for local development. Optional.
33+
* Defaults to false.
34+
*/
35+
disableBatch?: boolean;
36+
37+
/**
38+
* The OpenTelemetry SpanExporter to be used for sending traces data. Optional.
39+
* Defaults to the OTLP exporter.
40+
*/
41+
exporter?: SpanExporter;
42+
43+
/**
44+
* The headers to be sent with the traces data. Optional.
45+
*/
46+
headers?: Record<string, string>;
47+
}
48+
49+
/**
50+
* Creates a span processor with Traceloop's custom span handling logic.
51+
* This can be used independently of the full SDK initialization.
52+
*
53+
* @param options - Configuration options for the span processor
54+
* @returns A configured SpanProcessor instance
55+
*/
56+
export const createSpanProcessor = (options: SpanProcessorOptions): SpanProcessor => {
57+
const headers =
58+
options.headers ||
59+
(process.env.TRACELOOP_HEADERS
60+
? baggageUtils.parseKeyPairsIntoRecord(process.env.TRACELOOP_HEADERS)
61+
: { Authorization: `Bearer ${options.apiKey}` });
62+
63+
const traceExporter =
64+
options.exporter ??
65+
new OTLPTraceExporter({
66+
url: `${options.baseUrl || process.env.TRACELOOP_BASE_URL || "https://api.traceloop.com"}/v1/traces`,
67+
headers,
68+
});
69+
70+
const spanProcessor = options.disableBatch
71+
? new SimpleSpanProcessor(traceExporter)
72+
: new BatchSpanProcessor(traceExporter);
73+
74+
spanProcessor.onStart = (span: Span) => {
75+
const workflowName = context.active().getValue(WORKFLOW_NAME_KEY);
76+
if (workflowName) {
77+
span.setAttribute(
78+
SpanAttributes.TRACELOOP_WORKFLOW_NAME,
79+
workflowName as string,
80+
);
81+
}
82+
83+
const entityName = context.active().getValue(ENTITY_NAME_KEY);
84+
if (entityName) {
85+
span.setAttribute(
86+
SpanAttributes.TRACELOOP_ENTITY_PATH,
87+
entityName as string,
88+
);
89+
}
90+
91+
const associationProperties = context
92+
.active()
93+
.getValue(ASSOCATION_PROPERTIES_KEY);
94+
if (associationProperties) {
95+
for (const [key, value] of Object.entries(associationProperties)) {
96+
span.setAttribute(
97+
`${SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.${key}`,
98+
value,
99+
);
100+
}
101+
}
102+
};
103+
104+
spanProcessor.onEnd = (span: ReadableSpan) => {
105+
// Vercel AI Adapters
106+
const attributes = span.attributes;
107+
108+
// Adapt span names
109+
const nameMap: Record<string, string> = {
110+
"ai.generateText.doGenerate": "ai.generateText.generate",
111+
"ai.streamText.doStream": "ai.streamText.stream",
112+
};
113+
if (span.name in nameMap) {
114+
// Unfortunately, the span name is not writable as this is not the intended behavior
115+
// but it is a workaround to set the correct span name
116+
(span as any).name = nameMap[span.name];
117+
}
118+
119+
if ("ai.response.text" in attributes) {
120+
attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.content`] =
121+
attributes["ai.response.text"];
122+
attributes[`${SpanAttributes.LLM_COMPLETIONS}.0.role`] = "assistant";
123+
delete attributes["ai.response.text"];
124+
}
125+
126+
if ("ai.prompt.messages" in attributes) {
127+
try {
128+
const messages = JSON.parse(attributes["ai.prompt.messages"] as string);
129+
messages.forEach(
130+
(msg: { role: string; content: any }, index: number) => {
131+
attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.content`] =
132+
typeof msg.content === "string"
133+
? msg.content
134+
: JSON.stringify(msg.content);
135+
attributes[`${SpanAttributes.LLM_PROMPTS}.${index}.role`] =
136+
msg.role;
137+
},
138+
);
139+
delete attributes["ai.prompt.messages"];
140+
} catch (e) {
141+
//Skip if JSON parsing fails
142+
}
143+
}
144+
145+
if ("ai.usage.promptTokens" in attributes) {
146+
attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`] =
147+
attributes["ai.usage.promptTokens"];
148+
delete attributes["ai.usage.promptTokens"];
149+
}
150+
151+
if ("ai.usage.completionTokens" in attributes) {
152+
attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`] =
153+
attributes["ai.usage.completionTokens"];
154+
delete attributes["ai.usage.completionTokens"];
155+
}
156+
157+
if (
158+
attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`] &&
159+
attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`]
160+
) {
161+
attributes[`${SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`] =
162+
Number(attributes[`${SpanAttributes.LLM_USAGE_PROMPT_TOKENS}`]) +
163+
Number(attributes[`${SpanAttributes.LLM_USAGE_COMPLETION_TOKENS}`]);
164+
}
165+
};
166+
167+
// TODO: move this to the index.ts file
168+
// if (options.exporter) {
169+
// Telemetry.getInstance().capture("tracer:init", {
170+
// exporter: "custom",
171+
// processor: options.disableBatch ? "simple" : "batch",
172+
// });
173+
// } else {
174+
// Telemetry.getInstance().capture("tracer:init", {
175+
// exporter: options.baseUrl ?? "",
176+
// processor: options.disableBatch ? "simple" : "batch",
177+
// });
178+
// }
179+
180+
return spanProcessor;
181+
}

0 commit comments

Comments
 (0)