Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,13 @@ export class Run<_T extends t.BaseGraphState> {
) {
const userId = config.configurable?.user_id;
const sessionId = config.configurable?.thread_id;
const primaryContext = this.Graph.agentContexts.get(
this.Graph.defaultAgentId
);
const traceMetadata = {
messageId: this.id,
parentMessageId: config.configurable?.requestBody?.parentMessageId,
agentName: primaryContext?.name,
};
const handler = new CallbackHandler({
userId,
Expand Down Expand Up @@ -453,8 +457,12 @@ export class Run<_T extends t.BaseGraphState> {
) {
const userId = chainOptions.configurable?.user_id;
const sessionId = chainOptions.configurable?.thread_id;
const titleContext = this.Graph?.agentContexts.get(
this.Graph.defaultAgentId
);
const traceMetadata = {
messageId: 'title-' + this.id,
agentName: titleContext?.name,
};
const handler = new CallbackHandler({
userId,
Expand Down
91 changes: 91 additions & 0 deletions src/specs/langfuse-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { CallbackHandler } from '@langfuse/langchain';
import { Providers } from '@/common';
import { Run } from '@/run';

jest.mock('@langfuse/langchain', () => ({
CallbackHandler: jest.fn().mockImplementation(() => ({})),
}));

const MockedCallbackHandler = CallbackHandler as jest.MockedClass<
typeof CallbackHandler
>;

async function createTestRun(agentName?: string): Promise<Run<never>> {
const run = await Run.create({
runId: 'test-run-id',
graphConfig: {
type: 'standard',
agents: [
{
agentId: 'agent_abc123',
...(agentName != null && { name: agentName }),
provider: Providers.OPENAI,
clientOptions: { model: 'gpt-4' },
tools: [],
},
],
},
});

const emptyStream = (async function* (): AsyncGenerator {
/* no events */
})();
run.graphRunnable = { streamEvents: () => emptyStream } as never;

return run;
}

describe('Langfuse trace metadata includes agentName', () => {
const originalEnv = process.env;

beforeEach(() => {
jest.clearAllMocks();
process.env = {
...originalEnv,
LANGFUSE_SECRET_KEY: 'sk-test',
LANGFUSE_PUBLIC_KEY: 'pk-test',
LANGFUSE_BASE_URL: 'https://langfuse.test',
};
});

afterEach(() => {
process.env = originalEnv;
});

it('passes agentName in processStream traceMetadata when agent has a name', async () => {
const run = await createTestRun('DWAINE');
await run.processStream(
{ messages: [] },
{ configurable: { thread_id: 't1', user_id: 'u1' }, version: 'v2' }
);

expect(MockedCallbackHandler).toHaveBeenCalledTimes(1);
const ctorArgs = MockedCallbackHandler.mock.calls[0][0];
expect(ctorArgs?.traceMetadata).toMatchObject({ agentName: 'DWAINE' });
});

it('falls back to agentId when agent has no explicit name', async () => {
const run = await createTestRun();
await run.processStream(
{ messages: [] },
{ configurable: { thread_id: 't1', user_id: 'u1' }, version: 'v2' }
);

expect(MockedCallbackHandler).toHaveBeenCalledTimes(1);
const ctorArgs = MockedCallbackHandler.mock.calls[0][0];
expect(ctorArgs?.traceMetadata).toMatchObject({
agentName: 'agent_abc123',
});
});

it('does not create CallbackHandler when Langfuse env vars are missing', async () => {
delete process.env.LANGFUSE_SECRET_KEY;
const run = await createTestRun('MAIA');
await run.processStream(
{ messages: [] },
{ configurable: { thread_id: 't1', user_id: 'u1' }, version: 'v2' }
);

expect(MockedCallbackHandler).not.toHaveBeenCalled();
});
});
Loading