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

refactor: unify response and agent response #930

Merged
merged 6 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 16 additions & 5 deletions packages/core/src/Response.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import type { NodeWithScore } from "./Node.js";
import type { ChatMessage, ChatResponse } from "./llm/types.js";

/**
* Response is the output of a LLM
*/
export class Response {
export class Response implements ChatResponse {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Idea: we include message and raw from ChatResponse instead of adding chatResponse: ChatResponse as an attribute to Response

Note: we ChatResponse supports extension with Options we have to add this to this PR (after we agree on the contract)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

another advantage is that Reponse behaves like ChatReponse

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TBD: how to deal with chunks? We have ChatReponseChunks as type returned by LLMs but chat engines are just streaming Response objects.

Suggestion: we add an optional delta attribute to Response

Copy link
Member

Choose a reason for hiding this comment

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

could we don't naming Response it's already a Web API

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

how about EngineResponse (see new code)?

// @deprecated use 'message' instead
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we tell the user to not use response instead migrate to message

response: string;
sourceNodes?: NodeWithScore[];
metadata: Record<string, unknown> = {};

constructor(response: string, sourceNodes?: NodeWithScore[]) {
message: ChatMessage;
raw: object | null;

constructor(
response: string,
sourceNodes?: NodeWithScore[],
chatResponse?: ChatResponse,
) {
this.response = response;
this.sourceNodes = sourceNodes || [];
this.message = chatResponse?.message ?? {
content: response,
role: "assistant",
};
this.raw = chatResponse?.raw ?? null;
}

protected _getFormattedSources() {
Expand Down
13 changes: 3 additions & 10 deletions packages/core/src/agent/anthropic.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Response } from "../Response.js";
import { Settings } from "../Settings.js";
import {
type ChatEngineParamsNonStreaming,
type ChatEngineParamsStreaming,
} from "../engines/chat/index.js";
import { stringifyJSONToMessageContent } from "../internal/utils.js";
import { Anthropic } from "../llm/anthropic.js";
import type { ToolCallLLMMessageOptions } from "../llm/index.js";
import { ObjectRetriever } from "../objects/index.js";
import type { BaseToolWithCall } from "../types.js";
import {
AgentRunner,
AgentWorker,
type AgentChatResponse,
type AgentParamsBase,
} from "./base.js";
import { AgentRunner, AgentWorker, type AgentParamsBase } from "./base.js";
import type { TaskHandler } from "./types.js";
import { callTool } from "./utils.js";

Expand Down Expand Up @@ -56,9 +51,7 @@ export class AnthropicAgent extends AgentRunner<Anthropic> {

createStore = AgentRunner.defaultCreateStore;

async chat(
params: ChatEngineParamsNonStreaming,
): Promise<AgentChatResponse<ToolCallLLMMessageOptions>>;
async chat(params: ChatEngineParamsNonStreaming): Promise<Response>;
async chat(params: ChatEngineParamsStreaming): Promise<never>;
override async chat(
params: ChatEngineParamsNonStreaming | ChatEngineParamsStreaming,
Expand Down
61 changes: 15 additions & 46 deletions packages/core/src/agent/base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReadableStream, TransformStream, randomUUID } from "@llamaindex/env";
import { Response } from "../Response.js";
import { Settings } from "../Settings.js";
import {
type ChatEngine,
Expand All @@ -9,13 +10,8 @@ import { wrapEventCaller } from "../internal/context/EventCaller.js";
import { consoleLogger, emptyLogger } from "../internal/logger.js";
import { getCallbackManager } from "../internal/settings/CallbackManager.js";
import { isAsyncIterable } from "../internal/utils.js";
import type {
ChatMessage,
ChatResponse,
ChatResponseChunk,
LLM,
MessageContent,
} from "../llm/index.js";
import type { ChatMessage, LLM, MessageContent } from "../llm/index.js";
import { extractText } from "../llm/utils.js";
import type { BaseToolWithCall, ToolOutput } from "../types.js";
import type {
AgentTaskContext,
Expand Down Expand Up @@ -101,16 +97,6 @@ export function createTaskOutputStream<
});
}

export type AgentStreamChatResponse<Options extends object> = {
response: ChatResponseChunk<Options>;
sources: ToolOutput[];
};

export type AgentChatResponse<Options extends object> = {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

AgentChatResponse is better than Response but most people are using normal chat engines I assume, so we better improve the existing Response class (see above)

response: ChatResponse<Options>;
sources: ToolOutput[];
};

export type AgentRunnerParams<
AI extends LLM,
Store extends object = {},
Expand Down Expand Up @@ -210,11 +196,7 @@ export abstract class AgentRunner<
>
? AdditionalMessageOptions
: never,
> implements
ChatEngine<
AgentChatResponse<AdditionalMessageOptions>,
ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>>
>
> implements ChatEngine
{
readonly #llm: AI;
readonly #tools:
Expand Down Expand Up @@ -320,47 +302,34 @@ export abstract class AgentRunner<
});
}

async chat(
params: ChatEngineParamsNonStreaming,
): Promise<AgentChatResponse<AdditionalMessageOptions>>;
async chat(params: ChatEngineParamsNonStreaming): Promise<Response>;
async chat(
params: ChatEngineParamsStreaming,
): Promise<ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>>>;
): Promise<ReadableStream<Response>>;
@wrapEventCaller
async chat(
params: ChatEngineParamsNonStreaming | ChatEngineParamsStreaming,
): Promise<
| AgentChatResponse<AdditionalMessageOptions>
| ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>>
> {
): Promise<Response | ReadableStream<Response>> {
const task = this.createTask(params.message, !!params.stream);
for await (const stepOutput of task) {
// update chat history for each round
this.#chatHistory = [...stepOutput.taskStep.context.store.messages];
if (stepOutput.isLast) {
const { output, taskStep } = stepOutput;
const { output } = stepOutput;
if (isAsyncIterable(output)) {
return output.pipeThrough<
AgentStreamChatResponse<AdditionalMessageOptions>
>(
return output.pipeThrough<Response>(
new TransformStream({
transform(chunk, controller) {
controller.enqueue({
response: chunk,
get sources() {
return [...taskStep.context.store.toolOutputs];
},
});
controller.enqueue(new Response(chunk.delta));
},
}),
);
} else {
return {
response: output,
get sources() {
return [...taskStep.context.store.toolOutputs];
},
} satisfies AgentChatResponse<AdditionalMessageOptions>;
return new Response(
extractText(output.message.content),
undefined,
output,
);
}
}
}
Expand Down
Loading