Skip to content
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

fix: return actual source nodes with compact and refine response synt… #1554

Merged
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
5 changes: 5 additions & 0 deletions .changeset/new-cups-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@llamaindex/core": patch
---

The compact and refine response synthesizer (retrieved by using `getResponseSynthesizer('compact')`) has been fixed to return the original source nodes that were provided to it in its response. Previous to this it was returning the compacted text chunk documents.
31 changes: 29 additions & 2 deletions packages/core/src/response-synthesizers/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ class Refine extends BaseSynthesizer {
}
}

async getResponse(
query: MessageContent,
nodes: NodeWithScore[],
stream: true,
): Promise<AsyncIterable<EngineResponse>>;
async getResponse(
query: MessageContent,
nodes: NodeWithScore[],
stream: false,
): Promise<EngineResponse>;
Comment on lines +80 to +89
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these signatures so that I didn't have to do more type jumbling after grabbing the original response and transforming it. If this is not accurate please let me know, but based on my knowledge if stream = true it will always be an AsyncIterable.

async getResponse(
query: MessageContent,
nodes: NodeWithScore[],
Expand Down Expand Up @@ -197,6 +207,16 @@ class Refine extends BaseSynthesizer {
* CompactAndRefine is a slight variation of Refine that first compacts the text chunks into the smallest possible number of chunks.
*/
class CompactAndRefine extends Refine {
async getResponse(
query: MessageContent,
nodes: NodeWithScore[],
stream: true,
): Promise<AsyncIterable<EngineResponse>>;
async getResponse(
query: MessageContent,
nodes: NodeWithScore[],
stream: false,
): Promise<EngineResponse>;
async getResponse(
query: MessageContent,
nodes: NodeWithScore[],
Expand All @@ -216,17 +236,24 @@ class CompactAndRefine extends Refine {
const newTexts = this.promptHelper.repack(maxPrompt, textChunks);
const newNodes = newTexts.map((text) => new TextNode({ text }));
if (stream) {
return super.getResponse(
const streamResponse = await super.getResponse(
query,
newNodes.map((node) => ({ node })),
true,
);
return streamConverter(streamResponse, (chunk) => {
chunk.sourceNodes = nodes;
return chunk;
});
}
return super.getResponse(

const originalResponse = await super.getResponse(
query,
newNodes.map((node) => ({ node })),
false,
);
originalResponse.sourceNodes = nodes;
return originalResponse;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, expect, test, vi } from "vitest";
import type { LLMMetadata } from "../../llms/dist/index.js";
import { getResponseSynthesizer } from "../../response-synthesizers/dist/index.js";
import { Document } from "../../schema/dist/index.js";

const mockLllm = () => ({
complete: vi.fn().mockImplementation(({ stream }) => {
const response = { text: "unimportant" };
if (!stream) {
return response;
}

function* gen() {
// yield a few times to make sure each chunk has the sourceNodes
yield response;
yield response;
yield response;
}

return gen();
}),
chat: vi.fn(),
metadata: {} as unknown as LLMMetadata,
});

describe("compact and refine response synthesizer", () => {
describe("synthesize", () => {
test("should return original sourceNodes with response when stream = false", async () => {
const synthesizer = getResponseSynthesizer("compact", {
llm: mockLllm(),
});

const sourceNode = { node: new Document({}), score: 1 };

const response = await synthesizer.synthesize(
{
query: "test",
nodes: [sourceNode],
},
false,
);

expect(response.sourceNodes).toEqual([sourceNode]);
});

test("should return original sourceNodes with response when stream = true", async () => {
const synthesizer = getResponseSynthesizer("compact", {
llm: mockLllm(),
});

const sourceNode = { node: new Document({}), score: 1 };

const response = await synthesizer.synthesize(
{
query: "test",
nodes: [sourceNode],
},
true,
);

for await (const chunk of response) {
expect(chunk.sourceNodes).toEqual([sourceNode]);
}
});
});
});
Loading