-
Notifications
You must be signed in to change notification settings - Fork 0
fix: emit Langfuse generations for root RLM calls #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -426,6 +426,12 @@ export async function rlmLoop( | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Call LLM | ||||||||||||||||||||||||||
| const llmStartMs = Date.now(); | ||||||||||||||||||||||||||
| const generationId = langfuse.rootGenerationStart({ | ||||||||||||||||||||||||||
| name: `Model call — root iteration ${iteration + 1}`, | ||||||||||||||||||||||||||
| input: messages, | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Passing the live Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||
| model: `${config.model.provider}/${config.model.model}`, | ||||||||||||||||||||||||||
| iteration, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+429
to
+434
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Snapshot
🐛 Capture a snapshot at start time const generationId = langfuse.rootGenerationStart({
name: `Model call — root iteration ${iteration + 1}`,
- input: messages,
+ input: [...messages],
model: `${config.model.provider}/${config.model.model}`,
iteration,
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| const response = await llmComplete(messages, config.model, { | ||||||||||||||||||||||||||
| signal: abortController.signal, | ||||||||||||||||||||||||||
| cacheConfig, | ||||||||||||||||||||||||||
|
|
@@ -434,6 +440,11 @@ export async function rlmLoop( | |||||||||||||||||||||||||
| geminiConfig: config.gemini, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| const llmDurationMs = Date.now() - llmStartMs; | ||||||||||||||||||||||||||
| langfuse.rootGenerationEnd(generationId, { | ||||||||||||||||||||||||||
| output: response.text, | ||||||||||||||||||||||||||
| durationMs: llmDurationMs, | ||||||||||||||||||||||||||
| usage: response.usage, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| mergeUsage(usage, response.usage); | ||||||||||||||||||||||||||
| budget.record(response.usage.inputTokens, response.usage.outputTokens, response.usage.totalCost); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -664,7 +675,7 @@ export async function rlmLoop( | |||||||||||||||||||||||||
| logVerbose(actualIterations, `${reason}, forcing final answer`); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const forcedResult = await forceFinalAnswer(messages, config, usage, abortController.signal, cacheConfig); | ||||||||||||||||||||||||||
| const forcedResult = await forceFinalAnswer(messages, config, usage, abortController.signal, cacheConfig, langfuse, actualIterations); | ||||||||||||||||||||||||||
| return finalize(forcedResult, actualIterations); | ||||||||||||||||||||||||||
| } catch (err: unknown) { | ||||||||||||||||||||||||||
| clearTimeout(timeoutHandle); | ||||||||||||||||||||||||||
|
|
@@ -697,7 +708,9 @@ async function forceFinalAnswer( | |||||||||||||||||||||||||
| config: RlmxConfig, | ||||||||||||||||||||||||||
| usage: UsageStats, | ||||||||||||||||||||||||||
| signal?: AbortSignal, | ||||||||||||||||||||||||||
| cacheConfig?: CacheLLMConfig | ||||||||||||||||||||||||||
| cacheConfig?: CacheLLMConfig, | ||||||||||||||||||||||||||
| langfuse?: LangfuseTraceRecorder, | ||||||||||||||||||||||||||
| iteration = 0 | ||||||||||||||||||||||||||
| ): Promise<string> { | ||||||||||||||||||||||||||
| const forceMessages: ChatMessage[] = [ | ||||||||||||||||||||||||||
| ...messages, | ||||||||||||||||||||||||||
|
|
@@ -708,13 +721,27 @@ async function forceFinalAnswer( | |||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const generationId = langfuse?.rootGenerationStart({ | ||||||||||||||||||||||||||
| name: "Model call — forced final answer", | ||||||||||||||||||||||||||
| input: forceMessages, | ||||||||||||||||||||||||||
| model: `${config.model.provider}/${config.model.model}`, | ||||||||||||||||||||||||||
| iteration, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| const llmStartMs = Date.now(); | ||||||||||||||||||||||||||
| const response = await llmComplete(forceMessages, config.model, { | ||||||||||||||||||||||||||
| signal, | ||||||||||||||||||||||||||
| cacheConfig, | ||||||||||||||||||||||||||
| thinkingLevel: config.gemini.thinkingLevel, | ||||||||||||||||||||||||||
| outputSchema: config.output.schema, | ||||||||||||||||||||||||||
| geminiConfig: config.gemini, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| if (generationId) { | ||||||||||||||||||||||||||
| langfuse?.rootGenerationEnd(generationId, { | ||||||||||||||||||||||||||
| output: response.text, | ||||||||||||||||||||||||||
| durationMs: Date.now() - llmStartMs, | ||||||||||||||||||||||||||
| usage: response.usage, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+724
to
+744
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the main loop iteration, if const generationId = langfuse?.rootGenerationStart({
name: "Model call — forced final answer",
input: forceMessages,
model: `${config.model.provider}/${config.model.model}`,
iteration,
});
const llmStartMs = Date.now();
let response: any;
try {
response = await llmComplete(forceMessages, config.model, {
signal,
cacheConfig,
thinkingLevel: config.gemini.thinkingLevel,
outputSchema: config.output.schema,
geminiConfig: config.gemini,
});
if (generationId) {
langfuse?.rootGenerationEnd(generationId, {
output: response.text,
durationMs: Date.now() - llmStartMs,
usage: response.usage,
});
}
} catch (err) {
if (generationId) {
langfuse?.rootGenerationEnd(generationId, {
output: null,
durationMs: Date.now() - llmStartMs,
isError: true,
errorMessage: err instanceof Error ? err.message : String(err),
});
}
throw err;
} |
||||||||||||||||||||||||||
| mergeUsage(usage, response.usage); | ||||||||||||||||||||||||||
| return response.text; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Langfuse uses the standard top-level
costfield (a number) to track and display generation costs in the UI. Passing onlycostDetailsmight result in the cost not being displayed correctly in standard Langfuse cost columns. Addingcostat the top level of the payload ensures standard cost tracking works out of the box.