mirror of
https://github.com/hwchase17/langchain.git
synced 2026-02-09 18:51:07 +00:00
Compare commits
14 Commits
harrison/s
...
harrison/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93db5470bb | ||
|
|
711a2436bb | ||
|
|
1b81f3b125 | ||
|
|
5d887970f6 | ||
|
|
d70b5a2cbe | ||
|
|
d3a7429f61 | ||
|
|
22bd12a097 | ||
|
|
4a4dfbfbed | ||
|
|
15c19fcc60 | ||
|
|
315b0c09c6 | ||
|
|
e9baf9c134 | ||
|
|
e49fc51492 | ||
|
|
243211a5ae | ||
|
|
a19ad935b3 |
107
README.md
107
README.md
@@ -17,11 +17,6 @@ create a truly powerful app - the real power comes when you are able to
|
||||
combine them with other sources of computation or knowledge.
|
||||
|
||||
This library is aimed at assisting in the development of those types of applications.
|
||||
It aims to create:
|
||||
|
||||
1. a comprehensive collection of pieces you would ever want to combine
|
||||
2. a flexible interface for combining pieces into a single comprehensive "chain"
|
||||
3. a schema for easily saving and sharing those chains
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
@@ -31,78 +26,62 @@ Please see [here](https://langchain.readthedocs.io/en/latest/?) for full documen
|
||||
- Reference (full API docs)
|
||||
- Resources (high level explanation of core concepts)
|
||||
|
||||
## 🚀 What can I do with this
|
||||
## 🚀 What can this help with?
|
||||
|
||||
This project was largely inspired by a few projects seen on Twitter for which we thought it would make sense to have more explicit tooling. A lot of the initial functionality was done in an attempt to recreate those. Those are:
|
||||
There are three main areas (with a forth coming soon) that LangChain is designed to help with.
|
||||
These are, in increasing order of complexity:
|
||||
1. LLM and Prompts
|
||||
2. Chains
|
||||
3. Agents
|
||||
4. (Coming Soon) Memory
|
||||
|
||||
**[Self-ask-with-search](https://ofir.io/self-ask.pdf)**
|
||||
Let's go through these categories and for each one identify key concepts (to clarify terminology) as well as the problems in this area LangChain helps solve.
|
||||
|
||||
To recreate this paper, use the following code snippet or checkout the [example notebook](https://github.com/hwchase17/langchain/blob/master/docs/examples/demos/self_ask_with_search.ipynb).
|
||||
### LLMs and Prompts
|
||||
Calling out to an LLM once is pretty easy, with most of them being behind well documented APIs.
|
||||
However, there are still some challenges going from that to an application running in production that LangChain attempts to address.
|
||||
|
||||
```python
|
||||
from langchain import SelfAskWithSearchChain, OpenAI, SerpAPIChain
|
||||
**Key Concepts**
|
||||
- LLM: A large language model, in particular a text-to-text model.
|
||||
- Prompt: The input to a language model. Typically this is not simply a hardcoded string but rather a combination of a template, some examples, and user input.
|
||||
- Prompt Template: An object responsible for constructing the final prompt to pass to a LLM.
|
||||
|
||||
llm = OpenAI(temperature=0)
|
||||
search = SerpAPIChain()
|
||||
**Problems solved**
|
||||
- Switching costs: by exposing a standard interface for all the top LLM providers, LangChain makes it easy to switch from one provider to another, whether it be for production use cases or just for testing stuff out.
|
||||
- Prompt management: managing your prompts is easy when you only have one simple one, but can get tricky when you have a bunch or when they start to get more complex. LangChain provides a standard way for storing, constructing, and referencing prompts.
|
||||
- Prompt optimization: despite the underlying models getting better and better, there is still currently a need for carefully constructing prompts.
|
||||
|
||||
self_ask_with_search = SelfAskWithSearchChain(llm=llm, search_chain=search)
|
||||
### Chains
|
||||
Using an LLM in isolation is fine for some simple applications, but many more complex ones require chaining LLMs - either with eachother or with other experts.
|
||||
LangChain provides several parts to help with that.
|
||||
|
||||
self_ask_with_search.run("What is the hometown of the reigning men's U.S. Open champion?")
|
||||
```
|
||||
**Key Concepts**
|
||||
- Tools: APIs designed for assisting with a particular use case (search, databases, Python REPL, etc). Prompt templates, LLMs, and chains can also be considered tools.
|
||||
- Chains: A combination of multiple tools in a deterministic manner.
|
||||
|
||||
**[LLM Math](https://twitter.com/amasad/status/1568824744367259648?s=20&t=-7wxpXBJinPgDuyHLouP1w)**
|
||||
**Problems solved**
|
||||
- Standard interface for working with Chains
|
||||
- Easy way to construct chains of LLMs
|
||||
- Lots of integrations with other tools that you may want to use in conjunction with LLMs
|
||||
- End-to-end chains for common workflows (database question/answer, recursive summarization, etc)
|
||||
|
||||
To recreate this example, use the following code snippet or check out the [example notebook](https://github.com/hwchase17/langchain/blob/master/docs/examples/demos/llm_math.ipynb).
|
||||
### Agents
|
||||
Some applications will require not just a predetermined chain of calls to LLMs/other tools, but potentially an unknown chain that depends on the user input.
|
||||
In these types of chains, there is a “agent” which has access to a suite of tools.
|
||||
Depending on the user input, the agent can then decide which, if any, of these tools to call.
|
||||
|
||||
```python
|
||||
from langchain import OpenAI, LLMMathChain
|
||||
**Key Concepts**
|
||||
- Tools: same as above.
|
||||
- Agent: An LLM-powered class responsible for determining which tools to use and in what order.
|
||||
|
||||
llm = OpenAI(temperature=0)
|
||||
llm_math = LLMMathChain(llm=llm)
|
||||
|
||||
llm_math.run("How many of the integers between 0 and 99 inclusive are divisible by 8?")
|
||||
```
|
||||
**Problems solved**
|
||||
- Standard agent interfaces
|
||||
- A selection of powerful agents to choose from
|
||||
- Common chains that can be used as tools
|
||||
|
||||
**Generic Prompting**
|
||||
|
||||
You can also use this for simple prompting pipelines, as in the below example and this [example notebook](https://github.com/hwchase17/langchain/blob/master/docs/examples/demos/simple_prompts.ipynb).
|
||||
|
||||
```python
|
||||
from langchain import PromptTemplate, OpenAI, LLMChain
|
||||
|
||||
template = """Question: {question}
|
||||
|
||||
Answer: Let's think step by step."""
|
||||
prompt = PromptTemplate(template=template, input_variables=["question"])
|
||||
llm = OpenAI(temperature=0)
|
||||
llm_chain = LLMChain(prompt=prompt, llm=llm)
|
||||
|
||||
question = "What NFL team won the Super Bowl in the year Justin Bieber was born?"
|
||||
|
||||
llm_chain.predict(question=question)
|
||||
```
|
||||
|
||||
**Embed & Search Documents**
|
||||
|
||||
We support two vector databases to store and search embeddings -- FAISS and Elasticsearch. Here's a code snippet showing how to use FAISS to store embeddings and search for text similar to a query. Both database backends are featured in this [example notebook](https://github.com/hwchase17/langchain/blob/master/docs/examples/integrations/embeddings.ipynb).
|
||||
|
||||
```python
|
||||
from langchain.embeddings.openai import OpenAIEmbeddings
|
||||
from langchain.faiss import FAISS
|
||||
from langchain.text_splitter import CharacterTextSplitter
|
||||
|
||||
with open('state_of_the_union.txt') as f:
|
||||
state_of_the_union = f.read()
|
||||
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
|
||||
texts = text_splitter.split_text(state_of_the_union)
|
||||
|
||||
embeddings = OpenAIEmbeddings()
|
||||
|
||||
docsearch = FAISS.from_texts(texts, embeddings)
|
||||
|
||||
query = "What did the president say about Ketanji Brown Jackson"
|
||||
docs = docsearch.similarity_search(query)
|
||||
```
|
||||
### Memory
|
||||
Coming soon.
|
||||
|
||||
## 🤖 Developer Guide
|
||||
|
||||
|
||||
13
docs/examples/agents.rst
Normal file
13
docs/examples/agents.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
Agents
|
||||
======
|
||||
|
||||
The examples here are all end-to-end agents for specific applications.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
:caption: Agents
|
||||
|
||||
agents/mrkl.ipynb
|
||||
agents/react.ipynb
|
||||
agents/self_ask_with_search.ipynb
|
||||
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# MRKL\n",
|
||||
"\n",
|
||||
"This notebook showcases using the MRKL chain to route between tasks"
|
||||
"This notebook showcases using an agent to replicate the MRKL chain."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -26,13 +26,13 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import LLMMathChain, OpenAI, SerpAPIChain, MRKLChain, SQLDatabase, SQLDatabaseChain\n",
|
||||
"from langchain.chains.mrkl.base import ChainConfig"
|
||||
"from langchain import LLMMathChain, OpenAI, SerpAPIChain, SQLDatabase, SQLDatabaseChain\n",
|
||||
"from langchain.agents import initialize_agent, Tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 2,
|
||||
"id": "07e96d99",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -42,39 +42,38 @@
|
||||
"llm_math_chain = LLMMathChain(llm=llm, verbose=True)\n",
|
||||
"db = SQLDatabase.from_uri(\"sqlite:///../../../notebooks/Chinook.db\")\n",
|
||||
"db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True)\n",
|
||||
"chains = [\n",
|
||||
" ChainConfig(\n",
|
||||
" action_name = \"Search\",\n",
|
||||
" action=search.run,\n",
|
||||
" action_description=\"useful for when you need to answer questions about current events\"\n",
|
||||
"tools = [\n",
|
||||
" Tool(\n",
|
||||
" name = \"Search\",\n",
|
||||
" func=search.run,\n",
|
||||
" description=\"useful for when you need to answer questions about current events\"\n",
|
||||
" ),\n",
|
||||
" ChainConfig(\n",
|
||||
" action_name=\"Calculator\",\n",
|
||||
" action=llm_math_chain.run,\n",
|
||||
" action_description=\"useful for when you need to answer questions about math\"\n",
|
||||
" Tool(\n",
|
||||
" name=\"Calculator\",\n",
|
||||
" func=llm_math_chain.run,\n",
|
||||
" description=\"useful for when you need to answer questions about math\"\n",
|
||||
" ),\n",
|
||||
" \n",
|
||||
" ChainConfig(\n",
|
||||
" action_name=\"FooBar DB\",\n",
|
||||
" action=db_chain.run,\n",
|
||||
" action_description=\"useful for when you need to answer questions about FooBar. Input should be in the form of a question\"\n",
|
||||
" Tool(\n",
|
||||
" name=\"FooBar DB\",\n",
|
||||
" func=db_chain.run,\n",
|
||||
" description=\"useful for when you need to answer questions about FooBar. Input should be in the form of a question\"\n",
|
||||
" )\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 3,
|
||||
"id": "a069c4b6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"mrkl = MRKLChain.from_chains(llm, chains, verbose=True)"
|
||||
"mrkl = initialize_agent(tools, llm, agent=\"zero-shot-react-description\", verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 4,
|
||||
"id": "e603cd7d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -82,38 +81,34 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"What is the age of Olivia Wilde's boyfriend raised to the 0.23 power?\n",
|
||||
"Thought:\u001b[102m I need to find the age of Olivia Wilde's boyfriend\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Olivia Wilde's boyfriend\n",
|
||||
"Action: Search\n",
|
||||
"Action Input: \"Olivia Wilde's boyfriend\"\u001b[0m\n",
|
||||
"Observation: \u001b[104mOlivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.\u001b[0m\n",
|
||||
"Thought:\u001b[102m I need to find the age of Harry Styles\n",
|
||||
"Observation: \u001b[36;1m\u001b[1;3mOlivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Harry Styles\n",
|
||||
"Action: Search\n",
|
||||
"Action Input: \"Harry Styles age\"\u001b[0m\n",
|
||||
"Observation: \u001b[104m28 years\u001b[0m\n",
|
||||
"Thought:\u001b[102m I need to calculate 28 to the 0.23 power\n",
|
||||
"Observation: \u001b[36;1m\u001b[1;3m28 years\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 28 to the 0.23 power\n",
|
||||
"Action: Calculator\n",
|
||||
"Action Input: 28^0.23\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"28^0.23\u001b[102m\n",
|
||||
"28^0.23\u001b[32;1m\u001b[1;3m\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"print(28**0.23)\n",
|
||||
"```\n",
|
||||
"\u001b[0m\n",
|
||||
"Answer: \u001b[103m2.1520202182226886\n",
|
||||
"Answer: \u001b[33;1m\u001b[1;3m2.1520202182226886\n",
|
||||
"\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\n",
|
||||
"Observation: \u001b[103mAnswer: 2.1520202182226886\n",
|
||||
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.1520202182226886\n",
|
||||
"\u001b[0m\n",
|
||||
"Thought:\u001b[102m I now know the final answer\n",
|
||||
"Final Answer: 2.1520202182226886\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||
"Final Answer: 2.1520202182226886\u001b[0m"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -122,7 +117,7 @@
|
||||
"'2.1520202182226886'"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -133,7 +128,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": 5,
|
||||
"id": "a5c07010",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -141,41 +136,37 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"Who recently released an album called 'The Storm Before the Calm' and are they in the FooBar database? If so, what albums of theirs are in the FooBar database?\n",
|
||||
"Thought:\u001b[102m I need to find an album called 'The Storm Before the Calm'\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to find an album called 'The Storm Before the Calm'\n",
|
||||
"Action: Search\n",
|
||||
"Action Input: \"The Storm Before the Calm album\"\u001b[0m\n",
|
||||
"Observation: \u001b[104mThe Storm Before the Calm (stylized in all lowercase) is the tenth (and eighth international) studio album by Canadian-American singer-songwriter Alanis ...\u001b[0m\n",
|
||||
"Thought:\u001b[102m I need to check if Alanis is in the FooBar database\n",
|
||||
"Observation: \u001b[36;1m\u001b[1;3mThe Storm Before the Calm (stylized in all lowercase) is the tenth (and eighth international) studio album by Canadian-American singer-songwriter Alanis ...\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to check if Alanis is in the FooBar database\n",
|
||||
"Action: FooBar DB\n",
|
||||
"Action Input: \"Does Alanis Morissette exist in the FooBar database?\"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"Does Alanis Morissette exist in the FooBar database?\n",
|
||||
"SQLQuery:\u001b[102m SELECT * FROM Artist WHERE Name = 'Alanis Morissette'\u001b[0m\n",
|
||||
"SQLResult: \u001b[103m[(4, 'Alanis Morissette')]\u001b[0m\n",
|
||||
"Answer:\u001b[102m Yes\u001b[0m\n",
|
||||
"SQLQuery:\u001b[32;1m\u001b[1;3m SELECT * FROM Artist WHERE Name = 'Alanis Morissette'\u001b[0m\n",
|
||||
"SQLResult: \u001b[33;1m\u001b[1;3m[(4, 'Alanis Morissette')]\u001b[0m\n",
|
||||
"Answer:\u001b[32;1m\u001b[1;3m Yes\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\n",
|
||||
"Observation: \u001b[101m Yes\u001b[0m\n",
|
||||
"Thought:\u001b[102m I need to find out what albums of Alanis's are in the FooBar database\n",
|
||||
"Observation: \u001b[38;5;200m\u001b[1;3m Yes\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to find out what albums of Alanis's are in the FooBar database\n",
|
||||
"Action: FooBar DB\n",
|
||||
"Action Input: \"What albums by Alanis Morissette are in the FooBar database?\"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"What albums by Alanis Morissette are in the FooBar database?\n",
|
||||
"SQLQuery:\u001b[102m SELECT Title FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'Alanis Morissette')\u001b[0m\n",
|
||||
"SQLResult: \u001b[103m[('Jagged Little Pill',)]\u001b[0m\n",
|
||||
"Answer:\u001b[102m Jagged Little Pill\u001b[0m\n",
|
||||
"SQLQuery:\u001b[32;1m\u001b[1;3m SELECT Album.Title FROM Album JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name = 'Alanis Morissette'\u001b[0m\n",
|
||||
"SQLResult: \u001b[33;1m\u001b[1;3m[('Jagged Little Pill',)]\u001b[0m\n",
|
||||
"Answer:\u001b[32;1m\u001b[1;3m Jagged Little Pill\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\n",
|
||||
"Observation: \u001b[101m Jagged Little Pill\u001b[0m\n",
|
||||
"Thought:\u001b[102m I now know the final answer\n",
|
||||
"Final Answer: The album is by Alanis Morissette and the albums in the FooBar database by her are Jagged Little Pill\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
"Observation: \u001b[38;5;200m\u001b[1;3m Jagged Little Pill\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||
"Final Answer: The album is by Alanis Morissette and the albums in the FooBar database by her are Jagged Little Pill\u001b[0m"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -184,7 +175,7 @@
|
||||
"'The album is by Alanis Morissette and the albums in the FooBar database by her are Jagged Little Pill'"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
89
docs/examples/agents/react.ipynb
Normal file
89
docs/examples/agents/react.ipynb
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "82140df0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ReAct\n",
|
||||
"\n",
|
||||
"This notebook showcases using an agent to implement the ReAct logic."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "4e272b47",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import OpenAI, Wikipedia\n",
|
||||
"from langchain.agents import initialize_agent, Tool\n",
|
||||
"from langchain.agents.react.base import DocstoreExplorer\n",
|
||||
"docstore=DocstoreExplorer(Wikipedia())\n",
|
||||
"tools = [\n",
|
||||
" Tool(\n",
|
||||
" name=\"Search\",\n",
|
||||
" func=docstore.search\n",
|
||||
" ),\n",
|
||||
" Tool(\n",
|
||||
" name=\"Lookup\",\n",
|
||||
" func=docstore.lookup\n",
|
||||
" )\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"react = initialize_agent(tools, llm, agent=\"react-docstore\", verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8078c8f1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?\n",
|
||||
"Thought 1:"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"question = \"Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?\"\n",
|
||||
"react.run(question)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4ff64e81",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -20,23 +20,19 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"What is the hometown of the reigning men's U.S. Open champion?\n",
|
||||
"Are follow up questions needed here:\u001b[102m Yes.\n",
|
||||
"Are follow up questions needed here:\u001b[32;1m\u001b[1;3m Yes.\n",
|
||||
"Follow up: Who is the reigning men's U.S. Open champion?\u001b[0m\n",
|
||||
"Intermediate answer: \u001b[103mCarlos Alcaraz won the 2022 Men's single title while Poland's Iga Swiatek won the Women's single title defeating Tunisian's Ons Jabeur..\u001b[0m\u001b[102m\n",
|
||||
"Follow up: Where is Carlos Alcaraz from?\u001b[0m\n",
|
||||
"Intermediate answer: \u001b[103mEl Palmar, Murcia, Spain.\u001b[0m\u001b[102m\n",
|
||||
"So the final answer is: El Palmar, Murcia, Spain\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
"Intermediate answer: \u001b[36;1m\u001b[1;3mCarlos Alcaraz\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3mFollow up: Where is Carlos Alcaraz from?\u001b[0m\n",
|
||||
"Intermediate answer: \u001b[36;1m\u001b[1;3mEl Palmar, Spain\u001b[0m\n",
|
||||
"\u001b[32;1m\u001b[1;3mSo the final answer is: El Palmar, Spain\u001b[0m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'\\nSo the final answer is: El Palmar, Murcia, Spain'"
|
||||
"'El Palmar, Spain'"
|
||||
]
|
||||
},
|
||||
"execution_count": 1,
|
||||
@@ -45,12 +41,19 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain import SelfAskWithSearchChain, OpenAI, SerpAPIChain\n",
|
||||
"from langchain import OpenAI, SerpAPIChain\n",
|
||||
"from langchain.agents import initialize_agent, Tool\n",
|
||||
"\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"search = SerpAPIChain()\n",
|
||||
"tools = [\n",
|
||||
" Tool(\n",
|
||||
" name=\"Intermediate Answer\",\n",
|
||||
" func=search.run\n",
|
||||
" )\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"self_ask_with_search = SelfAskWithSearchChain(llm=llm, search_chain=search, verbose=True)\n",
|
||||
"self_ask_with_search = initialize_agent(tools, llm, agent=\"self-ask-with-search\", verbose=True)\n",
|
||||
"\n",
|
||||
"self_ask_with_search.run(\"What is the hometown of the reigning men's U.S. Open champion?\")"
|
||||
]
|
||||
15
docs/examples/chains.rst
Normal file
15
docs/examples/chains.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
Chains
|
||||
======
|
||||
|
||||
The examples here are all end-to-end chains for specific applications.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
:caption: Chains
|
||||
|
||||
chains/llm_chain.ipynb
|
||||
chains/llm_math.ipynb
|
||||
chains/map_reduce.ipynb
|
||||
chains/sqlite.ipynb
|
||||
chains/vector_db_qa.ipynb
|
||||
@@ -5,24 +5,39 @@
|
||||
"id": "d8a5c5d4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Simple Example\n",
|
||||
"# LLM Chain\n",
|
||||
"\n",
|
||||
"This notebook showcases a simple chain."
|
||||
"This notebook showcases a simple LLM chain."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 1,
|
||||
"id": "51a54c4d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"Prompt after formatting:\n",
|
||||
"\u001b[32;1m\u001b[1;3mQuestion: What NFL team won the Super Bowl in the year Justin Beiber was born?\n",
|
||||
"\n",
|
||||
"Answer: Let's think step by step.\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"' The year Justin Beiber was born was 1994. In 1994, the Dallas Cowboys won the Super Bowl.'"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -34,7 +49,7 @@
|
||||
"\n",
|
||||
"Answer: Let's think step by step.\"\"\"\n",
|
||||
"prompt = PromptTemplate(template=template, input_variables=[\"question\"])\n",
|
||||
"llm_chain = LLMChain(prompt=prompt, llm=OpenAI(temperature=0))\n",
|
||||
"llm_chain = LLMChain(prompt=prompt, llm=OpenAI(temperature=0), verbose=True)\n",
|
||||
"\n",
|
||||
"question = \"What NFL team won the Super Bowl in the year Justin Beiber was born?\"\n",
|
||||
"\n",
|
||||
@@ -66,7 +81,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.7"
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
@@ -1,10 +0,0 @@
|
||||
Demos
|
||||
=====
|
||||
|
||||
The examples here are all end-to-end chains of specific applications.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
demos/*
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "82140df0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ReAct\n",
|
||||
"\n",
|
||||
"This notebook showcases the implementation of the ReAct chain logic."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "4e272b47",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain import OpenAI, ReActChain, Wikipedia\n",
|
||||
"\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"react = ReActChain(llm=llm, docstore=Wikipedia(), verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "8078c8f1",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?\n",
|
||||
"Thought 1:\u001b[102m I need to search David Chanoff and find the U.S. Navy admiral he\n",
|
||||
"collaborated with.\n",
|
||||
"Action 1: Search[David Chanoff]\u001b[0m\n",
|
||||
"Observation 1: \u001b[103mDavid Chanoff is a noted author of non-fiction work. His work has typically involved collaborations with the principal protagonist of the work concerned. His collaborators have included; Augustus A. White, Joycelyn Elders, Đoàn Văn Toại, William J. Crowe, Ariel Sharon, Kenneth Good and Felix Zandman. He has also written about a wide range of subjects including literary history, education and foreign for The Washington Post, The New Republic and The New York Times Magazine. He has published more than twelve books.\u001b[0m\n",
|
||||
"Thought 2:\u001b[102m The U.S. Navy admiral David Chanoff collaborated with is William J. Crowe.\n",
|
||||
"Action 2: Search[William J. Crowe]\u001b[0m\n",
|
||||
"Observation 2: \u001b[103mWilliam James Crowe Jr. (January 2, 1925 – October 18, 2007) was a United States Navy admiral and diplomat who served as the 11th chairman of the Joint Chiefs of Staff under Presidents Ronald Reagan and George H. W. Bush, and as the ambassador to the United Kingdom and Chair of the Intelligence Oversight Board under President Bill Clinton.\u001b[0m\n",
|
||||
"Thought 3:\u001b[102m William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton. So the answer is Bill Clinton.\n",
|
||||
"Action 3: Finish[Bill Clinton]\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Bill Clinton'"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"question = \"Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?\"\n",
|
||||
"react.run(question)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0a6bd3b4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -94,7 +94,7 @@
|
||||
"]\n",
|
||||
"example_prompt = PromptTemplate(input_variables=[\"question\", \"answer\"], template=\"Question: {question}\\n{answer}\")\n",
|
||||
"\n",
|
||||
"prompt = FewShotPrompt(\n",
|
||||
"prompt = FewShotPromptTemplate(\n",
|
||||
" examples=examples, \n",
|
||||
" example_prompt=example_prompt, \n",
|
||||
" suffix=\"Question: {input}\", \n",
|
||||
@@ -104,7 +104,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"id": "897d4e08",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -128,7 +128,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"id": "7ab7379f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -298,7 +298,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.7"
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 3,
|
||||
"id": "a7bd36bc",
|
||||
"metadata": {
|
||||
"pycharm": {
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 4,
|
||||
"id": "e1efb008",
|
||||
"metadata": {
|
||||
"pycharm": {
|
||||
@@ -100,15 +100,26 @@
|
||||
"text/plain": [
|
||||
"['',\n",
|
||||
" '',\n",
|
||||
" 'Question: What is the highest mountain peak in North America?',\n",
|
||||
" '',\n",
|
||||
" 'Question: Is the film \"The Omen\" based on a book?',\n",
|
||||
" 'Thought 1: I need to search \"The Omen\" and find if it is based on a book.',\n",
|
||||
" 'Action 1: Search[\"The Omen\"]',\n",
|
||||
" 'Observation 1: The Omen is a 1976 American supernatural horror film directed by Richard Donner and written by David Seltzer.',\n",
|
||||
" 'Thought 2: The Omen is not based on a book.']"
|
||||
" 'Thought 1: I need to search North America and find the highest mountain peak.',\n",
|
||||
" '',\n",
|
||||
" 'Action 1: Search[North America]',\n",
|
||||
" '',\n",
|
||||
" 'Observation 1: North America is a continent entirely within the Northern Hemisphere and almost all within the Western Hemisphere.',\n",
|
||||
" '',\n",
|
||||
" 'Thought 2: I need to look up \"highest mountain peak\".',\n",
|
||||
" '',\n",
|
||||
" 'Action 2: Lookup[highest mountain peak]',\n",
|
||||
" '',\n",
|
||||
" 'Observation 2: (Result 1 / 1) Denali, formerly Mount McKinley, is the highest mountain peak in North America, with a summit elevation of 20,310 feet (6,190 m) above sea level.',\n",
|
||||
" '',\n",
|
||||
" 'Thought 3: Denali is the highest mountain peak in North America, with a summit elevation of 20,310 feet.',\n",
|
||||
" '',\n",
|
||||
" 'Action 3: Finish[20,310 feet]']"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -142,7 +153,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.7"
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"source": [
|
||||
"### PromptTemplate\n",
|
||||
"\n",
|
||||
"This is the most simple type of prompt - a string template that takes any number of input variables. The template should be formatted as a Python f-string, although we will support other formats (Jinja, Mako, etc) in the future. \n",
|
||||
"This is the most simple type of prompt template, consisting of a string template that takes any number of input variables. The template should be formatted as a Python f-string, although we will support other formats (Jinja, Mako, etc) in the future. \n",
|
||||
"\n",
|
||||
"If you just want to use a hardcoded prompt template, you should use this implementation.\n",
|
||||
"\n",
|
||||
@@ -191,7 +191,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.prompts.few_shot import FewShotPromptTemplate"
|
||||
"from langchain.prompts import FewShotPromptTemplate"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -276,7 +276,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.prompts.example_selector.length_based import LengthBasedExampleSelector"
|
||||
"from langchain.prompts.example_selector import LengthBasedExampleSelector"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -298,7 +298,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"execution_count": 10,
|
||||
"id": "207e55f7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -328,7 +328,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"execution_count": 11,
|
||||
"id": "d00b4385",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -365,7 +365,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"execution_count": 12,
|
||||
"id": "878bcde9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -406,7 +406,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.prompts.example_selector.semantic_similarity import SemanticSimilarityExampleSelector\n",
|
||||
"from langchain.prompts.example_selector import SemanticSimilarityExampleSelector\n",
|
||||
"from langchain.vectorstores import FAISS\n",
|
||||
"from langchain.embeddings import OpenAIEmbeddings"
|
||||
]
|
||||
|
||||
@@ -10,19 +10,23 @@
|
||||
"It is often preferrable to store prompts not as python code but as files. This can make it easy to share, store, and version prompts. This notebook covers how to do that in LangChain, walking through all the different types of prompts and the different serialization options.\n",
|
||||
"\n",
|
||||
"At a high level, the following design principles are applied to serialization:\n",
|
||||
"\n",
|
||||
"1. Both JSON and YAML are supported. We want to support serialization methods are human readable on disk, and YAML and JSON are two of the most popular methods for that. Note that this rule applies to prompts. For other assets, like Examples, different serialization methods may be supported.\n",
|
||||
"2. We support specifying everything in one file, or storing different components (templates, examples, etc) in different files and referencing them. For some cases, storing everything in file makes the most sense, but for others it is preferrable to split up some of the assets (long templates, large examples, reusable components). LangChain supports both."
|
||||
"\n",
|
||||
"2. We support specifying everything in one file, or storing different components (templates, examples, etc) in different files and referencing them. For some cases, storing everything in file makes the most sense, but for others it is preferrable to split up some of the assets (long templates, large examples, reusable components). LangChain supports both.\n",
|
||||
"\n",
|
||||
"There is also a single entry point to load prompts from disk, making it easy to load any type of prompt."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"execution_count": 1,
|
||||
"id": "2c8d7587",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# All prompts are loading through the `load_prompt` function.\n",
|
||||
"from langchain.prompts.loading import load_prompt"
|
||||
"# All prompts are loaded through the `load_prompt` function.\n",
|
||||
"from langchain.prompts import load_prompt"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -46,7 +50,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"execution_count": 2,
|
||||
"id": "2d6e5117",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -67,7 +71,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"execution_count": 3,
|
||||
"id": "4f4ca686",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -95,7 +99,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"execution_count": 4,
|
||||
"id": "510def23",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -125,7 +129,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"execution_count": 5,
|
||||
"id": "5547760d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -143,7 +147,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"execution_count": 6,
|
||||
"id": "9cb13ac5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -164,7 +168,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 36,
|
||||
"execution_count": 7,
|
||||
"id": "762cb4bf",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -202,7 +206,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"execution_count": 8,
|
||||
"id": "b21f5b95",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -232,7 +236,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": 9,
|
||||
"id": "e2bec0fc",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -263,7 +267,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 10,
|
||||
"id": "98c8f356",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -300,7 +304,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"execution_count": 11,
|
||||
"id": "9d996a86",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -328,7 +332,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 12,
|
||||
"id": "dd2c10bb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -365,7 +369,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": 13,
|
||||
"id": "6cd781ef",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -396,7 +400,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 14,
|
||||
"id": "533ab8a7",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -433,7 +437,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"execution_count": 15,
|
||||
"id": "0b6dd7b8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -454,7 +458,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"execution_count": 16,
|
||||
"id": "76a1065d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -479,7 +483,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 17,
|
||||
"id": "744d275d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
|
||||
28
docs/explanation/agents.md
Normal file
28
docs/explanation/agents.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Agents
|
||||
|
||||
Agents use an LLM to determine which tools to call and in what order.
|
||||
Here are the agents available in LangChain.
|
||||
|
||||
For a tutorial on how to load agents, see [here](/getting_started/agents.ipynb).
|
||||
|
||||
### `zero-shot-react-description`
|
||||
|
||||
This agent uses the ReAct framework to determine which tool to use
|
||||
based solely on the tool's description. Any number of tools can be provided.
|
||||
This agent requires that a description is provided for each tool.
|
||||
|
||||
### `react-docstore`
|
||||
|
||||
This agent uses the ReAct framework to interact with a docstore. Two tools must
|
||||
be provided: a `Search` tool and a `Lookup` tool (they must be named exactly as so).
|
||||
The `Search` tool should search for a document, while the `Lookup` tool should lookup
|
||||
a term in the most recently found document.
|
||||
This agent is equivalent to the
|
||||
original [ReAct paper](https://arxiv.org/pdf/2210.03629.pdf), specifically the Wikipedia example.
|
||||
|
||||
### `self-ask-with-search`
|
||||
|
||||
This agent utilizes a single tool that should be named `Intermediate Answer`.
|
||||
This tool should be able to lookup factual answers to questions. This agent
|
||||
is equivalent to the original [self ask with search paper](https://ofir.io/self-ask.pdf),
|
||||
where a Google search API was provided as the tool.
|
||||
@@ -8,7 +8,7 @@ PromptTemplates generically have a `format` method that takes in variables and r
|
||||
The most simple implementation of this is to have a template string with some variables in it, and then format it with the incoming variables.
|
||||
More complex iterations dynamically construct the template string from few shot examples, etc.
|
||||
|
||||
For a more detailed explanation of how LangChain approaches prompts and prompt templates, see [here](prompts.md).
|
||||
For a more detailed explanation of how LangChain approaches prompts and prompt templates, see [here](/examples/prompts/prompt_management).
|
||||
|
||||
## LLMs
|
||||
Wrappers around Large Language Models (in particular, the `generate` ability of large language models) are some of the core functionality of LangChain.
|
||||
@@ -25,3 +25,7 @@ These are datastores that store documents. They expose a method for passing in a
|
||||
## Chains
|
||||
These are pipelines that combine multiple of the above ideas.
|
||||
They vary greatly in complexity and are combination of generic, highly configurable pipelines and more narrow (but usually more complex) pipelines.
|
||||
|
||||
## Agents
|
||||
As opposed to a chain, whether the steps to be taken are known ahead of time, agents
|
||||
use an LLM to determine which tools to call and in what order.
|
||||
|
||||
@@ -29,7 +29,7 @@ This induces the to model to think about what action to take, then take it.
|
||||
|
||||
Resources:
|
||||
- [Paper](https://arxiv.org/pdf/2210.03629.pdf)
|
||||
- [LangChain Example](https://github.com/hwchase17/langchain/blob/master/examples/react.ipynb)
|
||||
- [LangChain Example](https://github.com/hwchase17/langchain/blob/master/docs/examples/agents/react.ipynb)
|
||||
|
||||
### Self-ask
|
||||
|
||||
@@ -38,7 +38,7 @@ In this method, the model explicitly asks itself follow-up questions, which are
|
||||
|
||||
Resources:
|
||||
- [Paper](https://ofir.io/self-ask.pdf)
|
||||
- [LangChain Example](https://github.com/hwchase17/langchain/blob/master/examples/self_ask_with_search.ipynb)
|
||||
- [LangChain Example](https://github.com/hwchase17/langchain/blob/master/docs/examples/agents/self_ask_with_search.ipynb)
|
||||
|
||||
### Prompt Chaining
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
# Prompts
|
||||
|
||||
Prompts and all the tooling around them are integral to working with language models, and therefor
|
||||
really important to get right, from both and interface and naming perspective. This is a "design doc"
|
||||
of sorts explaining how we think about prompts and the related concepts, and why the interfaces
|
||||
for working with are the way they are in LangChain.
|
||||
|
||||
For a more code-based walkthrough of all these concept, checkout our example [here](/examples/prompts/prompt_management)
|
||||
|
||||
## Prompt
|
||||
|
||||
### Concept
|
||||
A prompt is the final string that gets fed into the language model.
|
||||
|
||||
### LangChain Implementation
|
||||
In LangChain a prompt is represented as just a string.
|
||||
|
||||
## Input Variables
|
||||
|
||||
### Concept
|
||||
Input variables are parts of a prompt that are not known until runtime, eg could be user provided.
|
||||
|
||||
### LangChain Implementation
|
||||
In LangChain input variables are just represented as a dictionary of key-value pairs, with the key
|
||||
being the variable name and the value being the variable value.
|
||||
|
||||
## Examples
|
||||
|
||||
### Concept
|
||||
Examples are basically datapoints that can be used to teach the model what to do. These can be included
|
||||
in prompts to better instruct the model on what to do.
|
||||
|
||||
### LangChain Implementation
|
||||
In LangChain examples are represented as a dictionary of key-value pairs, with the key being the feature
|
||||
(or label) name, and the value being the feature (or label) value.
|
||||
|
||||
## Example Selector
|
||||
|
||||
### Concept
|
||||
If you have a large number of examples, you may need to select which ones to include in the prompt. The
|
||||
Example Selector is the class responsible for doing so.
|
||||
|
||||
### LangChain Implementation
|
||||
|
||||
#### BaseExampleSelector
|
||||
In LangChain there is a BaseExampleSelector that exposes the following interface
|
||||
|
||||
```python
|
||||
class BaseExampleSelector:
|
||||
|
||||
def select_examples(self, input_variables: dict):
|
||||
```
|
||||
|
||||
Notice that it does not take in examples at runtime when it's selecting them - those are assumed to have been provided ahead of time.
|
||||
|
||||
#### LengthExampleSelector
|
||||
The LengthExampleSelector selects examples based on the length of the input variables.
|
||||
This is useful when you are worried about constructing a prompt that will go over the length
|
||||
of the context window. For longer inputs, it will select fewer examples to include, while for
|
||||
shorter inputs it will select more.
|
||||
|
||||
#### SemanticSimilarityExampleSelector
|
||||
The SemanticSimilarityExampleSelector selects examples based on which examples are most similar
|
||||
to the inputs. It does this by finding the examples with the embeddings that have the greatest
|
||||
cosine similarity with the inputs.
|
||||
|
||||
|
||||
## Prompt Template
|
||||
|
||||
### Concept
|
||||
The prompts that get fed into the language model are nearly always not hardcoded, but rather a combination
|
||||
of parts, including Examples and Input Variables. A prompt template is responsible
|
||||
for taking those parts and constructing a prompt.
|
||||
|
||||
### LangChain Implementation
|
||||
|
||||
#### BasePromptTemplate
|
||||
In LangChain there is a BasePromptTemplate that exposes the following interface
|
||||
|
||||
```python
|
||||
class BasePromptTemplate:
|
||||
|
||||
@property
|
||||
def input_variables(self) -> List[str]:
|
||||
|
||||
def format(self, **kwargs) -> str:
|
||||
```
|
||||
The input variables property is used to provide introspection of the PromptTemplate and know
|
||||
what inputs it expects. The format method takes in input variables and returns the prompt.
|
||||
|
||||
#### PromptTemplate
|
||||
The PromptTemplate implementation is the most simple form of a prompt template. It consists of three parts:
|
||||
- input variables: which variables this prompt template expects
|
||||
- template: the template into which these variables will be formatted
|
||||
- template format: the format of the template (eg mustache, python f-strings, etc)
|
||||
|
||||
For example, if I was making an application that took a user inputted concept and asked a language model
|
||||
to make a joke about that concept, I might use this specification for the PromptTemplate
|
||||
- input variables = `["thing"]`
|
||||
- template = `"Tell me a joke about {thing}"`
|
||||
- template format = `"f-string"`
|
||||
|
||||
#### FewShotPromptTemplate
|
||||
A FewShotPromptTemplate is a Prompt Template that includes some examples. It consists of:
|
||||
- examples OR example selector: a list of examples to use, or an Example Selector to select which examples to use
|
||||
- example prompt template: a Prompt Template responsible for taking an individual example (a dictionary) and turning it into a string to be used in the prompt.
|
||||
- prefix: the template put in the prompt before listing any examples
|
||||
- suffix: the template put in the prompt after listing any examples
|
||||
- example separator: a string separator which is used to join the prefix, the examples, and the suffix together
|
||||
|
||||
|
||||
For example, if I wanted to turn the above example into a few shot prompt, this is what it would
|
||||
look like:
|
||||
|
||||
First I would collect some examples, like
|
||||
```python
|
||||
examples = [
|
||||
{"concept": "chicken", "joke": "Why did the chicken cross the road?"},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
I would then make sure to define a prompt template for how each example should be formatted
|
||||
when inserted into the prompt:
|
||||
```python
|
||||
prompt_template = PromptTemplate(
|
||||
input_variables=["concept", "joke"],
|
||||
template="Tell me a joke about {concept}\n{joke}"
|
||||
)
|
||||
```
|
||||
|
||||
Then, I would define the components as:
|
||||
- examples: The above examples
|
||||
- example_prompt: The above example prompt
|
||||
- prefix = `"You are a comedian telling jokes on demand."`
|
||||
- suffix = `"Tell me a joke about {concept}"`
|
||||
- input variables = `["concept"]`
|
||||
- template format = `"f-string"`
|
||||
197
docs/getting_started/agents.ipynb
Normal file
197
docs/getting_started/agents.ipynb
Normal file
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5436020b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Agents\n",
|
||||
"\n",
|
||||
"Agents use an LLM to determine which tools to call and in what order.\n",
|
||||
"\n",
|
||||
"Some applications will require not just a predetermined chain of calls to LLMs/other tools, but potentially an unknown chain that depends on the user input. In these types of chains, there is a \"agent\" (backed by an LLM) which has access to a suite of tools. Depending on the user input, the agent can then decide which, if any, of these tools to call.\n",
|
||||
"\n",
|
||||
"When used correctly agents can be extremely powerful. The purpose of this notebook is to show you how to easily use agents through the simplest, highest level API. If you want more low level control over various components, check out the documentation for custom agents (coming soon)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c6226b9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Concepts\n",
|
||||
"\n",
|
||||
"In order to load agents, you should understand the following concepts:\n",
|
||||
"\n",
|
||||
"- Tool: A function that performs a specific duty. This can be things like: Google Search, Database lookup, Python REPL, other chains. The interface for a tool is currently a function that is expected to have a string as an input, with a string as an output.\n",
|
||||
"- LLM: The language model powering the agent.\n",
|
||||
"- Agent: The agent to use. This should be a string. Because this notebook focuses on the simplest, highest level API, this only covers using the standard supported agents. If you want to implement a custom agent, see the documentation for custom agents (coming soon).\n",
|
||||
"\n",
|
||||
"**For a list of supported agents and their specifications, see [here](../explanation/agents.md)**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "05d4b21e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Tools\n",
|
||||
"When constructing your own agent, you will need to provide it with a list of Tools that it can use. A Tool is defined as below.\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class Tool(NamedTuple):\n",
|
||||
" \"\"\"Interface for tools.\"\"\"\n",
|
||||
"\n",
|
||||
" name: str\n",
|
||||
" func: Callable[[str], str]\n",
|
||||
" description: Optional[str] = None\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"The two required components of a Tool are the name and then the tool itself. A tool description is optional, as it is needed for some agents but not all."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2558a02d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Loading an agent\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "36ed392e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import things that are needed generically\n",
|
||||
"from langchain.agents import initialize_agent, Tool\n",
|
||||
"from langchain.llms import OpenAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "56ff7670",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Load the tool configs that are needed.\n",
|
||||
"from langchain import LLMMathChain, SerpAPIChain\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"search = SerpAPIChain()\n",
|
||||
"llm_math_chain = LLMMathChain(llm=llm, verbose=True)\n",
|
||||
"tools = [\n",
|
||||
" Tool(\n",
|
||||
" name = \"Search\",\n",
|
||||
" func=search.run,\n",
|
||||
" description=\"useful for when you need to answer questions about current events\"\n",
|
||||
" ),\n",
|
||||
" Tool(\n",
|
||||
" name=\"Calculator\",\n",
|
||||
" func=llm_math_chain.run,\n",
|
||||
" description=\"useful for when you need to answer questions about math\"\n",
|
||||
" )\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "5b93047d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Construct the agent. We will use the default agent type here.\n",
|
||||
"# See documentation for a full list of options.\n",
|
||||
"llm = OpenAI(temperature=0)\n",
|
||||
"agent = initialize_agent(tools, llm, agent=\"zero-shot-react-description\", verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "6f96a891",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"What is the age of Olivia Wilde's boyfriend raised to the 0.23 power?\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Olivia Wilde's boyfriend\n",
|
||||
"Action: Search\n",
|
||||
"Action Input: \"Olivia Wilde's boyfriend\"\u001b[0m\n",
|
||||
"Observation: \u001b[36;1m\u001b[1;3mOlivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Harry Styles\n",
|
||||
"Action: Search\n",
|
||||
"Action Input: \"Harry Styles age\"\u001b[0m\n",
|
||||
"Observation: \u001b[36;1m\u001b[1;3m28 years\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 28 to the 0.23 power\n",
|
||||
"Action: Calculator\n",
|
||||
"Action Input: 28^0.23\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"28^0.23\u001b[32;1m\u001b[1;3m\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"print(28**0.23)\n",
|
||||
"```\n",
|
||||
"\u001b[0m\n",
|
||||
"Answer: \u001b[33;1m\u001b[1;3m2.1520202182226886\n",
|
||||
"\u001b[0m\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n",
|
||||
"\n",
|
||||
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.1520202182226886\n",
|
||||
"\u001b[0m\n",
|
||||
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
|
||||
"Final Answer: 2.1520202182226886\u001b[0m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'2.1520202182226886'"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"agent.run(\"What is the age of Olivia Wilde's boyfriend raised to the 0.23 power?\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2f0852ff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -21,5 +21,5 @@ We can now call it on some input!
|
||||
|
||||
```python
|
||||
text = "What would be a good company name a company that makes colorful socks?"
|
||||
llm(text)
|
||||
print(llm(text))
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Using Chains
|
||||
# LLM Chains
|
||||
|
||||
Calling an LLM is a great first step, but it's just the beginning.
|
||||
Normally when you use an LLM in an application, you are not sending user input directly to the LLM.
|
||||
@@ -33,7 +33,5 @@ Now we can run that can only specifying the product!
|
||||
chain.run("colorful socks")
|
||||
```
|
||||
|
||||
There we go! There's the first chain.
|
||||
|
||||
That is it for the Getting Started example.
|
||||
As a next step, we would suggest checking out the more complex chains in the [Demos section](/examples/demos)
|
||||
There we go! There's the first chain - an LLM Chain.
|
||||
This is one of the simpler types of chains, but understanding how it works will set you up well for working with more complex chains.
|
||||
265
docs/getting_started/sequential_chains.ipynb
Normal file
265
docs/getting_started/sequential_chains.ipynb
Normal file
@@ -0,0 +1,265 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4f73605d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Sequential Chains"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3b235f7a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The next step after calling a language model is make a series of calls to a language model. This is particularly useful when you want to take the output from one call and use it as the input to another.\n",
|
||||
"\n",
|
||||
"In this notebook we will walk through some examples for how to do this, using sequential chains. Sequential chains are defined as a series of chains, called in deterministic order. There are two types of sequential chains:\n",
|
||||
"\n",
|
||||
"- `SimpleSequentialChain`: The simplest form of sequential chains, where each step has a singular input/output, and the output of one step is the input to the next.\n",
|
||||
"- `SequentialChain`: A more general form of sequential chains, allowing for multiple inputs/outputs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5162794e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## SimpleSequentialChain\n",
|
||||
"\n",
|
||||
"In this series of chains, each individual chain has a single input and a single output, and the output of one step is used as input to the next.\n",
|
||||
"\n",
|
||||
"Let's walk through a toy example of doing this, where the first chain takes in the title of an imaginary play and then generates a synopsis for that title, and the second chain takes in the synopsis of that play and generates an imaginary review for that play."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "3f2f9b8c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.llms import OpenAI\n",
|
||||
"from langchain.chains import LLMChain\n",
|
||||
"from langchain.prompts import PromptTemplate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "b8237d1a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is an LLMChain to write a synopsis given a title of a play.\n",
|
||||
"llm = OpenAI(temperature=.7)\n",
|
||||
"template = \"\"\"You are a playwright. Given the title of play, it is your job to write a synopsis for that title.\n",
|
||||
"\n",
|
||||
"Title: {title}\n",
|
||||
"Playwright: This is a synopsis for the above play:\"\"\"\n",
|
||||
"prompt_template = PromptTemplate(input_variables=[\"title\"], template=template)\n",
|
||||
"synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "4a391730",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is an LLMChain to write a review of a play given a synopsis.\n",
|
||||
"llm = OpenAI(temperature=.7)\n",
|
||||
"template = \"\"\"You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.\n",
|
||||
"\n",
|
||||
"Play Synopsis:\n",
|
||||
"{synopsis}\n",
|
||||
"Review from a New York Times play critic of the above play:\"\"\"\n",
|
||||
"prompt_template = PromptTemplate(input_variables=[\"synopsis\"], template=template)\n",
|
||||
"review_chain = LLMChain(llm=llm, prompt=prompt_template)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "9368bd63",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is the overall chain where we run these two chains in sequence.\n",
|
||||
"from langchain.chains import SimpleSequentialChain\n",
|
||||
"overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "d39e15f5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"\u001b[36;1m\u001b[1;3m\n",
|
||||
"\n",
|
||||
"A young couple, John and Mary, are enjoying a day at the beach. As the sun sets, they share a romantic moment. However, their happiness is short-lived, as a tragic accident claims John's life. Mary is left devastated by the loss of her husband.\u001b[0m\n",
|
||||
"\u001b[33;1m\u001b[1;3m\n",
|
||||
"\n",
|
||||
"\"A young couple's happiness is cut short by tragedy in this moving play. Mary is left devastated by the loss of her husband, John, in a freak accident. The play captures the pain and grief of loss, as well as the strength of love. A must-see for fans of theater.\"\u001b[0m\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"review = overall_chain.run(\"Tragedy at sunset on the beach\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "c6649a01",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\"A young couple's happiness is cut short by tragedy in this moving play. Mary is left devastated by the loss of her husband, John, in a freak accident. The play captures the pain and grief of loss, as well as the strength of love. A must-see for fans of theater.\"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(review)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c3f1549a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Sequential Chain\n",
|
||||
"Of course, not all sequential chains will be as simple as passing a single string as an argument and getting a single string as output for all steps in the chain. In this next example, we will experiment with more complex chains that involve multiple inputs, and where there also multiple final outputs. \n",
|
||||
"\n",
|
||||
"Of particular importance is how we name the input/output variable names. In the above example we didn't have to think about that because we were just passing the output of one chain directly as input to the next, but here we do have worry about that because we have multiple inputs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "02016a51",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is an LLMChain to write a synopsis given a title of a play and the era it is set in.\n",
|
||||
"llm = OpenAI(temperature=.7)\n",
|
||||
"template = \"\"\"You are a playwright. Given the title of play and the era it is set in, it is your job to write a synopsis for that title.\n",
|
||||
"\n",
|
||||
"Title: {title}\n",
|
||||
"Era: {era}\n",
|
||||
"Playwright: This is a synopsis for the above play:\"\"\"\n",
|
||||
"prompt_template = PromptTemplate(input_variables=[\"title\", 'era'], template=template)\n",
|
||||
"synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=\"synopsis\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "8bd38cc2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is an LLMChain to write a review of a play given a synopsis.\n",
|
||||
"llm = OpenAI(temperature=.7)\n",
|
||||
"template = \"\"\"You are a play critic from the New York Times. Given the synopsis of play, it is your job to write a review for that play.\n",
|
||||
"\n",
|
||||
"Play Synopsis:\n",
|
||||
"{synopsis}\n",
|
||||
"Review from a New York Times play critic of the above play:\"\"\"\n",
|
||||
"prompt_template = PromptTemplate(input_variables=[\"synopsis\"], template=template)\n",
|
||||
"review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=\"review\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "524523af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is the overall chain where we run these two chains in sequence.\n",
|
||||
"from langchain.chains import SequentialChain\n",
|
||||
"overall_chain = SequentialChain(\n",
|
||||
" chains=[synopsis_chain, review_chain],\n",
|
||||
" input_variables=[\"era\", \"title\"],\n",
|
||||
" # Here we return multiple variables\n",
|
||||
" output_variables=[\"synopsis\", \"review\"],\n",
|
||||
" verbose=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "3fd3a7be",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Entering new chain...\u001b[0m\n",
|
||||
"\u001b[1mChain 0\u001b[0m:\n",
|
||||
"{'synopsis': \"\\n\\nThe play is set in Victorian England and follows the tragic story of a young woman who drowns while swimming at sunset on the beach. Her body is found the next morning by a fisherman who raises the alarm. The young woman's family and friends are devastated by her death and the play ends with their mourning her loss.\"}\n",
|
||||
"\n",
|
||||
"\u001b[1mChain 1\u001b[0m:\n",
|
||||
"{'review': '\\n\\n\"The play is a tragedy, pure and simple. It is the story of a young woman\\'s death, told through the eyes of those who loved her. It is a sad, beautiful play that will stay with you long after you\\'ve seen it. The acting is superb, and the writing is exquisite. If you are looking for a play that will touch your heart and make you think, this is it.\"'}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\u001b[1m> Finished chain.\u001b[0m\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"review = overall_chain({\"title\":\"Tragedy at sunset on the beach\", \"era\": \"Victorian England\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6be70d27",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -8,12 +8,75 @@ create a truly powerful app - the real power comes when you are able to
|
||||
combine them with other sources of computation or knowledge.
|
||||
|
||||
This library is aimed at assisting in the development of those types of applications.
|
||||
It aims to create:
|
||||
|
||||
1. a comprehensive collection of pieces you would ever want to combine
|
||||
2. a flexible interface for combining pieces into a single comprehensive "chain"
|
||||
3. a schema for easily saving and sharing those chains
|
||||
There are three main areas (with a forth coming soon) that LangChain is designed to help with.
|
||||
These are, in increasing order of complexity:
|
||||
|
||||
1. LLM and Prompts
|
||||
2. Chains
|
||||
3. Agents
|
||||
4. (Coming Soon) Memory
|
||||
|
||||
Let's go through these categories and for each one identify key concepts (to clarify terminology) as well as the problems in this area LangChain helps solve.
|
||||
|
||||
**🦜 LLMs and Prompts**
|
||||
|
||||
Calling out to an LLM once is pretty easy, with most of them being behind well documented APIs.
|
||||
However, there are still some challenges going from that to an application running in production that LangChain attempts to address.
|
||||
|
||||
*Key Concepts*
|
||||
|
||||
- LLM: A large language model, in particular a text-to-text model.
|
||||
- Prompt: The input to a language model. Typically this is not simply a hardcoded string but rather a combination of a template, some examples, and user input.
|
||||
- Prompt Template: An object responsible for constructing the final prompt to pass to a LLM.
|
||||
|
||||
*Problems solved*
|
||||
|
||||
- Switching costs: by exposing a standard interface for all the top LLM providers, LangChain makes it easy to switch from one provider to another, whether it be for production use cases or just for testing stuff out.
|
||||
- Prompt management: managing your prompts is easy when you only have one simple one, but can get tricky when you have a bunch or when they start to get more complex. LangChain provides a standard way for storing, constructing, and referencing prompts.
|
||||
- Prompt optimization: despite the underlying models getting better and better, there is still currently a need for carefully constructing prompts.
|
||||
|
||||
**🔗️ Chains**
|
||||
|
||||
Using an LLM in isolation is fine for some simple applications, but many more complex ones require chaining LLMs - either with eachother or with other experts.
|
||||
LangChain provides several parts to help with that.
|
||||
|
||||
*Key Concepts*
|
||||
|
||||
- Tools: APIs designed for assisting with a particular use case (search, databases, Python REPL, etc). Prompt templates, LLMs, and chains can also be considered tools.
|
||||
- Chains: A combination of multiple tools in a deterministic manner.
|
||||
|
||||
*Problems solved*
|
||||
|
||||
- Standard interface for working with Chains
|
||||
- Easy way to construct chains of LLMs
|
||||
- Lots of integrations with other tools that you may want to use in conjunction with LLMs
|
||||
- End-to-end chains for common workflows (database question/answer, recursive summarization, etc)
|
||||
|
||||
**🤖 Agents**
|
||||
|
||||
Some applications will require not just a predetermined chain of calls to LLMs/other tools, but potentially an unknown chain that depends on the user input.
|
||||
In these types of chains, there is a “agent” which has access to a suite of tools.
|
||||
Depending on the user input, the agent can then decide which, if any, of these tools to call.
|
||||
|
||||
*Key Concepts*
|
||||
|
||||
- Tools: same as above.
|
||||
- Agent: An LLM-powered class responsible for determining which tools to use and in what order.
|
||||
|
||||
|
||||
*Problems solved*
|
||||
|
||||
- Standard agent interfaces
|
||||
- A selection of powerful agents to choose from
|
||||
- Common chains that can be used as tools
|
||||
|
||||
**🧠 Memory**
|
||||
|
||||
Coming soon.
|
||||
|
||||
Documentation Structure
|
||||
=======================
|
||||
The documentation is structured into the following sections:
|
||||
|
||||
|
||||
@@ -25,7 +88,9 @@ The documentation is structured into the following sections:
|
||||
getting_started/installation.md
|
||||
getting_started/environment.md
|
||||
getting_started/llm.md
|
||||
getting_started/chains.md
|
||||
getting_started/llm_chain.md
|
||||
getting_started/sequential_chains.md
|
||||
getting_started/agents.ipynb
|
||||
|
||||
Goes over a simple walk through and tutorial for getting started setting up a simple chain that generates a company name based on what the company makes.
|
||||
Covers installation, environment set up, calling LLMs, and using prompts.
|
||||
@@ -37,9 +102,10 @@ Start here if you haven't used LangChain before.
|
||||
:caption: How-To Examples
|
||||
:name: examples
|
||||
|
||||
examples/demos.rst
|
||||
examples/integrations.rst
|
||||
examples/prompts.rst
|
||||
examples/integrations.rst
|
||||
examples/chains.rst
|
||||
examples/agents.rst
|
||||
examples/model_laboratory.ipynb
|
||||
|
||||
More elaborate examples and walk-throughs of particular
|
||||
@@ -62,6 +128,7 @@ common tasks or cool demos.
|
||||
modules/text_splitter
|
||||
modules/vectorstore
|
||||
modules/chains
|
||||
modules/agents
|
||||
|
||||
|
||||
Full API documentation. This is the place to look if you want to
|
||||
@@ -74,7 +141,7 @@ see detailed information about the various classes, methods, and APIs.
|
||||
:name: resources
|
||||
|
||||
explanation/core_concepts.md
|
||||
explanation/prompts.md
|
||||
explanation/agents.md
|
||||
explanation/glossary.md
|
||||
Discord <https://discord.gg/6adMQxSpJS>
|
||||
|
||||
|
||||
7
docs/modules/agents.rst
Normal file
7
docs/modules/agents.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
:mod:`langchain.agents`
|
||||
===============================
|
||||
|
||||
.. automodule:: langchain.agents
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.0.16
|
||||
0.0.20
|
||||
|
||||
@@ -5,20 +5,23 @@ from pathlib import Path
|
||||
with open(Path(__file__).absolute().parents[0] / "VERSION") as _f:
|
||||
__version__ = _f.read().strip()
|
||||
|
||||
from langchain.agents import MRKLChain, ReActChain, SelfAskWithSearchChain
|
||||
from langchain.chains import (
|
||||
LLMChain,
|
||||
LLMMathChain,
|
||||
MRKLChain,
|
||||
PythonChain,
|
||||
ReActChain,
|
||||
SelfAskWithSearchChain,
|
||||
SerpAPIChain,
|
||||
SQLDatabaseChain,
|
||||
VectorDBQA,
|
||||
)
|
||||
from langchain.docstore import Wikipedia
|
||||
from langchain.llms import Cohere, HuggingFaceHub, OpenAI
|
||||
from langchain.prompts import BasePromptTemplate, PromptTemplate
|
||||
from langchain.prompts import (
|
||||
BasePromptTemplate,
|
||||
FewShotPromptTemplate,
|
||||
Prompt,
|
||||
PromptTemplate,
|
||||
)
|
||||
from langchain.sql_database import SQLDatabase
|
||||
from langchain.vectorstores import FAISS, ElasticVectorSearch
|
||||
|
||||
@@ -31,7 +34,8 @@ __all__ = [
|
||||
"Cohere",
|
||||
"OpenAI",
|
||||
"BasePromptTemplate",
|
||||
"DynamicPrompt",
|
||||
"Prompt",
|
||||
"FewShotPromptTemplate",
|
||||
"PromptTemplate",
|
||||
"ReActChain",
|
||||
"Wikipedia",
|
||||
|
||||
16
langchain/agents/__init__.py
Normal file
16
langchain/agents/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""Routing chains."""
|
||||
from langchain.agents.agent import Agent
|
||||
from langchain.agents.loading import initialize_agent
|
||||
from langchain.agents.mrkl.base import MRKLChain
|
||||
from langchain.agents.react.base import ReActChain
|
||||
from langchain.agents.self_ask_with_search.base import SelfAskWithSearchChain
|
||||
from langchain.agents.tools import Tool
|
||||
|
||||
__all__ = [
|
||||
"MRKLChain",
|
||||
"SelfAskWithSearchChain",
|
||||
"ReActChain",
|
||||
"Agent",
|
||||
"Tool",
|
||||
"initialize_agent",
|
||||
]
|
||||
162
langchain/agents/agent.py
Normal file
162
langchain/agents/agent.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""Chain that takes in an input and produces an action and action input."""
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, ClassVar, Dict, List, NamedTuple, Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.input import ChainedInput, get_color_mapping
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts.base import BasePromptTemplate
|
||||
|
||||
|
||||
class Action(NamedTuple):
|
||||
"""Action to take."""
|
||||
|
||||
tool: str
|
||||
tool_input: str
|
||||
log: str
|
||||
|
||||
|
||||
class Agent(Chain, BaseModel, ABC):
|
||||
"""Agent that uses an LLM."""
|
||||
|
||||
prompt: ClassVar[BasePromptTemplate]
|
||||
llm_chain: LLMChain
|
||||
tools: List[Tool]
|
||||
retry_on_failed_tool: bool = False
|
||||
input_key: str = "input" #: :meta private:
|
||||
output_key: str = "output" #: :meta private:
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Return the singular input key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.input_key]
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Return the singular output key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.output_key]
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def observation_prefix(self) -> str:
|
||||
"""Prefix to append the observation with."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def llm_prefix(self) -> str:
|
||||
"""Prefix to append the LLM call with."""
|
||||
|
||||
@property
|
||||
def finish_tool_name(self) -> str:
|
||||
"""Name of the tool to use to finish the chain."""
|
||||
return "Final Answer"
|
||||
|
||||
@property
|
||||
def starter_string(self) -> str:
|
||||
"""Put this string after user input but before first LLM call."""
|
||||
return "\n"
|
||||
|
||||
@abstractmethod
|
||||
def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]:
|
||||
"""Extract tool and tool input from llm output."""
|
||||
|
||||
def _fix_text(self, text: str) -> str:
|
||||
"""Fix the text."""
|
||||
raise ValueError("fix_text not implemented for this agent.")
|
||||
|
||||
@property
|
||||
def _stop(self) -> List[str]:
|
||||
return [f"\n{self.observation_prefix}"]
|
||||
|
||||
@classmethod
|
||||
def _validate_tools(cls, tools: List[Tool]) -> None:
|
||||
"""Validate that appropriate tools are passed in."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _get_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
|
||||
return cls.prompt
|
||||
|
||||
@classmethod
|
||||
def from_llm_and_tools(cls, llm: LLM, tools: List[Tool], **kwargs: Any) -> "Agent":
|
||||
"""Construct an agent from an LLM and tools."""
|
||||
cls._validate_tools(tools)
|
||||
llm_chain = LLMChain(llm=llm, prompt=cls._get_prompt(tools))
|
||||
return cls(llm_chain=llm_chain, tools=tools, **kwargs)
|
||||
|
||||
def get_action(self, text: str) -> Action:
|
||||
"""Given input, decided what to do.
|
||||
|
||||
Args:
|
||||
text: input string
|
||||
|
||||
Returns:
|
||||
Action specifying what tool to use.
|
||||
"""
|
||||
input_key = self.llm_chain.input_keys[0]
|
||||
inputs = {input_key: text, "stop": self._stop}
|
||||
full_output = self.llm_chain.predict(**inputs)
|
||||
parsed_output = self._extract_tool_and_input(full_output)
|
||||
while parsed_output is None:
|
||||
full_output = self._fix_text(full_output)
|
||||
inputs = {input_key: text + full_output, "stop": self._stop}
|
||||
output = self.llm_chain.predict(**inputs)
|
||||
full_output += output
|
||||
parsed_output = self._extract_tool_and_input(full_output)
|
||||
tool, tool_input = parsed_output
|
||||
return Action(tool, tool_input, full_output)
|
||||
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
"""Run text through and get agent response."""
|
||||
text = inputs[self.input_key]
|
||||
# Construct a mapping of tool name to tool for easy lookup
|
||||
name_to_tool_map = {tool.name: tool.func for tool in self.tools}
|
||||
# Construct the initial string to pass into the LLM. This is made up
|
||||
# of the user input, the special starter string, and then the LLM prefix.
|
||||
# The starter string is a special string that may be used by a LLM to
|
||||
# immediately follow the user input. The LLM prefix is a string that
|
||||
# prompts the LLM to take an action.
|
||||
starter_string = text + self.starter_string + self.llm_prefix
|
||||
# We use the ChainedInput class to iteratively add to the input over time.
|
||||
chained_input = ChainedInput(starter_string, verbose=self.verbose)
|
||||
# We construct a mapping from each tool to a color, used for logging.
|
||||
color_mapping = get_color_mapping(
|
||||
[tool.name for tool in self.tools], excluded_colors=["green"]
|
||||
)
|
||||
# We now enter the agent loop (until it returns something).
|
||||
while True:
|
||||
# Call the LLM to see what to do.
|
||||
output = self.get_action(chained_input.input)
|
||||
# Add the log to the Chained Input.
|
||||
chained_input.add(output.log, color="green")
|
||||
# If the tool chosen is the finishing tool, then we end and return.
|
||||
if output.tool == self.finish_tool_name:
|
||||
return {self.output_key: output.tool_input}
|
||||
if output.tool not in name_to_tool_map:
|
||||
if self.retry_on_failed_tool:
|
||||
observation = f"Tool {output.tool} not found."
|
||||
color = None
|
||||
else:
|
||||
raise KeyError(observation)
|
||||
else:
|
||||
# Otherwise we lookup the tool
|
||||
chain = name_to_tool_map[output.tool]
|
||||
# We then call the tool on the tool input to get an observation
|
||||
observation = chain(output.tool_input)
|
||||
color = color_mapping[output.tool]
|
||||
# We then log the observation
|
||||
chained_input.add(f"\n{self.observation_prefix}")
|
||||
chained_input.add(observation, color=color)
|
||||
# We then add the LLM prefix into the prompt to get the LLM to start
|
||||
# thinking, and start the loop all over.
|
||||
chained_input.add(f"\n{self.llm_prefix}")
|
||||
42
langchain/agents/loading.py
Normal file
42
langchain/agents/loading.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Load agent."""
|
||||
from typing import Any, List
|
||||
|
||||
from langchain.agents.agent import Agent
|
||||
from langchain.agents.mrkl.base import ZeroShotAgent
|
||||
from langchain.agents.react.base import ReActDocstoreAgent
|
||||
from langchain.agents.self_ask_with_search.base import SelfAskWithSearchAgent
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.llms.base import LLM
|
||||
|
||||
AGENT_TO_CLASS = {
|
||||
"zero-shot-react-description": ZeroShotAgent,
|
||||
"react-docstore": ReActDocstoreAgent,
|
||||
"self-ask-with-search": SelfAskWithSearchAgent,
|
||||
}
|
||||
|
||||
|
||||
def initialize_agent(
|
||||
tools: List[Tool],
|
||||
llm: LLM,
|
||||
agent: str = "zero-shot-react-description",
|
||||
**kwargs: Any,
|
||||
) -> Agent:
|
||||
"""Load agent given tools and LLM.
|
||||
|
||||
Args:
|
||||
tools: List of tools this agent has access to.
|
||||
llm: Language model to use as the agent.
|
||||
agent: The agent to use. Valid options are:
|
||||
`zero-shot-react-description`, `react-docstore`, `self-ask-with-search`.
|
||||
**kwargs: Additional key word arguments to pass to the agent.
|
||||
|
||||
Returns:
|
||||
An agent.
|
||||
"""
|
||||
if agent not in AGENT_TO_CLASS:
|
||||
raise ValueError(
|
||||
f"Got unknown agent type: {agent}. "
|
||||
f"Valid types are: {AGENT_TO_CLASS.keys()}."
|
||||
)
|
||||
agent_cls = AGENT_TO_CLASS[agent]
|
||||
return agent_cls.from_llm_and_tools(llm, tools, **kwargs)
|
||||
137
langchain/agents/mrkl/base.py
Normal file
137
langchain/agents/mrkl/base.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""Attempt to implement MRKL systems as described in arxiv.org/pdf/2205.00445.pdf."""
|
||||
from typing import Any, Callable, List, NamedTuple, Optional, Tuple
|
||||
|
||||
from langchain.agents.agent import Agent
|
||||
from langchain.agents.mrkl.prompt import BASE_TEMPLATE
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain.prompts.base import BasePromptTemplate
|
||||
|
||||
FINAL_ANSWER_ACTION = "Final Answer: "
|
||||
|
||||
|
||||
class ChainConfig(NamedTuple):
|
||||
"""Configuration for chain to use in MRKL system.
|
||||
|
||||
Args:
|
||||
action_name: Name of the action.
|
||||
action: Action function to call.
|
||||
action_description: Description of the action.
|
||||
"""
|
||||
|
||||
action_name: str
|
||||
action: Callable
|
||||
action_description: str
|
||||
|
||||
|
||||
def get_action_and_input(llm_output: str) -> Tuple[str, str]:
|
||||
"""Parse out the action and input from the LLM output."""
|
||||
ps = [p for p in llm_output.split("\n") if p]
|
||||
if ps[-1].startswith("Final Answer"):
|
||||
directive = ps[-1][len(FINAL_ANSWER_ACTION) :]
|
||||
return "Final Answer", directive
|
||||
if not ps[-1].startswith("Action Input: "):
|
||||
raise ValueError(
|
||||
"The last line does not have an action input, "
|
||||
"something has gone terribly wrong."
|
||||
)
|
||||
if not ps[-2].startswith("Action: "):
|
||||
raise ValueError(
|
||||
"The second to last line does not have an action, "
|
||||
"something has gone terribly wrong."
|
||||
)
|
||||
action = ps[-2][len("Action: ") :]
|
||||
action_input = ps[-1][len("Action Input: ") :]
|
||||
return action, action_input.strip(" ").strip('"')
|
||||
|
||||
|
||||
class ZeroShotAgent(Agent):
|
||||
"""Agent for the MRKL chain."""
|
||||
|
||||
@property
|
||||
def observation_prefix(self) -> str:
|
||||
"""Prefix to append the observation with."""
|
||||
return "Observation: "
|
||||
|
||||
@property
|
||||
def llm_prefix(self) -> str:
|
||||
"""Prefix to append the llm call with."""
|
||||
return "Thought:"
|
||||
|
||||
@classmethod
|
||||
def _get_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
|
||||
tool_strings = "\n".join([f"{tool.name}: {tool.description}" for tool in tools])
|
||||
tool_names = ", ".join([tool.name for tool in tools])
|
||||
template = BASE_TEMPLATE.format(tools=tool_strings, tool_names=tool_names)
|
||||
return PromptTemplate(template=template, input_variables=["input"])
|
||||
|
||||
@classmethod
|
||||
def _validate_tools(cls, tools: List[Tool]) -> None:
|
||||
for tool in tools:
|
||||
if tool.description is None:
|
||||
raise ValueError(
|
||||
f"Got a tool {tool.name} without a description. For this agent, "
|
||||
f"a description must always be provided."
|
||||
)
|
||||
|
||||
def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]:
|
||||
return get_action_and_input(text)
|
||||
|
||||
|
||||
class MRKLChain(ZeroShotAgent):
|
||||
"""Chain that implements the MRKL system.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import OpenAI, MRKLChain
|
||||
from langchain.chains.mrkl.base import ChainConfig
|
||||
llm = OpenAI(temperature=0)
|
||||
prompt = PromptTemplate(...)
|
||||
chains = [...]
|
||||
mrkl = MRKLChain.from_chains(llm=llm, prompt=prompt)
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_chains(cls, llm: LLM, chains: List[ChainConfig], **kwargs: Any) -> "Agent":
|
||||
"""User friendly way to initialize the MRKL chain.
|
||||
|
||||
This is intended to be an easy way to get up and running with the
|
||||
MRKL chain.
|
||||
|
||||
Args:
|
||||
llm: The LLM to use as the agent LLM.
|
||||
chains: The chains the MRKL system has access to.
|
||||
**kwargs: parameters to be passed to initialization.
|
||||
|
||||
Returns:
|
||||
An initialized MRKL chain.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import LLMMathChain, OpenAI, SerpAPIChain, MRKLChain
|
||||
from langchain.chains.mrkl.base import ChainConfig
|
||||
llm = OpenAI(temperature=0)
|
||||
search = SerpAPIChain()
|
||||
llm_math_chain = LLMMathChain(llm=llm)
|
||||
chains = [
|
||||
ChainConfig(
|
||||
action_name = "Search",
|
||||
action=search.search,
|
||||
action_description="useful for searching"
|
||||
),
|
||||
ChainConfig(
|
||||
action_name="Calculator",
|
||||
action=llm_math_chain.run,
|
||||
action_description="useful for doing math"
|
||||
)
|
||||
]
|
||||
mrkl = MRKLChain.from_chains(llm, chains)
|
||||
"""
|
||||
tools = [
|
||||
Tool(name=c.action_name, func=c.action, description=c.action_description)
|
||||
for c in chains
|
||||
]
|
||||
return cls.from_llm_and_tools(llm, tools, **kwargs)
|
||||
@@ -14,6 +14,9 @@ Observation: the result of the action
|
||||
Thought: I now know the final answer
|
||||
Final Answer: the final answer to the original input question
|
||||
|
||||
Do NOT take the same action with the same action input.
|
||||
Only take actions with tools that exist.
|
||||
|
||||
Begin!
|
||||
|
||||
Question: {{input}}"""
|
||||
114
langchain/agents/react/base.py
Normal file
114
langchain/agents/react/base.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""Chain that implements the ReAct paper from https://arxiv.org/pdf/2210.03629.pdf."""
|
||||
import re
|
||||
from typing import Any, ClassVar, List, Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langchain.agents.agent import Agent
|
||||
from langchain.agents.react.prompt import PROMPT
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.docstore.base import Docstore
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts.base import BasePromptTemplate
|
||||
|
||||
|
||||
class ReActDocstoreAgent(Agent, BaseModel):
|
||||
"""Agent for the ReAct chin."""
|
||||
|
||||
prompt: ClassVar[BasePromptTemplate] = PROMPT
|
||||
|
||||
i: int = 1
|
||||
|
||||
@classmethod
|
||||
def _validate_tools(cls, tools: List[Tool]) -> None:
|
||||
if len(tools) != 2:
|
||||
raise ValueError(f"Exactly two tools must be specified, but got {tools}")
|
||||
tool_names = {tool.name for tool in tools}
|
||||
if tool_names != {"Lookup", "Search"}:
|
||||
raise ValueError(
|
||||
f"Tool names should be Lookup and Search, got {tool_names}"
|
||||
)
|
||||
|
||||
def _fix_text(self, text: str) -> str:
|
||||
return text + f"\nAction {self.i}:"
|
||||
|
||||
def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]:
|
||||
action_prefix = f"Action {self.i}: "
|
||||
if not text.split("\n")[-1].startswith(action_prefix):
|
||||
return None
|
||||
self.i += 1
|
||||
action_block = text.split("\n")[-1]
|
||||
|
||||
action_str = action_block[len(action_prefix) :]
|
||||
# Parse out the action and the directive.
|
||||
re_matches = re.search(r"(.*?)\[(.*?)\]", action_str)
|
||||
if re_matches is None:
|
||||
raise ValueError(f"Could not parse action directive: {action_str}")
|
||||
return re_matches.group(1), re_matches.group(2)
|
||||
|
||||
@property
|
||||
def finish_tool_name(self) -> str:
|
||||
"""Name of the tool of when to finish the chain."""
|
||||
return "Finish"
|
||||
|
||||
@property
|
||||
def observation_prefix(self) -> str:
|
||||
"""Prefix to append the observation with."""
|
||||
return f"Observation {self.i - 1}: "
|
||||
|
||||
@property
|
||||
def _stop(self) -> List[str]:
|
||||
return [f"\nObservation {self.i}: "]
|
||||
|
||||
@property
|
||||
def llm_prefix(self) -> str:
|
||||
"""Prefix to append the LLM call with."""
|
||||
return f"Thought {self.i}:"
|
||||
|
||||
|
||||
class DocstoreExplorer:
|
||||
"""Class to assist with exploration of a document store."""
|
||||
|
||||
def __init__(self, docstore: Docstore):
|
||||
"""Initialize with a docstore, and set initial document to None."""
|
||||
self.docstore = docstore
|
||||
self.document: Optional[Document] = None
|
||||
|
||||
def search(self, term: str) -> str:
|
||||
"""Search for a term in the docstore, and if found save."""
|
||||
result = self.docstore.search(term)
|
||||
if isinstance(result, Document):
|
||||
self.document = result
|
||||
return self.document.summary
|
||||
else:
|
||||
self.document = None
|
||||
return result
|
||||
|
||||
def lookup(self, term: str) -> str:
|
||||
"""Lookup a term in document (if saved)."""
|
||||
if self.document is None:
|
||||
raise ValueError("Cannot lookup without a successful search first")
|
||||
return self.document.lookup(term)
|
||||
|
||||
|
||||
class ReActChain(ReActDocstoreAgent):
|
||||
"""Chain that implements the ReAct paper.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import ReActChain, OpenAI
|
||||
react = ReAct(llm=OpenAI())
|
||||
"""
|
||||
|
||||
def __init__(self, llm: LLM, docstore: Docstore, **kwargs: Any):
|
||||
"""Initialize with the LLM and a docstore."""
|
||||
docstore_explorer = DocstoreExplorer(docstore)
|
||||
tools = [
|
||||
Tool(name="Search", func=docstore_explorer.search),
|
||||
Tool(name="Lookup", func=docstore_explorer.lookup),
|
||||
]
|
||||
llm_chain = LLMChain(llm=llm, prompt=PROMPT)
|
||||
super().__init__(llm_chain=llm_chain, tools=tools, **kwargs)
|
||||
84
langchain/agents/self_ask_with_search/base.py
Normal file
84
langchain/agents/self_ask_with_search/base.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Chain that does self ask with search."""
|
||||
from typing import Any, ClassVar, List, Tuple
|
||||
|
||||
from langchain.agents.agent import Agent
|
||||
from langchain.agents.self_ask_with_search.prompt import PROMPT
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.serpapi import SerpAPIChain
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts.base import BasePromptTemplate
|
||||
|
||||
|
||||
class SelfAskWithSearchAgent(Agent):
|
||||
"""Agent for the self-ask-with-search paper."""
|
||||
|
||||
prompt: ClassVar[BasePromptTemplate] = PROMPT
|
||||
|
||||
@classmethod
|
||||
def _validate_tools(cls, tools: List[Tool]) -> None:
|
||||
if len(tools) != 1:
|
||||
raise ValueError(f"Exactly one tool must be specified, but got {tools}")
|
||||
tool_names = {tool.name for tool in tools}
|
||||
if tool_names != {"Intermediate Answer"}:
|
||||
raise ValueError(
|
||||
f"Tool name should be Intermediate Answer, got {tool_names}"
|
||||
)
|
||||
|
||||
def _extract_tool_and_input(self, text: str) -> Tuple[str, str]:
|
||||
followup = "Follow up:"
|
||||
if "\n" not in text:
|
||||
last_line = text
|
||||
else:
|
||||
last_line = text.split("\n")[-1]
|
||||
|
||||
if followup not in last_line:
|
||||
finish_string = "So the final answer is: "
|
||||
if finish_string not in last_line:
|
||||
raise ValueError("We should probably never get here")
|
||||
return "Final Answer", text[len(finish_string) :]
|
||||
|
||||
if ":" not in last_line:
|
||||
after_colon = last_line
|
||||
else:
|
||||
after_colon = text.split(":")[-1]
|
||||
|
||||
if " " == after_colon[0]:
|
||||
after_colon = after_colon[1:]
|
||||
if "?" != after_colon[-1]:
|
||||
print("we probably should never get here..." + text)
|
||||
|
||||
return "Intermediate Answer", after_colon
|
||||
|
||||
@property
|
||||
def observation_prefix(self) -> str:
|
||||
"""Prefix to append the observation with."""
|
||||
return "Intermediate answer: "
|
||||
|
||||
@property
|
||||
def llm_prefix(self) -> str:
|
||||
"""Prefix to append the LLM call with."""
|
||||
return ""
|
||||
|
||||
@property
|
||||
def starter_string(self) -> str:
|
||||
"""Put this string after user input but before first LLM call."""
|
||||
return "\nAre follow up questions needed here:"
|
||||
|
||||
|
||||
class SelfAskWithSearchChain(SelfAskWithSearchAgent):
|
||||
"""Chain that does self ask with search.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import SelfAskWithSearchChain, OpenAI, SerpAPIChain
|
||||
search_chain = SerpAPIChain()
|
||||
self_ask = SelfAskWithSearchChain(llm=OpenAI(), search_chain=search_chain)
|
||||
"""
|
||||
|
||||
def __init__(self, llm: LLM, search_chain: SerpAPIChain, **kwargs: Any):
|
||||
"""Initialize with just an LLM and a search chain."""
|
||||
search_tool = Tool(name="Intermediate Answer", func=search_chain.run)
|
||||
llm_chain = LLMChain(llm=llm, prompt=PROMPT)
|
||||
super().__init__(llm_chain=llm_chain, tools=[search_tool], **kwargs)
|
||||
10
langchain/agents/tools.py
Normal file
10
langchain/agents/tools.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Interface for tools."""
|
||||
from typing import Callable, NamedTuple, Optional
|
||||
|
||||
|
||||
class Tool(NamedTuple):
|
||||
"""Interface for tools."""
|
||||
|
||||
name: str
|
||||
func: Callable[[str], str]
|
||||
description: Optional[str] = None
|
||||
@@ -1,10 +1,8 @@
|
||||
"""Chains are easily reusable components which can be linked together."""
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.llm_math.base import LLMMathChain
|
||||
from langchain.chains.mrkl.base import MRKLChain
|
||||
from langchain.chains.python import PythonChain
|
||||
from langchain.chains.react.base import ReActChain
|
||||
from langchain.chains.self_ask_with_search.base import SelfAskWithSearchChain
|
||||
from langchain.chains.sequential import SequentialChain, SimpleSequentialChain
|
||||
from langchain.chains.serpapi import SerpAPIChain
|
||||
from langchain.chains.sql_database.base import SQLDatabaseChain
|
||||
from langchain.chains.vector_db_qa.base import VectorDBQA
|
||||
@@ -13,10 +11,9 @@ __all__ = [
|
||||
"LLMChain",
|
||||
"LLMMathChain",
|
||||
"PythonChain",
|
||||
"SelfAskWithSearchChain",
|
||||
"SerpAPIChain",
|
||||
"ReActChain",
|
||||
"SQLDatabaseChain",
|
||||
"MRKLChain",
|
||||
"VectorDBQA",
|
||||
"SequentialChain",
|
||||
"SimpleSequentialChain",
|
||||
]
|
||||
|
||||
@@ -38,8 +38,19 @@ class Chain(BaseModel, ABC):
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
"""Run the logic of this chain and return the output."""
|
||||
|
||||
def __call__(self, inputs: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Run the logic of this chain and add to output."""
|
||||
def __call__(
|
||||
self, inputs: Dict[str, Any], return_only_outputs: bool = False
|
||||
) -> Dict[str, str]:
|
||||
"""Run the logic of this chain and add to output if desired.
|
||||
|
||||
Args:
|
||||
inputs: Dictionary of inputs.
|
||||
return_only_outputs: boolean for whether to return only outputs in the
|
||||
response. If True, only new keys generated by this chain will be
|
||||
returned. If False, both input keys and new keys generated by this
|
||||
chain will be returned. Defaults to False.
|
||||
|
||||
"""
|
||||
self._validate_inputs(inputs)
|
||||
if self.verbose:
|
||||
print("\n\n\033[1m> Entering new chain...\033[0m")
|
||||
@@ -47,7 +58,10 @@ class Chain(BaseModel, ABC):
|
||||
if self.verbose:
|
||||
print("\n\033[1m> Finished chain.\033[0m")
|
||||
self._validate_outputs(outputs)
|
||||
return {**inputs, **outputs}
|
||||
if return_only_outputs:
|
||||
return outputs
|
||||
else:
|
||||
return {**inputs, **outputs}
|
||||
|
||||
def apply(self, input_list: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
||||
"""Call the chain on all inputs in the list."""
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any, Dict, List
|
||||
from pydantic import BaseModel, Extra
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.input import print_text
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts.base import BasePromptTemplate
|
||||
|
||||
@@ -53,7 +54,9 @@ class LLMChain(Chain, BaseModel):
|
||||
def _call(self, inputs: Dict[str, Any]) -> Dict[str, str]:
|
||||
selected_inputs = {k: inputs[k] for k in self.prompt.input_variables}
|
||||
prompt = self.prompt.format(**selected_inputs)
|
||||
|
||||
if self.verbose:
|
||||
print("Prompt after formatting:")
|
||||
print_text(prompt, color="green", end="\n")
|
||||
kwargs = {}
|
||||
if "stop" in inputs:
|
||||
kwargs["stop"] = inputs["stop"]
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
"""Attempt to implement MRKL systems as described in arxiv.org/pdf/2205.00445.pdf."""
|
||||
from typing import Any, Callable, Dict, List, NamedTuple, Tuple
|
||||
|
||||
from pydantic import BaseModel, Extra
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.mrkl.prompt import BASE_TEMPLATE
|
||||
from langchain.input import ChainedInput, get_color_mapping
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts import BasePromptTemplate, PromptTemplate
|
||||
|
||||
FINAL_ANSWER_ACTION = "Final Answer: "
|
||||
|
||||
|
||||
class ChainConfig(NamedTuple):
|
||||
"""Configuration for chain to use in MRKL system.
|
||||
|
||||
Args:
|
||||
action_name: Name of the action.
|
||||
action: Action function to call.
|
||||
action_description: Description of the action.
|
||||
"""
|
||||
|
||||
action_name: str
|
||||
action: Callable
|
||||
action_description: str
|
||||
|
||||
|
||||
def get_action_and_input(llm_output: str) -> Tuple[str, str]:
|
||||
"""Parse out the action and input from the LLM output."""
|
||||
ps = [p for p in llm_output.split("\n") if p]
|
||||
if ps[-1].startswith(FINAL_ANSWER_ACTION):
|
||||
directive = ps[-1][len(FINAL_ANSWER_ACTION) :]
|
||||
return FINAL_ANSWER_ACTION, directive
|
||||
if not ps[-1].startswith("Action Input: "):
|
||||
raise ValueError(
|
||||
"The last line does not have an action input, "
|
||||
"something has gone terribly wrong."
|
||||
)
|
||||
if not ps[-2].startswith("Action: "):
|
||||
raise ValueError(
|
||||
"The second to last line does not have an action, "
|
||||
"something has gone terribly wrong."
|
||||
)
|
||||
action = ps[-2][len("Action: ") :]
|
||||
action_input = ps[-1][len("Action Input: ") :]
|
||||
return action, action_input.strip(" ").strip('"')
|
||||
|
||||
|
||||
class MRKLChain(Chain, BaseModel):
|
||||
"""Chain that implements the MRKL system.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import OpenAI, Prompt, MRKLChain
|
||||
from langchain.chains.mrkl.base import ChainConfig
|
||||
llm = OpenAI(temperature=0)
|
||||
prompt = PromptTemplate(...)
|
||||
action_to_chain_map = {...}
|
||||
mrkl = MRKLChain(
|
||||
llm=llm,
|
||||
prompt=prompt,
|
||||
action_to_chain_map=action_to_chain_map
|
||||
)
|
||||
"""
|
||||
|
||||
llm: LLM
|
||||
"""LLM wrapper to use as router."""
|
||||
prompt: BasePromptTemplate
|
||||
"""Prompt to use as router."""
|
||||
action_to_chain_map: Dict[str, Callable]
|
||||
"""Mapping from action name to chain to execute."""
|
||||
input_key: str = "question" #: :meta private:
|
||||
output_key: str = "answer" #: :meta private:
|
||||
|
||||
@classmethod
|
||||
def from_chains(
|
||||
cls, llm: LLM, chains: List[ChainConfig], **kwargs: Any
|
||||
) -> "MRKLChain":
|
||||
"""User friendly way to initialize the MRKL chain.
|
||||
|
||||
This is intended to be an easy way to get up and running with the
|
||||
MRKL chain.
|
||||
|
||||
Args:
|
||||
llm: The LLM to use as the router LLM.
|
||||
chains: The chains the MRKL system has access to.
|
||||
**kwargs: parameters to be passed to initialization.
|
||||
|
||||
Returns:
|
||||
An initialized MRKL chain.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import LLMMathChain, OpenAI, SerpAPIChain, MRKLChain
|
||||
from langchain.chains.mrkl.base import ChainConfig
|
||||
llm = OpenAI(temperature=0)
|
||||
search = SerpAPIChain()
|
||||
llm_math_chain = LLMMathChain(llm=llm)
|
||||
chains = [
|
||||
ChainConfig(
|
||||
action_name = "Search",
|
||||
action=search.search,
|
||||
action_description="useful for searching"
|
||||
),
|
||||
ChainConfig(
|
||||
action_name="Calculator",
|
||||
action=llm_math_chain.run,
|
||||
action_description="useful for doing math"
|
||||
)
|
||||
]
|
||||
mrkl = MRKLChain.from_chains(llm, chains)
|
||||
"""
|
||||
tools = "\n".join(
|
||||
[f"{chain.action_name}: {chain.action_description}" for chain in chains]
|
||||
)
|
||||
tool_names = ", ".join([chain.action_name for chain in chains])
|
||||
template = BASE_TEMPLATE.format(tools=tools, tool_names=tool_names)
|
||||
prompt = PromptTemplate(template=template, input_variables=["input"])
|
||||
action_to_chain_map = {chain.action_name: chain.action for chain in chains}
|
||||
return cls(
|
||||
llm=llm, prompt=prompt, action_to_chain_map=action_to_chain_map, **kwargs
|
||||
)
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Expect input key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.input_key]
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Expect output key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.output_key]
|
||||
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
llm_chain = LLMChain(llm=self.llm, prompt=self.prompt)
|
||||
chained_input = ChainedInput(
|
||||
f"{inputs[self.input_key]}\nThought:", verbose=self.verbose
|
||||
)
|
||||
color_mapping = get_color_mapping(
|
||||
list(self.action_to_chain_map.keys()), excluded_colors=["green"]
|
||||
)
|
||||
while True:
|
||||
thought = llm_chain.predict(
|
||||
input=chained_input.input, stop=["\nObservation"]
|
||||
)
|
||||
chained_input.add(thought, color="green")
|
||||
action, action_input = get_action_and_input(thought)
|
||||
if action == FINAL_ANSWER_ACTION:
|
||||
return {self.output_key: action_input}
|
||||
chain = self.action_to_chain_map[action]
|
||||
ca = chain(action_input)
|
||||
chained_input.add("\nObservation: ")
|
||||
chained_input.add(ca, color=color_mapping[action])
|
||||
chained_input.add("\nThought:")
|
||||
@@ -2,7 +2,7 @@
|
||||
from langchain.prompts.prompt import PromptTemplate
|
||||
|
||||
_PROMPT_TEMPLATE = """
|
||||
You are an agent controlling a browser. You are given:
|
||||
You are an agents controlling a browser. You are given:
|
||||
|
||||
(1) an objective that you are trying to achieve
|
||||
(2) the URL of your current web page
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
"""Chain that implements the ReAct paper from https://arxiv.org/pdf/2210.03629.pdf."""
|
||||
import re
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from pydantic import BaseModel, Extra
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.react.prompt import PROMPT
|
||||
from langchain.docstore.base import Docstore
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.input import ChainedInput
|
||||
from langchain.llms.base import LLM
|
||||
|
||||
|
||||
def predict_until_observation(
|
||||
llm_chain: LLMChain, prompt: str, i: int
|
||||
) -> Tuple[str, str, str]:
|
||||
"""Generate text until an observation is needed."""
|
||||
action_prefix = f"Action {i}: "
|
||||
stop_seq = f"\nObservation {i}:"
|
||||
ret_text = llm_chain.predict(input=prompt, stop=[stop_seq])
|
||||
# Sometimes the LLM forgets to take an action, so we prompt it to.
|
||||
while not ret_text.split("\n")[-1].startswith(action_prefix):
|
||||
ret_text += f"\nAction {i}:"
|
||||
new_text = llm_chain.predict(input=prompt + ret_text, stop=[stop_seq])
|
||||
ret_text += new_text
|
||||
# The action block should be the last line.
|
||||
action_block = ret_text.split("\n")[-1]
|
||||
action_str = action_block[len(action_prefix) :]
|
||||
# Parse out the action and the directive.
|
||||
re_matches = re.search(r"(.*?)\[(.*?)\]", action_str)
|
||||
if re_matches is None:
|
||||
raise ValueError(f"Could not parse action directive: {action_str}")
|
||||
return ret_text, re_matches.group(1), re_matches.group(2)
|
||||
|
||||
|
||||
class ReActChain(Chain, BaseModel):
|
||||
"""Chain that implements the ReAct paper.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import ReActChain, OpenAI
|
||||
react = ReAct(llm=OpenAI())
|
||||
"""
|
||||
|
||||
llm: LLM
|
||||
"""LLM wrapper to use."""
|
||||
docstore: Docstore
|
||||
"""Docstore to use."""
|
||||
input_key: str = "question" #: :meta private:
|
||||
output_key: str = "answer" #: :meta private:
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Expect input key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.input_key]
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Expect output key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.output_key]
|
||||
|
||||
def _call(self, inputs: Dict[str, Any]) -> Dict[str, str]:
|
||||
question = inputs[self.input_key]
|
||||
llm_chain = LLMChain(llm=self.llm, prompt=PROMPT)
|
||||
chained_input = ChainedInput(f"{question}\nThought 1:", verbose=self.verbose)
|
||||
i = 1
|
||||
document = None
|
||||
while True:
|
||||
ret_text, action, directive = predict_until_observation(
|
||||
llm_chain, chained_input.input, i
|
||||
)
|
||||
chained_input.add(ret_text, color="green")
|
||||
if action == "Search":
|
||||
result = self.docstore.search(directive)
|
||||
if isinstance(result, Document):
|
||||
document = result
|
||||
observation = document.summary
|
||||
else:
|
||||
document = None
|
||||
observation = result
|
||||
elif action == "Lookup":
|
||||
if document is None:
|
||||
raise ValueError("Cannot lookup without a successful search first")
|
||||
observation = document.lookup(directive)
|
||||
elif action == "Finish":
|
||||
return {self.output_key: directive}
|
||||
else:
|
||||
raise ValueError(f"Got unknown action directive: {action}")
|
||||
chained_input.add(f"\nObservation {i}: ")
|
||||
chained_input.add(observation, color="yellow")
|
||||
chained_input.add(f"\nThought {i + 1}:")
|
||||
i += 1
|
||||
@@ -1,149 +0,0 @@
|
||||
"""Chain that does self ask with search."""
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pydantic import BaseModel, Extra
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.self_ask_with_search.prompt import PROMPT
|
||||
from langchain.chains.serpapi import SerpAPIChain
|
||||
from langchain.input import ChainedInput
|
||||
from langchain.llms.base import LLM
|
||||
|
||||
|
||||
def extract_answer(generated: str) -> str:
|
||||
"""Extract answer from text."""
|
||||
if "\n" not in generated:
|
||||
last_line = generated
|
||||
else:
|
||||
last_line = generated.split("\n")[-1]
|
||||
|
||||
if ":" not in last_line:
|
||||
after_colon = last_line
|
||||
else:
|
||||
after_colon = generated.split(":")[-1]
|
||||
|
||||
if " " == after_colon[0]:
|
||||
after_colon = after_colon[1:]
|
||||
if "." == after_colon[-1]:
|
||||
after_colon = after_colon[:-1]
|
||||
|
||||
return after_colon
|
||||
|
||||
|
||||
def extract_question(generated: str, followup: str) -> str:
|
||||
"""Extract question from text."""
|
||||
if "\n" not in generated:
|
||||
last_line = generated
|
||||
else:
|
||||
last_line = generated.split("\n")[-1]
|
||||
|
||||
if followup not in last_line:
|
||||
print("we probably should never get here..." + generated)
|
||||
|
||||
if ":" not in last_line:
|
||||
after_colon = last_line
|
||||
else:
|
||||
after_colon = generated.split(":")[-1]
|
||||
|
||||
if " " == after_colon[0]:
|
||||
after_colon = after_colon[1:]
|
||||
if "?" != after_colon[-1]:
|
||||
print("we probably should never get here..." + generated)
|
||||
|
||||
return after_colon
|
||||
|
||||
|
||||
def get_last_line(generated: str) -> str:
|
||||
"""Get the last line in text."""
|
||||
if "\n" not in generated:
|
||||
last_line = generated
|
||||
else:
|
||||
last_line = generated.split("\n")[-1]
|
||||
|
||||
return last_line
|
||||
|
||||
|
||||
def greenify(_input: str) -> str:
|
||||
"""Add green highlighting to text."""
|
||||
return "\x1b[102m" + _input + "\x1b[0m"
|
||||
|
||||
|
||||
def yellowfy(_input: str) -> str:
|
||||
"""Add yellow highlighting to text."""
|
||||
return "\x1b[106m" + _input + "\x1b[0m"
|
||||
|
||||
|
||||
class SelfAskWithSearchChain(Chain, BaseModel):
|
||||
"""Chain that does self ask with search.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain import SelfAskWithSearchChain, OpenAI, SerpAPIChain
|
||||
search_chain = SerpAPIChain()
|
||||
self_ask = SelfAskWithSearchChain(llm=OpenAI(), search_chain=search_chain)
|
||||
"""
|
||||
|
||||
llm: LLM
|
||||
"""LLM wrapper to use."""
|
||||
search_chain: SerpAPIChain
|
||||
"""Search chain to use."""
|
||||
input_key: str = "question" #: :meta private:
|
||||
output_key: str = "answer" #: :meta private:
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Expect input key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.input_key]
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Expect output key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.output_key]
|
||||
|
||||
def _call(self, inputs: Dict[str, Any]) -> Dict[str, str]:
|
||||
chained_input = ChainedInput(inputs[self.input_key], verbose=self.verbose)
|
||||
chained_input.add("\nAre follow up questions needed here:")
|
||||
llm_chain = LLMChain(llm=self.llm, prompt=PROMPT)
|
||||
intermediate = "\nIntermediate answer:"
|
||||
followup = "Follow up:"
|
||||
finalans = "\nSo the final answer is:"
|
||||
ret_text = llm_chain.predict(input=chained_input.input, stop=[intermediate])
|
||||
chained_input.add(ret_text, color="green")
|
||||
while followup in get_last_line(ret_text):
|
||||
question = extract_question(ret_text, followup)
|
||||
external_answer = self.search_chain.run(question)
|
||||
if external_answer is not None:
|
||||
chained_input.add(intermediate + " ")
|
||||
chained_input.add(external_answer + ".", color="yellow")
|
||||
ret_text = llm_chain.predict(
|
||||
input=chained_input.input, stop=["\nIntermediate answer:"]
|
||||
)
|
||||
chained_input.add(ret_text, color="green")
|
||||
else:
|
||||
# We only get here in the very rare case that Google returns no answer.
|
||||
chained_input.add(intermediate + " ")
|
||||
preds = llm_chain.predict(
|
||||
input=chained_input.input, stop=["\n" + followup, finalans]
|
||||
)
|
||||
chained_input.add(preds, color="green")
|
||||
|
||||
if finalans not in ret_text:
|
||||
chained_input.add(finalans)
|
||||
ret_text = llm_chain.predict(input=chained_input.input, stop=["\n"])
|
||||
chained_input.add(ret_text, color="green")
|
||||
|
||||
return {self.output_key: ret_text}
|
||||
137
langchain/chains/sequential.py
Normal file
137
langchain/chains/sequential.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""Chain pipeline where the outputs of one step feed directly into next."""
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
from pydantic import BaseModel, Extra, root_validator
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.input import get_color_mapping, print_text
|
||||
|
||||
|
||||
class SequentialChain(Chain, BaseModel):
|
||||
"""Chain where the outputs of one step feed directly into next."""
|
||||
|
||||
chains: List[Chain]
|
||||
input_variables: List[str]
|
||||
output_variables: List[str] #: :meta private:
|
||||
return_all: bool = False
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Expect input key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return self.input_variables
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Return output key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return self.output_variables
|
||||
|
||||
@root_validator(pre=True)
|
||||
def validate_chains(cls, values: Dict) -> Dict:
|
||||
"""Validate that the correct inputs exist for all chains."""
|
||||
chains = values["chains"]
|
||||
input_variables = values["input_variables"]
|
||||
known_variables = set(input_variables)
|
||||
for chain in chains:
|
||||
missing_vars = set(chain.input_keys).difference(known_variables)
|
||||
if missing_vars:
|
||||
raise ValueError(f"Missing required input keys: {missing_vars}")
|
||||
overlapping_keys = known_variables.intersection(chain.output_keys)
|
||||
if overlapping_keys:
|
||||
raise ValueError(
|
||||
f"Chain returned keys that already exist: {overlapping_keys}"
|
||||
)
|
||||
known_variables |= set(chain.output_keys)
|
||||
|
||||
if "output_variables" not in values:
|
||||
if values.get("return_all", False):
|
||||
output_keys = known_variables.difference(input_variables)
|
||||
else:
|
||||
output_keys = chains[-1].output_keys
|
||||
values["output_variables"] = output_keys
|
||||
else:
|
||||
missing_vars = set(values["output_variables"]).difference(known_variables)
|
||||
if missing_vars:
|
||||
raise ValueError(
|
||||
f"Expected output variables that were not found: {missing_vars}."
|
||||
)
|
||||
return values
|
||||
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
known_values = inputs.copy()
|
||||
for i, chain in enumerate(self.chains):
|
||||
outputs = chain(known_values, return_only_outputs=True)
|
||||
if self.verbose:
|
||||
print(f"\033[1mChain {i}\033[0m:\n{outputs}\n")
|
||||
known_values.update(outputs)
|
||||
return {k: known_values[k] for k in self.output_variables}
|
||||
|
||||
|
||||
class SimpleSequentialChain(Chain, BaseModel):
|
||||
"""Simple chain where the outputs of one step feed directly into next."""
|
||||
|
||||
chains: List[Chain]
|
||||
strip_outputs: bool = False
|
||||
input_key: str = "input" #: :meta private:
|
||||
output_key: str = "output" #: :meta private:
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
|
||||
extra = Extra.forbid
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Expect input key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.input_key]
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Return output key.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return [self.output_key]
|
||||
|
||||
@root_validator()
|
||||
def validate_chains(cls, values: Dict) -> Dict:
|
||||
"""Validate that chains are all single input/output."""
|
||||
for chain in values["chains"]:
|
||||
if len(chain.input_keys) != 1:
|
||||
raise ValueError(
|
||||
"Chains used in SimplePipeline should all have one input, got "
|
||||
f"{chain} with {len(chain.input_keys)} inputs."
|
||||
)
|
||||
if len(chain.output_keys) != 1:
|
||||
raise ValueError(
|
||||
"Chains used in SimplePipeline should all have one output, got "
|
||||
f"{chain} with {len(chain.output_keys)} outputs."
|
||||
)
|
||||
return values
|
||||
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
_input = inputs[self.input_key]
|
||||
color_mapping = get_color_mapping([str(i) for i in range(len(self.chains))])
|
||||
for i, chain in enumerate(self.chains):
|
||||
_input = chain.run(_input)
|
||||
if self.strip_outputs:
|
||||
_input = _input.strip()
|
||||
if self.verbose:
|
||||
print_text(_input, color=color_mapping[str(i)], end="\n")
|
||||
return {self.output_key: _input}
|
||||
@@ -111,5 +111,5 @@ class SerpAPIChain(Chain, BaseModel):
|
||||
elif "snippet" in res["organic_results"][0].keys():
|
||||
toret = res["organic_results"][0]["snippet"]
|
||||
else:
|
||||
toret = None
|
||||
toret = "No good search result found"
|
||||
return {self.output_key: toret}
|
||||
|
||||
@@ -5,8 +5,9 @@ from pydantic import BaseModel, Extra
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.vector_db_qa.prompt import prompt
|
||||
from langchain.chains.vector_db_qa.prompt import PROMPT
|
||||
from langchain.llms.base import LLM
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
|
||||
|
||||
@@ -29,6 +30,8 @@ class VectorDBQA(Chain, BaseModel):
|
||||
"""Vector Database to connect to."""
|
||||
k: int = 4
|
||||
"""Number of documents to query for."""
|
||||
prompt: PromptTemplate = PROMPT
|
||||
"""Prompt to use when questioning the documents."""
|
||||
input_key: str = "query" #: :meta private:
|
||||
output_key: str = "result" #: :meta private:
|
||||
|
||||
@@ -56,7 +59,7 @@ class VectorDBQA(Chain, BaseModel):
|
||||
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
question = inputs[self.input_key]
|
||||
llm_chain = LLMChain(llm=self.llm, prompt=prompt)
|
||||
llm_chain = LLMChain(llm=self.llm, prompt=self.prompt)
|
||||
docs = self.vectorstore.similarity_search(question, k=self.k)
|
||||
contexts = []
|
||||
for j, doc in enumerate(docs):
|
||||
|
||||
@@ -7,6 +7,6 @@ prompt_template = """Use the following pieces of context to answer the question
|
||||
|
||||
Question: {question}
|
||||
Helpful Answer:"""
|
||||
prompt = PromptTemplate(
|
||||
PROMPT = PromptTemplate(
|
||||
template=prompt_template, input_variables=["context", "question"]
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Interface to access to place that stores documents."""
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Union
|
||||
from typing import Dict, Union
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
|
||||
@@ -15,3 +15,11 @@ class Docstore(ABC):
|
||||
If page exists, return the page summary, and a Document object.
|
||||
If page does not exist, return similar entries.
|
||||
"""
|
||||
|
||||
|
||||
class AddableMixin(ABC):
|
||||
"""Mixin class that supports adding texts."""
|
||||
|
||||
@abstractmethod
|
||||
def add(self, texts: Dict[str, Document]) -> None:
|
||||
"""Add more documents."""
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
"""Simple in memory docstore in the form of a dict."""
|
||||
from typing import Dict, Union
|
||||
|
||||
from langchain.docstore.base import Docstore
|
||||
from langchain.docstore.base import AddableMixin, Docstore
|
||||
from langchain.docstore.document import Document
|
||||
|
||||
|
||||
class InMemoryDocstore(Docstore):
|
||||
class InMemoryDocstore(Docstore, AddableMixin):
|
||||
"""Simple in memory docstore in the form of a dict."""
|
||||
|
||||
def __init__(self, _dict: Dict[str, Document]):
|
||||
"""Initialize with dict."""
|
||||
self._dict = _dict
|
||||
|
||||
def add(self, texts: Dict[str, Document]) -> None:
|
||||
"""Add texts to in memory dictionary."""
|
||||
overlapping = set(texts).intersection(self._dict)
|
||||
if overlapping:
|
||||
raise ValueError(f"Tried to add ids that already exist: {overlapping}")
|
||||
self._dict = dict(self._dict, **texts)
|
||||
|
||||
def search(self, search: str) -> Union[str, Document]:
|
||||
"""Search via direct lookup."""
|
||||
if search not in self._dict:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Experiment with different models."""
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List, Optional, Sequence, Union
|
||||
|
||||
from langchain.agents.agent import Agent
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.input import get_color_mapping, print_text
|
||||
@@ -11,29 +12,32 @@ from langchain.prompts.prompt import PromptTemplate
|
||||
class ModelLaboratory:
|
||||
"""Experiment with different models."""
|
||||
|
||||
def __init__(self, chains: Sequence[Chain], names: Optional[List[str]] = None):
|
||||
def __init__(
|
||||
self, chains: Sequence[Union[Chain, Agent]], names: Optional[List[str]] = None
|
||||
):
|
||||
"""Initialize with chains to experiment with.
|
||||
|
||||
Args:
|
||||
chains: list of chains to experiment with.
|
||||
"""
|
||||
if not isinstance(chains[0], Chain):
|
||||
raise ValueError(
|
||||
"ModelLaboratory should now be initialized with Chains. "
|
||||
"If you want to initialize with LLMs, use the `from_llms` method "
|
||||
"instead (`ModelLaboratory.from_llms(...)`)"
|
||||
)
|
||||
for chain in chains:
|
||||
if len(chain.input_keys) != 1:
|
||||
if not isinstance(chain, (Chain, Agent)):
|
||||
raise ValueError(
|
||||
"Currently only support chains with one input variable, "
|
||||
f"got {chain.input_keys}"
|
||||
)
|
||||
if len(chain.output_keys) != 1:
|
||||
raise ValueError(
|
||||
"Currently only support chains with one output variable, "
|
||||
f"got {chain.output_keys}"
|
||||
"ModelLaboratory should now be initialized with Chains or Agents. "
|
||||
"If you want to initialize with LLMs, use the `from_llms` method "
|
||||
"instead (`ModelLaboratory.from_llms(...)`)"
|
||||
)
|
||||
if isinstance(chain, Chain):
|
||||
if len(chain.input_keys) != 1:
|
||||
raise ValueError(
|
||||
"Currently only support chains with one input variable, "
|
||||
f"got {chain.input_keys}"
|
||||
)
|
||||
if len(chain.output_keys) != 1:
|
||||
raise ValueError(
|
||||
"Currently only support chains with one output variable, "
|
||||
f"got {chain.output_keys}"
|
||||
)
|
||||
if names is not None:
|
||||
if len(names) != len(chains):
|
||||
raise ValueError("Length of chains does not match length of names.")
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
from langchain.prompts.base import BasePromptTemplate
|
||||
from langchain.prompts.few_shot import FewShotPromptTemplate
|
||||
from langchain.prompts.loading import load_prompt
|
||||
from langchain.prompts.prompt import PromptTemplate
|
||||
from langchain.prompts.prompt import Prompt, PromptTemplate
|
||||
|
||||
__all__ = [
|
||||
"BasePromptTemplate",
|
||||
"load_prompt",
|
||||
"PromptTemplate",
|
||||
"FewShotPromptTemplate",
|
||||
"Prompt",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Interface for vector stores."""
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, Iterable, List, Optional
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.embeddings.base import Embeddings
|
||||
@@ -9,6 +9,10 @@ from langchain.embeddings.base import Embeddings
|
||||
class VectorStore(ABC):
|
||||
"""Interface for vector stores."""
|
||||
|
||||
@abstractmethod
|
||||
def add_texts(self, texts: Iterable[str]) -> None:
|
||||
"""Run more texts through the embeddings and add to the vectorstore."""
|
||||
|
||||
@abstractmethod
|
||||
def similarity_search(self, query: str, k: int = 4) -> List[Document]:
|
||||
"""Return docs most similar to query."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Wrapper around Elasticsearch vector database."""
|
||||
import uuid
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.embeddings.base import Embeddings
|
||||
@@ -65,6 +65,28 @@ class ElasticVectorSearch(VectorStore):
|
||||
)
|
||||
self.client = es_client
|
||||
|
||||
def add_texts(self, texts: Iterable[str]) -> None:
|
||||
"""Run more texts through the embeddings and add to the vectorstore."""
|
||||
try:
|
||||
from elasticsearch.helpers import bulk
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Could not import elasticsearch python package. "
|
||||
"Please install it with `pip install elasticearch`."
|
||||
)
|
||||
requests = []
|
||||
for i, text in enumerate(texts):
|
||||
request = {
|
||||
"_op_type": "index",
|
||||
"_index": self.index_name,
|
||||
"vector": self.embedding_function(text),
|
||||
"text": text,
|
||||
}
|
||||
requests.append(request)
|
||||
bulk(self.client, requests)
|
||||
# TODO: add option not to refresh
|
||||
self.client.indices.refresh(index=self.index_name)
|
||||
|
||||
def similarity_search(self, query: str, k: int = 4) -> List[Document]:
|
||||
"""Return docs most similar to query.
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""Wrapper around FAISS vector database."""
|
||||
from typing import Any, Callable, List, Optional
|
||||
import uuid
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
from langchain.docstore.base import Docstore
|
||||
from langchain.docstore.base import AddableMixin, Docstore
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.docstore.in_memory import InMemoryDocstore
|
||||
from langchain.embeddings.base import Embeddings
|
||||
@@ -23,11 +24,41 @@ class FAISS(VectorStore):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_function: Callable, index: Any, docstore: Docstore):
|
||||
def __init__(
|
||||
self,
|
||||
embedding_function: Callable,
|
||||
index: Any,
|
||||
docstore: Docstore,
|
||||
index_to_docstore_id: Dict[int, str],
|
||||
):
|
||||
"""Initialize with necessary components."""
|
||||
self.embedding_function = embedding_function
|
||||
self.index = index
|
||||
self.docstore = docstore
|
||||
self.index_to_docstore_id = index_to_docstore_id
|
||||
|
||||
def add_texts(self, texts: Iterable[str]) -> None:
|
||||
"""Run more texts through the embeddings and add to the vectorstore."""
|
||||
if not isinstance(self.docstore, AddableMixin):
|
||||
raise ValueError(
|
||||
"If trying to add texts, the underlying docstore should support "
|
||||
f"adding items, which {self.docstore} does not"
|
||||
)
|
||||
# Embed and create the documents.
|
||||
embeddings = [self.embedding_function(text) for text in texts]
|
||||
documents = [Document(page_content=text) for text in texts]
|
||||
# Add to the index, the index_to_id mapping, and the docstore.
|
||||
starting_len = len(self.index_to_docstore_id)
|
||||
self.index.add(np.array(embeddings, dtype=np.float32))
|
||||
# Get list of index, id, and docs.
|
||||
full_info = [
|
||||
(starting_len + i, str(uuid.uuid4()), doc)
|
||||
for i, doc in enumerate(documents)
|
||||
]
|
||||
# Add information to docstore and index.
|
||||
self.docstore.add({_id: doc for _, _id, doc in full_info})
|
||||
index_to_id = {index: _id for index, _id, _ in full_info}
|
||||
self.index_to_docstore_id.update(index_to_id)
|
||||
|
||||
def similarity_search(self, query: str, k: int = 4) -> List[Document]:
|
||||
"""Return docs most similar to query.
|
||||
@@ -46,9 +77,10 @@ class FAISS(VectorStore):
|
||||
if i == -1:
|
||||
# This happens when not enough docs are returned.
|
||||
continue
|
||||
doc = self.docstore.search(str(i))
|
||||
_id = self.index_to_docstore_id[i]
|
||||
doc = self.docstore.search(_id)
|
||||
if not isinstance(doc, Document):
|
||||
raise ValueError(f"Could not find document for id {i}, got {doc}")
|
||||
raise ValueError(f"Could not find document for id {_id}, got {doc}")
|
||||
docs.append(doc)
|
||||
return docs
|
||||
|
||||
@@ -92,5 +124,8 @@ class FAISS(VectorStore):
|
||||
for i, text in enumerate(texts):
|
||||
metadata = metadatas[i] if metadatas else {}
|
||||
documents.append(Document(page_content=text, metadata=metadata))
|
||||
docstore = InMemoryDocstore({str(i): doc for i, doc in enumerate(documents)})
|
||||
return cls(embedding.embed_query, index, docstore)
|
||||
index_to_id = {i: str(uuid.uuid4()) for i in range(len(documents))}
|
||||
docstore = InMemoryDocstore(
|
||||
{index_to_id[i]: doc for i, doc in enumerate(documents)}
|
||||
)
|
||||
return cls(embedding.embed_query, index, docstore, index_to_id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Integration test for self ask with search."""
|
||||
|
||||
from langchain.chains.react.base import ReActChain
|
||||
from langchain.agents.react.base import ReActChain
|
||||
from langchain.docstore.wikipedia import Wikipedia
|
||||
from langchain.llms.openai import OpenAI
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Integration test for self ask with search."""
|
||||
from langchain.chains.self_ask_with_search.base import SelfAskWithSearchChain
|
||||
from langchain.agents.self_ask_with_search.base import SelfAskWithSearchChain
|
||||
from langchain.chains.serpapi import SerpAPIChain
|
||||
from langchain.llms.openai import OpenAI
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.docstore.in_memory import InMemoryDocstore
|
||||
from langchain.docstore.wikipedia import Wikipedia
|
||||
from langchain.embeddings.base import Embeddings
|
||||
from langchain.vectorstores.faiss import FAISS
|
||||
|
||||
@@ -25,11 +26,12 @@ def test_faiss() -> None:
|
||||
"""Test end to end construction and search."""
|
||||
texts = ["foo", "bar", "baz"]
|
||||
docsearch = FAISS.from_texts(texts, FakeEmbeddings())
|
||||
index_to_id = docsearch.index_to_docstore_id
|
||||
expected_docstore = InMemoryDocstore(
|
||||
{
|
||||
"0": Document(page_content="foo"),
|
||||
"1": Document(page_content="bar"),
|
||||
"2": Document(page_content="baz"),
|
||||
index_to_id[0]: Document(page_content="foo"),
|
||||
index_to_id[1]: Document(page_content="bar"),
|
||||
index_to_id[2]: Document(page_content="baz"),
|
||||
}
|
||||
)
|
||||
assert docsearch.docstore.__dict__ == expected_docstore.__dict__
|
||||
@@ -62,3 +64,21 @@ def test_faiss_search_not_found() -> None:
|
||||
docsearch.docstore = InMemoryDocstore({})
|
||||
with pytest.raises(ValueError):
|
||||
docsearch.similarity_search("foo")
|
||||
|
||||
|
||||
def test_faiss_add_texts() -> None:
|
||||
"""Test end to end adding of texts."""
|
||||
# Create initial doc store.
|
||||
texts = ["foo", "bar", "baz"]
|
||||
docsearch = FAISS.from_texts(texts, FakeEmbeddings())
|
||||
# Test adding a similar document as before.
|
||||
docsearch.add_texts(["foo"])
|
||||
output = docsearch.similarity_search("foo", k=2)
|
||||
assert output == [Document(page_content="foo"), Document(page_content="foo")]
|
||||
|
||||
|
||||
def test_faiss_add_texts_not_supported() -> None:
|
||||
"""Test adding of texts to a docstore that doesn't support it."""
|
||||
docsearch = FAISS(FakeEmbeddings().embed_query, None, Wikipedia(), {})
|
||||
with pytest.raises(ValueError):
|
||||
docsearch.add_texts(["foo"])
|
||||
|
||||
1
tests/unit_tests/agents/__init__.py
Normal file
1
tests/unit_tests/agents/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Test agent functionality."""
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from langchain.chains.mrkl.base import ChainConfig, MRKLChain, get_action_and_input
|
||||
from langchain.chains.mrkl.prompt import BASE_TEMPLATE
|
||||
from langchain.agents.mrkl.base import ZeroShotAgent, get_action_and_input
|
||||
from langchain.agents.mrkl.prompt import BASE_TEMPLATE
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.prompts import PromptTemplate
|
||||
from tests.unit_tests.llms.fake_llm import FakeLLM
|
||||
|
||||
@@ -29,7 +30,7 @@ def test_get_final_answer() -> None:
|
||||
"Final Answer: 1994"
|
||||
)
|
||||
action, action_input = get_action_and_input(llm_output)
|
||||
assert action == "Final Answer: "
|
||||
assert action == "Final Answer"
|
||||
assert action_input == "1994"
|
||||
|
||||
|
||||
@@ -52,19 +53,15 @@ def test_bad_action_line() -> None:
|
||||
def test_from_chains() -> None:
|
||||
"""Test initializing from chains."""
|
||||
chain_configs = [
|
||||
ChainConfig(
|
||||
action_name="foo", action=lambda x: "foo", action_description="foobar1"
|
||||
),
|
||||
ChainConfig(
|
||||
action_name="bar", action=lambda x: "bar", action_description="foobar2"
|
||||
),
|
||||
Tool(name="foo", func=lambda x: "foo", description="foobar1"),
|
||||
Tool(name="bar", func=lambda x: "bar", description="foobar2"),
|
||||
]
|
||||
mrkl_chain = MRKLChain.from_chains(FakeLLM(), chain_configs)
|
||||
agent = ZeroShotAgent.from_llm_and_tools(FakeLLM(), chain_configs)
|
||||
expected_tools_prompt = "foo: foobar1\nbar: foobar2"
|
||||
expected_tool_names = "foo, bar"
|
||||
expected_template = BASE_TEMPLATE.format(
|
||||
tools=expected_tools_prompt, tool_names=expected_tool_names
|
||||
)
|
||||
prompt = mrkl_chain.prompt
|
||||
prompt = agent.llm_chain.prompt
|
||||
assert isinstance(prompt, PromptTemplate)
|
||||
assert prompt.template == expected_template
|
||||
@@ -4,8 +4,8 @@ from typing import Any, List, Mapping, Optional, Union
|
||||
|
||||
import pytest
|
||||
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.chains.react.base import ReActChain, predict_until_observation
|
||||
from langchain.agents.react.base import ReActChain, ReActDocstoreAgent
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.docstore.base import Docstore
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.llms.base import LLM
|
||||
@@ -51,33 +51,32 @@ class FakeDocstore(Docstore):
|
||||
|
||||
def test_predict_until_observation_normal() -> None:
|
||||
"""Test predict_until_observation when observation is made normally."""
|
||||
outputs = ["foo\nAction 1: search[foo]"]
|
||||
outputs = ["foo\nAction 1: Search[foo]"]
|
||||
fake_llm = FakeListLLM(outputs)
|
||||
fake_llm_chain = LLMChain(llm=fake_llm, prompt=_FAKE_PROMPT)
|
||||
ret_text, action, directive = predict_until_observation(fake_llm_chain, "", 1)
|
||||
assert ret_text == outputs[0]
|
||||
assert action == "search"
|
||||
assert directive == "foo"
|
||||
tools = [
|
||||
Tool("Search", lambda x: x),
|
||||
Tool("Lookup", lambda x: x),
|
||||
]
|
||||
agent = ReActDocstoreAgent.from_llm_and_tools(fake_llm, tools)
|
||||
output = agent.get_action("")
|
||||
assert output.log == outputs[0]
|
||||
assert output.tool == "Search"
|
||||
assert output.tool_input == "foo"
|
||||
|
||||
|
||||
def test_predict_until_observation_repeat() -> None:
|
||||
"""Test when no action is generated initially."""
|
||||
outputs = ["foo", " search[foo]"]
|
||||
outputs = ["foo", " Search[foo]"]
|
||||
fake_llm = FakeListLLM(outputs)
|
||||
fake_llm_chain = LLMChain(llm=fake_llm, prompt=_FAKE_PROMPT)
|
||||
ret_text, action, directive = predict_until_observation(fake_llm_chain, "", 1)
|
||||
assert ret_text == "foo\nAction 1: search[foo]"
|
||||
assert action == "search"
|
||||
assert directive == "foo"
|
||||
|
||||
|
||||
def test_predict_until_observation_error() -> None:
|
||||
"""Test handling of generation of text that cannot be parsed."""
|
||||
outputs = ["foo\nAction 1: foo"]
|
||||
fake_llm = FakeListLLM(outputs)
|
||||
fake_llm_chain = LLMChain(llm=fake_llm, prompt=_FAKE_PROMPT)
|
||||
with pytest.raises(ValueError):
|
||||
predict_until_observation(fake_llm_chain, "", 1)
|
||||
tools = [
|
||||
Tool("Search", lambda x: x),
|
||||
Tool("Lookup", lambda x: x),
|
||||
]
|
||||
agent = ReActDocstoreAgent.from_llm_and_tools(fake_llm, tools)
|
||||
output = agent.get_action("")
|
||||
assert output.log == "foo\nAction 1: Search[foo]"
|
||||
assert output.tool == "Search"
|
||||
assert output.tool_input == "foo"
|
||||
|
||||
|
||||
def test_react_chain() -> None:
|
||||
@@ -89,9 +88,8 @@ def test_react_chain() -> None:
|
||||
]
|
||||
fake_llm = FakeListLLM(responses)
|
||||
react_chain = ReActChain(llm=fake_llm, docstore=FakeDocstore())
|
||||
inputs = {"question": "when was langchain made"}
|
||||
output = react_chain(inputs)
|
||||
assert output["answer"] == "2022"
|
||||
output = react_chain.run("when was langchain made")
|
||||
assert output == "2022"
|
||||
|
||||
|
||||
def test_react_chain_bad_action() -> None:
|
||||
@@ -101,5 +99,5 @@ def test_react_chain_bad_action() -> None:
|
||||
]
|
||||
fake_llm = FakeListLLM(responses)
|
||||
react_chain = ReActChain(llm=fake_llm, docstore=FakeDocstore())
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(KeyError):
|
||||
react_chain.run("when was langchain made")
|
||||
140
tests/unit_tests/chains/test_sequential.py
Normal file
140
tests/unit_tests/chains/test_sequential.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""Test pipeline functionality."""
|
||||
from typing import Dict, List
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chains.sequential import SequentialChain, SimpleSequentialChain
|
||||
|
||||
|
||||
class FakeChain(Chain, BaseModel):
|
||||
"""Fake Chain for testing purposes."""
|
||||
|
||||
input_variables: List[str]
|
||||
output_variables: List[str]
|
||||
|
||||
@property
|
||||
def input_keys(self) -> List[str]:
|
||||
"""Input keys this chain returns."""
|
||||
return self.input_variables
|
||||
|
||||
@property
|
||||
def output_keys(self) -> List[str]:
|
||||
"""Input keys this chain returns."""
|
||||
return self.output_variables
|
||||
|
||||
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
|
||||
outputs = {}
|
||||
for var in self.output_variables:
|
||||
variables = [inputs[k] for k in self.input_variables]
|
||||
outputs[var] = " ".join(variables) + "foo"
|
||||
return outputs
|
||||
|
||||
|
||||
def test_sequential_usage_single_inputs() -> None:
|
||||
"""Test sequential on single input chains."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
|
||||
chain = SequentialChain(chains=[chain_1, chain_2], input_variables=["foo"])
|
||||
output = chain({"foo": "123"})
|
||||
expected_output = {"baz": "123foofoo", "foo": "123"}
|
||||
assert output == expected_output
|
||||
|
||||
|
||||
def test_sequential_usage_multiple_inputs() -> None:
|
||||
"""Test sequential on multiple input chains."""
|
||||
chain_1 = FakeChain(input_variables=["foo", "test"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar", "foo"], output_variables=["baz"])
|
||||
chain = SequentialChain(chains=[chain_1, chain_2], input_variables=["foo", "test"])
|
||||
output = chain({"foo": "123", "test": "456"})
|
||||
expected_output = {
|
||||
"baz": "123 456foo 123foo",
|
||||
"foo": "123",
|
||||
"test": "456",
|
||||
}
|
||||
assert output == expected_output
|
||||
|
||||
|
||||
def test_sequential_usage_multiple_outputs() -> None:
|
||||
"""Test sequential usage on multiple output chains."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar", "test"])
|
||||
chain_2 = FakeChain(input_variables=["bar", "foo"], output_variables=["baz"])
|
||||
chain = SequentialChain(chains=[chain_1, chain_2], input_variables=["foo"])
|
||||
output = chain({"foo": "123"})
|
||||
expected_output = {
|
||||
"baz": "123foo 123foo",
|
||||
"foo": "123",
|
||||
}
|
||||
assert output == expected_output
|
||||
|
||||
|
||||
def test_sequential_missing_inputs() -> None:
|
||||
"""Test error is raised when input variables are missing."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar", "test"], output_variables=["baz"])
|
||||
with pytest.raises(ValueError):
|
||||
# Also needs "test" as an input
|
||||
SequentialChain(chains=[chain_1, chain_2], input_variables=["foo"])
|
||||
|
||||
|
||||
def test_sequential_bad_outputs() -> None:
|
||||
"""Test error is raised when bad outputs are specified."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
|
||||
with pytest.raises(ValueError):
|
||||
# "test" is not present as an output variable.
|
||||
SequentialChain(
|
||||
chains=[chain_1, chain_2],
|
||||
input_variables=["foo"],
|
||||
output_variables=["test"],
|
||||
)
|
||||
|
||||
|
||||
def test_sequential_valid_outputs() -> None:
|
||||
"""Test chain runs when valid outputs are specified."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
|
||||
chain = SequentialChain(
|
||||
chains=[chain_1, chain_2],
|
||||
input_variables=["foo"],
|
||||
output_variables=["bar", "baz"],
|
||||
)
|
||||
output = chain({"foo": "123"}, return_only_outputs=True)
|
||||
expected_output = {"baz": "123foofoo", "bar": "123foo"}
|
||||
assert output == expected_output
|
||||
|
||||
|
||||
def test_sequential_overlapping_inputs() -> None:
|
||||
"""Test error is raised when input variables are overlapping."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar", "test"])
|
||||
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
|
||||
with pytest.raises(ValueError):
|
||||
# "test" is specified as an input, but also is an output of one step
|
||||
SequentialChain(chains=[chain_1, chain_2], input_variables=["foo", "test"])
|
||||
|
||||
|
||||
def test_simple_sequential_functionality() -> None:
|
||||
"""Test simple sequential functionality."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
|
||||
chain = SimpleSequentialChain(chains=[chain_1, chain_2])
|
||||
output = chain({"input": "123"})
|
||||
expected_output = {"output": "123foofoo", "input": "123"}
|
||||
assert output == expected_output
|
||||
|
||||
|
||||
def test_multi_input_errors() -> None:
|
||||
"""Test simple sequential errors if multiple input variables are expected."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
|
||||
chain_2 = FakeChain(input_variables=["bar", "foo"], output_variables=["baz"])
|
||||
with pytest.raises(ValueError):
|
||||
SimpleSequentialChain(chains=[chain_1, chain_2])
|
||||
|
||||
|
||||
def test_multi_output_errors() -> None:
|
||||
"""Test simple sequential errors if multiple output variables are expected."""
|
||||
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar", "grok"])
|
||||
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
|
||||
with pytest.raises(ValueError):
|
||||
SimpleSequentialChain(chains=[chain_1, chain_2])
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Test in memory docstore."""
|
||||
import pytest
|
||||
|
||||
from langchain.docstore.document import Document
|
||||
from langchain.docstore.in_memory import InMemoryDocstore
|
||||
@@ -19,3 +20,37 @@ def test_document_not_found() -> None:
|
||||
docstore = InMemoryDocstore(_dict)
|
||||
output = docstore.search("bar")
|
||||
assert output == "ID bar not found."
|
||||
|
||||
|
||||
def test_adding_document() -> None:
|
||||
"""Test that documents are added correctly."""
|
||||
_dict = {"foo": Document(page_content="bar")}
|
||||
docstore = InMemoryDocstore(_dict)
|
||||
new_dict = {"bar": Document(page_content="foo")}
|
||||
docstore.add(new_dict)
|
||||
|
||||
# Test that you can find new document.
|
||||
foo_output = docstore.search("bar")
|
||||
assert isinstance(foo_output, Document)
|
||||
assert foo_output.page_content == "foo"
|
||||
|
||||
# Test that old document is the same.
|
||||
bar_output = docstore.search("foo")
|
||||
assert isinstance(bar_output, Document)
|
||||
assert bar_output.page_content == "bar"
|
||||
|
||||
|
||||
def test_adding_document_already_exists() -> None:
|
||||
"""Test that error is raised if document id already exists."""
|
||||
_dict = {"foo": Document(page_content="bar")}
|
||||
docstore = InMemoryDocstore(_dict)
|
||||
new_dict = {"foo": Document(page_content="foo")}
|
||||
|
||||
# Test that error is raised.
|
||||
with pytest.raises(ValueError):
|
||||
docstore.add(new_dict)
|
||||
|
||||
# Test that old document is the same.
|
||||
bar_output = docstore.search("foo")
|
||||
assert isinstance(bar_output, Document)
|
||||
assert bar_output.page_content == "bar"
|
||||
|
||||
Reference in New Issue
Block a user