Closed mattiaseyram closed 3 months ago
To address the issue of trace ID propagation in LangChain.js when using traceable
functions and invoking chains, it's essential to ensure that the trace ID is consistently passed through all operations. This can be achieved by implementing a mechanism that explicitly carries the trace ID across different layers of your application, especially when dealing with asynchronous operations or callbacks.
For operations wrapped with traceable
, you should ensure that any chain invocation within these functions explicitly receives the current trace ID. This might involve modifying the traceable
wrapper or the functions it wraps to accept and forward a trace context that includes the trace ID. Similarly, when chains call traceable
functions internally, they should pass along the trace context they're operating within.
In practical terms, this could mean adjusting the signature of your functions to include a trace context parameter or leveraging a context propagation library that automatically handles this for you, depending on the execution environment of your JavaScript/TypeScript application.
For RunnableLambda
and similar constructs, consider enhancing their invocation mechanism to accept and propagate trace context. This ensures that when these runnables are executed, they're aware of the trace ID and can include it in any logging or tracing operations they perform.
This approach requires a consistent implementation across all parts of your system that participate in tracing to ensure that the trace ID is not lost at any point in the execution chain. By doing so, you'll achieve a seamless traceability experience that allows you to monitor and debug your application's behavior more effectively.
If you're implementing custom logging or tracing, ensure that every log entry or trace event includes the trace ID. This might involve adjusting your logging framework or tracing library configurations to automatically include this information based on the current execution context.
By focusing on these key areas, you should be able to resolve the issue with trace ID propagation in your LangChain.js application, ensuring that all related operations are correctly grouped under a single trace ID for easier monitoring and debugging.
Update: I'm using a custom runnable lambda class now to meet my needs:
/* eslint-disable no-unused-vars */
import {
RunnableConfig,
RunnableLambda,
patchConfig,
} from '@langchain/core/runnables';
import { CallbackManagerForChainRun } from 'langchain/callbacks';
export const DEFAULT_RECURSION_LIMIT = 25;
export type MyRunnableFuncOptions = {
config?: RunnableConfig;
internalInvokeConfig?: Partial<RunnableConfig>;
} & RunnableConfig;
export type MyRunnableFunc<RunInput, RunOutput> = (
input: RunInput,
options?: MyRunnableFuncOptions,
) => Promise<RunOutput>;
export class MyRunnableLambda<RunInput, RunOutput> extends RunnableLambda<
RunInput,
RunOutput
> {
static myFrom<RunInput, RunOutput>(
func: MyRunnableFunc<RunInput, RunOutput>,
name?: string,
): RunnableLambda<RunInput, RunOutput> {
const myRunnableLambda = new MyRunnableLambda({ func });
myRunnableLambda.name = name || func.name;
return myRunnableLambda;
}
async __invoke(
input: RunInput,
config?: Partial<RunnableConfig>,
runManager?: CallbackManagerForChainRun,
): Promise<RunOutput> {
const internalInvokeConfig = patchConfig(config, {
callbacks: runManager?.getChild(),
recursionLimit: (config?.recursionLimit ?? DEFAULT_RECURSION_LIMIT) - 2,
});
return this._invoke(
input,
{
...config,
// @ts-ignore
internalInvokeConfig,
},
runManager,
);
}
async invoke(
input: RunInput,
options?: Partial<RunnableConfig>,
): Promise<RunOutput> {
return this._callWithConfig(this.__invoke, input, options);
}
}
// const myInternalRunnableLambda = MyRunnableLambda.myFrom<string, string>(
// async (input, options) => {
// console.log('myInternalRunnableLambda input:', input);
// return input.toUpperCase();
// },
// 'myInternalRunnableLambda',
// );
// const myRunnableLambda = MyRunnableLambda.myFrom<[string, string], string>(
// async (input: [string, string], options) => {
// console.log('myRunnableLambda input:', input);
// const internalResult = await myInternalRunnableLambda.invoke(
// input[0],
// options?.internalInvokeConfig,
// );
// return `${internalResult} ${input[1]}!`;
// },
// 'myRunnableLambda',
// );
// const result = await myRunnableLambda.invoke(['hello', 'world']);
Thanks for flagging - will have a look!
Same issue with RunTree's, using chain.invoke inside creates a new trace. Interestingly they seem to re-use the traceId when inside a LangGraph node
Also interesting to note, if you set metadata at the end of a runnable it will force a new trace to be created when inside a LangGraph node
i.e.
const response = await ChatPromptTemplate.fromMessages([
['human', `When was {name} born?`],
])
.pipe(llm)
.pipe(parser)
.invoke(
{ name: 'Albert Einstein' },
{
metadata: {
example: 'metadata'
}
},
);
Ah sorry this should now be fixed! Bump to the latest versions of @langchain/core
and langsmith
Checked other resources
Example Code
Error Message and Stack Trace (if applicable)
No response
Description
Any chains invoked inside a function wrapped with traceable have separate trace ids (or vice versa, with chains calling traceable functions internally).
Ideally the trace id gets propagated automatically.
Related to https://github.com/langchain-ai/langsmith-sdk/issues/598
System Info
langchain: latest platform: mac node: v20.11.1 pnpm: 8.15.5