Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
hwchase17 authored Feb 26, 2023
1 parent a312dea commit 433e4dd
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 6 deletions.
77 changes: 77 additions & 0 deletions docs/docs/modules/agents/agents_with_vectorstores.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Agents with Vectorstores

This notebook covers how to combine agents and vectorstores. The use case for this is that you’ve ingested your data into a vectorstore and want to interact with it in an agentic manner.

The reccomended method for doing so is to create a VectorDBQAChain and then use that as a tool in the overall agent. Let’s take a look at doing this below. You can do this with multiple different vectordbs, and use the agent as a way to route between them. There are two different ways of doing this - you can either let the agent use the vectorstores as normal tools, or you can set return_direct=True to really just use the agent as a router.

First, you want to import the relevant modules

```typescript
import { OpenAI } from "langchain";
import { initializeAgentExecutor } from "langchain/agents";
import { SerpAPI, Calculator, ChainTool } from "langchain/tools";
import { VectorDBQAChain } from "langchain/chains";
import { HNSWLib } from "langchain/vectorstores";
import { OpenAIEmbeddings } from "langchain/embeddings";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import * as fs from "fs";
```

Next, you want to create the vectorstore with your data, and then the QA chain to interact with that vectorstore.

```typescript
const model = new OpenAI({ temperature: 0 });
/* Load in the file we want to do question answering over */
const text = fs.readFileSync("state_of_the_union.txt", "utf8");
/* Split the text into chunks */
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000 });
const docs = textSplitter.createDocuments([text]);
/* Create the vectorstore */
const vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings());
/* Create the chain */
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
```

Now that you have that chain, you can create a tool to use that chain. Note that you should update the name and description to be specific to your QA chain.

```typescript
const qaTool = new ChainTool({
name: "state-of-union-qa",
description:
"State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.",
chain: chain,
});
```

Now we can go about constructing and using the tool as we would any other tool!

```typescript
const tools = [new SerpAPI(), new Calculator(), qaTool];

const executor = await initializeAgentExecutor(
tools,
model,
"zero-shot-react-description"
);
console.log("Loaded agent.");

const input = `What did biden say about ketanji brown jackson is the state of the union address?`;

console.log(`Executing with input "${input}"...`);

const result = await executor.call({ input });

console.log(`Got output ${result.output}`);
```

You can also set return_direct=True if you intend to use the agent as a router and just want to directly return the result of the VectorDBQaChain.

```typescript
const qaTool = new ChainTool({
name: "state-of-union-qa",
description:
"State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.",
chain: chain,
returnDirect: true,
});
```
43 changes: 43 additions & 0 deletions examples/src/agents/agents_vectorstore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { OpenAI } from "langchain";
import { initializeAgentExecutor } from "langchain/agents";
import { SerpAPI, Calculator, ChainTool } from "langchain/tools";
import { VectorDBQAChain } from "langchain/chains";
import { HNSWLib } from "langchain/vectorstores";
import { OpenAIEmbeddings } from "langchain/embeddings";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import * as fs from "fs";

export const run = async () => {
const model = new OpenAI({ temperature: 0 });
/* Load in the file we want to do question answering over */
const text = fs.readFileSync("state_of_the_union.txt", "utf8");
/* Split the text into chunks */
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000 });
const docs = textSplitter.createDocuments([text]);
/* Create the vectorstore */
const vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings());
/* Create the chain */
const chain = VectorDBQAChain.fromLLM(model, vectorStore);
const qaTool = new ChainTool({
name: "state-of-union-qa",
description:
"State of the Union QA - useful for when you need to ask questions about the most recent state of the union address.",
chain,
});
const tools = [new SerpAPI(), new Calculator(), qaTool];

const executor = await initializeAgentExecutor(
tools,
model,
"zero-shot-react-description"
);
console.log("Loaded agent.");

const input = `What did biden say about ketanji brown jackson is the state of the union address?`;

console.log(`Executing with input "${input}"...`);

const result = await executor.call({ input });

console.log(`Got output ${result.output}`);
};
10 changes: 8 additions & 2 deletions examples/src/agents/custom_tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ export const run = async () => {
name: "FOO",
description:
"call this to get the value of foo. input should be an empty string.",
func: () => "baz",
func: () =>
new Promise((resolve) => {
resolve("foo");
}),
}),
new DynamicTool({
name: "BAR",
description:
"call this to get the value of bar. input should be an empty string.",
func: () => "baz1",
func: () =>
new Promise((resolve) => {
resolve("baz1");
}),
}),
];

Expand Down
29 changes: 29 additions & 0 deletions langchain/agents/tools/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Tool } from "./base";
import { BaseChain } from "../../chains/base";

export class ChainTool extends Tool {
name: string;

description: string;

chain: BaseChain;

returnDirect: boolean;

constructor(fields: {
name: string;
description: string;
chain: BaseChain;
returnDirect?: boolean;
}) {
super();
this.name = fields.name;
this.description = fields.description;
this.chain = fields.chain;
this.returnDirect = fields.returnDirect ?? this.returnDirect;
}

async call(input: string): Promise<string> {
return this.chain.run(input);
}
}
4 changes: 2 additions & 2 deletions langchain/agents/tools/dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ export class DynamicTool extends Tool {

description: string;

func: (arg1: string) => string;
func: (arg1: string) => Promise<string>;

constructor(fields: {
name: string;
description: string;
func: (arg1: string) => string;
func: (arg1: string) => Promise<string>;
}) {
super();
this.name = fields.name;
Expand Down
1 change: 1 addition & 0 deletions langchain/agents/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { BingSerpAPI } from "./bingserpapi";
export { Tool } from "./base";
export { DynamicTool } from "./dynamic";
export { IFTTTWebhook } from "./IFTTTWebhook";
export { ChainTool } from "./chain";
export {
SqlDatabase,
QuerySqlTool,
Expand Down
12 changes: 10 additions & 2 deletions langchain/chains/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,23 @@ export abstract class BaseChain implements ChainInputs {
abstract get inputKeys(): string[];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async run(input: any): Promise<ChainValues> {
async run(input: any): Promise<string> {
const isKeylessInput = this.inputKeys.length === 1;
if (!isKeylessInput) {
throw new Error(
`Chain ${this._chainType()} expects multiple inputs, cannot use 'run' `
);
}
const values = { [this.inputKeys[0]]: input };
return this.call(values);
const returnValues = await this.call(values);
const keys = Object.keys(returnValues);
if (keys.length === 1) {
const finalReturn = returnValues[keys[0]];
return finalReturn;
}
throw new Error(
"return values have multiple keys, `run` only supported when one key currently"
);
}

/**
Expand Down

0 comments on commit 433e4dd

Please sign in to comment.