Group pipeline runs into Langfuse sessions, tagged by trigger source

Each pipeline invocation (runAsync/searchAsync) now gets a fresh
sessionId, wrapped via the new Tracer.runWithSession() (implemented
with @langfuse/tracing's propagateAttributes, since v5 has no direct
sessionId/tags option on startObservation) so it shows up in
Langfuse's Sessions view. The session is also tagged with what
triggered the run: "cron", "api-list", "api-trigger", "api-search".

Verified: full test suite (79/79) passing, tsc/eslint clean, real
docker compose build + container smoke test against /health.
This commit is contained in:
2026-08-12 22:51:17 +02:00
parent b615f08039
commit e47c57b50d
10 changed files with 135 additions and 52 deletions

View File

@@ -23,24 +23,44 @@ class ThrowingPipeline extends LlmPipeline<void, void> {
function makeTracer(trace: TraceHandle) {
const flush = vi.fn().mockResolvedValue(undefined);
const startTrace = vi.fn().mockReturnValue(trace);
const tracer = { startTrace, flush } as unknown as Tracer;
return { tracer, startTrace, flush };
const runWithSession = vi.fn().mockImplementation((_session: unknown, fn: () => unknown) => fn());
const tracer = { startTrace, flush, runWithSession } as unknown as Tracer;
return { tracer, startTrace, flush, runWithSession };
}
describe('LlmPipeline', () => {
it('opens one trace, runs runInternal with it, and flushes afterward', async () => {
it('runs the whole call inside a session scoped to a fresh session id, opens one trace, runs runInternal, and flushes afterward', async () => {
const trace = { id: 'trace-1', startGeneration: vi.fn() } as unknown as TraceHandle;
const { tracer, startTrace, flush } = makeTracer(trace);
const { tracer, startTrace, flush, runWithSession } = makeTracer(trace);
const logger = {} as unknown as Logger;
const pipeline = new SumPipeline(tracer, logger);
const result = await pipeline.runAsync([1, 2, 3]);
expect(result).toBe(6);
expect(startTrace).toHaveBeenCalledWith('sum-pipeline');
expect(runWithSession).toHaveBeenCalledWith(
expect.objectContaining({ sessionId: expect.any(String), tags: undefined }),
expect.any(Function)
);
expect(startTrace).toHaveBeenCalledWith('sum-pipeline', { trigger: undefined });
expect(flush).toHaveBeenCalledTimes(1);
});
it('tags the session with the given trigger', async () => {
const trace = { id: 'trace-1', startGeneration: vi.fn() } as unknown as TraceHandle;
const { tracer, startTrace, runWithSession } = makeTracer(trace);
const logger = {} as unknown as Logger;
const pipeline = new SumPipeline(tracer, logger);
await pipeline.runAsync([1, 2, 3], { trigger: 'cron' });
expect(runWithSession).toHaveBeenCalledWith(
expect.objectContaining({ tags: ['cron'] }),
expect.any(Function)
);
expect(startTrace).toHaveBeenCalledWith('sum-pipeline', { trigger: 'cron' });
});
it('still flushes the trace when runInternal throws', async () => {
const trace = { id: 'trace-2', startGeneration: vi.fn() } as unknown as TraceHandle;
const { tracer, flush } = makeTracer(trace);