Skip to content

Commit e14eb6f

Browse files
committed
Refine ChatML tracing
1 parent 4429d4d commit e14eb6f

3 files changed

Lines changed: 33 additions & 51 deletions

File tree

src/index.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { Data, Effect, Layer, Schema } from "effect";
88
import {
99
LangfuseClientService,
1010
createLangfuseClient,
11-
type ActiveGenerationStep,
1211
type LangfuseClient,
1312
type ToolDefinition,
1413
} from "./langfuse.js";
@@ -26,7 +25,9 @@ type SessionNextEvent =
2625
timestamp: number;
2726
assistantMessageID?: string;
2827
agent: string;
29-
model: NonNullable<ActiveGenerationStep["model"]>;
28+
model: Parameters<
29+
LangfuseClient["startActiveGenerationStep"]
30+
>[0]["model"];
3031
snapshot?: string;
3132
};
3233
}
@@ -502,10 +503,7 @@ const main = Effect.gen(function* () {
502503
tool.parameters !== null &&
503504
!Array.isArray(tool.parameters)
504505
? {
505-
parameters: tool.parameters as Record<
506-
string,
507-
unknown
508-
>,
506+
parameters: tool.parameters,
509507
}
510508
: {}),
511509
})),

src/langfuse.ts

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,19 @@ export class LangfuseClient {
205205
}
206206

207207
traceReasoningPart(part: MessagePart) {
208+
if (
209+
part.type !== "reasoning" ||
210+
typeof part.id !== "string" ||
211+
typeof part.sessionID !== "string" ||
212+
typeof part.messageID !== "string" ||
213+
typeof part.text !== "string"
214+
) {
215+
return;
216+
}
217+
208218
const completed = getCompletedReasoningTimestamp(part);
209219

210-
if (!isCompletedReasoningPart(part) || completed === undefined) {
220+
if (completed === undefined) {
211221
return;
212222
}
213223

@@ -252,7 +262,6 @@ export class LangfuseClient {
252262
...input.model,
253263
variant: input.model.variant ?? existingMessageStep.model?.variant,
254264
},
255-
started: input.started,
256265
snapshot: input.snapshot ?? existingMessageStep.snapshot,
257266
};
258267

@@ -291,7 +300,6 @@ export class LangfuseClient {
291300
...input.model,
292301
variant: input.model.variant ?? existingStep.model?.variant,
293302
},
294-
started: input.started,
295303
snapshot: input.snapshot ?? existingStep.snapshot,
296304
};
297305

@@ -358,7 +366,6 @@ export class LangfuseClient {
358366
agent: input.agent,
359367
model: input.model,
360368
span,
361-
started: input.started,
362369
snapshot: input.snapshot,
363370
});
364371
if (messageID) {
@@ -456,7 +463,7 @@ export class LangfuseClient {
456463

457464
const span = this.traceState.tracer.startSpan("opencode.turn", {
458465
attributes: {
459-
"langfuse.observation.type": "span",
466+
"langfuse.observation.type": "agent",
460467
"langfuse.internal.is_app_root": true,
461468
"session.id": input.sessionID,
462469
"langfuse.observation.input": JSON.stringify([formattedMessage]),
@@ -519,26 +526,19 @@ export class LangfuseClient {
519526
this.traceState.assistantParts.set(part.messageID, parts);
520527

521528
if (part.type === "tool") {
522-
this.rememberToolCall({
523-
callID: part.callID,
524-
messageID: part.messageID,
525-
});
529+
this.traceState.toolMessageIdsByCallId.set(part.callID, part.messageID);
526530
}
527531
}
528532

529533
rememberToolCall(input: {
530534
callID: string;
531535
messageID: string;
532-
sessionID?: string;
533-
tool?: string;
534-
args?: Record<string, unknown>;
536+
sessionID: string;
537+
tool: string;
538+
args: Record<string, unknown>;
535539
}) {
536540
this.traceState.toolMessageIdsByCallId.set(input.callID, input.messageID);
537541

538-
if (!input.sessionID || !input.tool || !input.args) {
539-
return;
540-
}
541-
542542
const parts =
543543
this.traceState.assistantParts.get(input.messageID) ??
544544
new Map<string, MessagePart>();
@@ -1207,36 +1207,17 @@ export type MessagePart = Extract<
12071207
{ type: "message.part.updated" }
12081208
>["properties"]["part"];
12091209

1210-
type CompletedReasoningPart = MessagePart & {
1211-
id: string;
1212-
sessionID: string;
1213-
text: string;
1214-
messageID: string;
1215-
time: { completed?: number; end?: number };
1216-
};
1217-
1218-
function isCompletedReasoningPart(
1219-
part: MessagePart,
1220-
): part is CompletedReasoningPart {
1221-
return (
1222-
part.type === "reasoning" &&
1223-
typeof part.id === "string" &&
1224-
typeof part.sessionID === "string" &&
1225-
typeof part.messageID === "string" &&
1226-
typeof part.text === "string" &&
1227-
typeof getCompletedReasoningTimestamp(part) === "number"
1228-
);
1229-
}
1230-
12311210
function getCompletedReasoningTimestamp(part: MessagePart) {
1232-
const time = (part as { time?: { completed?: unknown; end?: unknown } }).time;
1211+
if (!("time" in part) || !part.time || typeof part.time !== "object") {
1212+
return undefined;
1213+
}
12331214

1234-
if (typeof time?.completed === "number") {
1235-
return time.completed;
1215+
if ("completed" in part.time && typeof part.time.completed === "number") {
1216+
return part.time.completed;
12361217
}
12371218

1238-
if (typeof time?.end === "number") {
1239-
return time.end;
1219+
if ("end" in part.time && typeof part.time.end === "number") {
1220+
return part.time.end;
12401221
}
12411222

12421223
return undefined;
@@ -1266,7 +1247,7 @@ export type UserMessageInput = {
12661247
export type ToolDefinition = {
12671248
name: string;
12681249
description?: string;
1269-
parameters?: Record<string, unknown>;
1250+
parameters?: object;
12701251
};
12711252

12721253
type ChatMlMessage =
@@ -1301,9 +1282,7 @@ export type ActiveGenerationStep = {
13011282
variant?: string;
13021283
};
13031284
span: ApiSpan;
1304-
started?: number;
13051285
snapshot?: string;
1306-
input?: ChatMlMessage[];
13071286
};
13081287

13091288
export class LangfuseClientService extends EffectContext.Tag(

test/integration/plugin.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,11 @@ describe.sequential("built plugin", () => {
578578
},
579579
]);
580580
}
581+
for (const turn of spans.filter((span) => span.name === "opencode.turn")) {
582+
expect(getAttributes(turn)).toMatchObject({
583+
"langfuse.observation.type": "agent",
584+
});
585+
}
581586

582587
const firstGeneration = spans
583588
.filter((span) => span.name === "opencode.generation")

0 commit comments

Comments
 (0)