Harrison/simple memory (#1435)

Co-authored-by: Tim Asp <707699+timothyasp@users.noreply.github.com>
This commit is contained in:
Harrison Chase 2023-03-04 08:15:52 -08:00 committed by GitHub
parent a1b9dfc099
commit 63a5614d23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 206 additions and 40 deletions

View File

@ -36,6 +36,25 @@
{
"cell_type": "code",
"execution_count": 1,
"id": "7a886879",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cannot find .env file\n"
]
}
],
"source": [
"%load_ext dotenv\n",
"%dotenv"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3f2f9b8c",
"metadata": {},
"outputs": [],
@ -47,7 +66,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 3,
"id": "b8237d1a",
"metadata": {},
"outputs": [],
@ -64,7 +83,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 4,
"id": "4a391730",
"metadata": {},
"outputs": [],
@ -82,7 +101,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 5,
"id": "9368bd63",
"metadata": {},
"outputs": [],
@ -94,7 +113,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 6,
"id": "d39e15f5",
"metadata": {},
"outputs": [
@ -107,22 +126,20 @@
"\u001b[1m> Entering new SimpleSequentialChain chain...\u001b[0m\n",
"\u001b[36;1m\u001b[1;3m\n",
"\n",
"Tragedy at Sunset on the Beach follows the story of a young couple, Jack and Annie, who have just started to explore the possibility of a relationship together. After a day spent in the sun and sand, they decide to take a romantic stroll down the beach as the sun sets. \n",
"Tragedy at Sunset on the Beach is a story of a young couple, Jack and Sarah, who are in love and looking forward to their future together. On the night of their anniversary, they decide to take a walk on the beach at sunset. As they are walking, they come across a mysterious figure, who tells them that their love will be tested in the near future. \n",
"\n",
"However, their romantic evening quickly turns tragic when they stumble upon a body lying in the sand. As they approach to investigate, they are shocked to discover that it is Jack's long-lost brother, who has been missing for several years. \n",
"The figure then tells the couple that the sun will soon set, and with it, a tragedy will strike. If Jack and Sarah can stay together and pass the test, they will be granted everlasting love. However, if they fail, their love will be lost forever.\n",
"\n",
"The story follows Jack and Annie as they navigate their way through the tragedy and their newfound relationship. With the help of their friends, family, and the beach's inhabitants, Jack and Annie must come to terms with their deep-seated emotions and the reality of the situation. \n",
"\n",
"Ultimately, the play explores themes of family, love, and loss, as Jack and Annie's story unfolds against the beautiful backdrop of the beach at sunset.\u001b[0m\n",
"The play follows the couple as they struggle to stay together and battle the forces that threaten to tear them apart. Despite the tragedy that awaits them, they remain devoted to one another and fight to keep their love alive. In the end, the couple must decide whether to take a chance on their future together or succumb to the tragedy of the sunset.\u001b[0m\n",
"\u001b[33;1m\u001b[1;3m\n",
"\n",
"Tragedy at Sunset on the Beach is an emotionally complex tale of family, love, and loss. Told against the beautiful backdrop of a beach at sunset, the story follows Jack and Annie, a young couple just beginning to explore a relationship together. When they stumble upon the body of Jack's long-lost brother on the beach, they must face the reality of the tragedy and come to terms with their deep-seated emotions. \n",
"Tragedy at Sunset on the Beach is an emotionally gripping story of love, hope, and sacrifice. Through the story of Jack and Sarah, the audience is taken on a journey of self-discovery and the power of love to overcome even the greatest of obstacles. \n",
"\n",
"The playwright has crafted a heartfelt and thought-provoking story, one that probes into the depths of the human experience. The cast of characters is well-rounded and fully realized, and the dialogue is natural and emotional. The direction and choreography are top-notch, and the scenic design is breathtaking. \n",
"The play's talented cast brings the characters to life, allowing us to feel the depths of their emotion and the intensity of their struggle. With its compelling story and captivating performances, this play is sure to draw in audiences and leave them on the edge of their seats. \n",
"\n",
"Overall, Tragedy at Sunset on the Beach is a powerful and moving story about the fragility of life and the strength of love. It is sure to tug at your heartstrings and leave you with a newfound appreciation of life's precious moments. Highly recommended.\u001b[0m\n",
"The play's setting of the beach at sunset adds a touch of poignancy and romanticism to the story, while the mysterious figure serves to keep the audience enthralled. Overall, Tragedy at Sunset on the Beach is an engaging and thought-provoking play that is sure to leave audiences feeling inspired and hopeful.\u001b[0m\n",
"\n",
"\u001b[1m> Finished SimpleSequentialChain chain.\u001b[0m\n"
"\u001b[1m> Finished chain.\u001b[0m\n"
]
}
],
@ -132,7 +149,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 7,
"id": "c6649a01",
"metadata": {},
"outputs": [
@ -142,11 +159,11 @@
"text": [
"\n",
"\n",
"Tragedy at Sunset on the Beach is an emotionally complex tale of family, love, and loss. Told against the beautiful backdrop of a beach at sunset, the story follows Jack and Annie, a young couple just beginning to explore a relationship together. When they stumble upon the body of Jack's long-lost brother on the beach, they must face the reality of the tragedy and come to terms with their deep-seated emotions. \n",
"Tragedy at Sunset on the Beach is an emotionally gripping story of love, hope, and sacrifice. Through the story of Jack and Sarah, the audience is taken on a journey of self-discovery and the power of love to overcome even the greatest of obstacles. \n",
"\n",
"The playwright has crafted a heartfelt and thought-provoking story, one that probes into the depths of the human experience. The cast of characters is well-rounded and fully realized, and the dialogue is natural and emotional. The direction and choreography are top-notch, and the scenic design is breathtaking. \n",
"The play's talented cast brings the characters to life, allowing us to feel the depths of their emotion and the intensity of their struggle. With its compelling story and captivating performances, this play is sure to draw in audiences and leave them on the edge of their seats. \n",
"\n",
"Overall, Tragedy at Sunset on the Beach is a powerful and moving story about the fragility of life and the strength of love. It is sure to tug at your heartstrings and leave you with a newfound appreciation of life's precious moments. Highly recommended.\n"
"The play's setting of the beach at sunset adds a touch of poignancy and romanticism to the story, while the mysterious figure serves to keep the audience enthralled. Overall, Tragedy at Sunset on the Beach is an engaging and thought-provoking play that is sure to leave audiences feeling inspired and hopeful.\n"
]
}
],
@ -167,7 +184,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 8,
"id": "02016a51",
"metadata": {},
"outputs": [],
@ -185,7 +202,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 9,
"id": "8bd38cc2",
"metadata": {},
"outputs": [],
@ -203,7 +220,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 10,
"id": "524523af",
"metadata": {},
"outputs": [],
@ -220,7 +237,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 11,
"id": "3fd3a7be",
"metadata": {},
"outputs": [
@ -231,14 +248,8 @@
"\n",
"\n",
"\u001b[1m> Entering new SequentialChain chain...\u001b[0m\n",
"\u001b[1mChain 0\u001b[0m:\n",
"{'synopsis': \" \\n\\nTragedy at Sunset on the Beach is a dark and gripping drama set in Victorian England. The play follows the story of two lovers, Emma and Edward, whose passionate relationship is threatened by the strict rules and regulations of the time.\\n\\nThe two are deeply in love, but Edward is from a wealthy family and Emma is from a lower class background. Despite the obstacles, the two are determined to be together and decide to elope.\\n\\nOn the night of their planned escape, Emma and Edward meet at the beach at sunset to declare their love for one another and begin a new life together. However, their plans are disrupted when Emma's father discovers their plan and appears on the beach with a gun.\\n\\nIn a heartbreaking scene, Emma's father orders Edward to leave, but Edward refuses and fights for their love. In a fit of rage, Emma's father shoots Edward, killing him instantly. \\n\\nThe tragedy of the play lies in the fact that Emma and Edward are denied their chance at a happy ending due to the rigid social conventions of Victorian England. The audience is left with a heavy heart as the play ends with Emma standing alone on the beach, mourning the loss of her beloved.\"}\n",
"\n",
"\u001b[1mChain 1\u001b[0m:\n",
"{'review': \"\\n\\nTragedy at Sunset on the Beach is an emotionally charged production that will leave audiences heartsick. The play follows the ill-fated love story of Emma and Edward, two star-crossed lovers whose passionate relationship is tragically thwarted by Victorian England's societal conventions. The performance is captivating from start to finish, as the audience is taken on an emotional rollercoaster of love, loss, and heartbreak.\\n\\nThe acting is powerful and sincere, and the performances of the two leads are particularly stirring. Emma and Edward are both portrayed with such tenderness and emotion that it's hard not to feel their pain as they fight for their forbidden love. The climactic scene, in which Edward is shot by Emma's father, is especially heartbreaking and will leave audience members on the edge of their seats.\\n\\nOverall, Tragedy at Sunset on the Beach is a powerful and moving work of theatre. It is a tragedy of impossible love, and a vivid reminder of the devastating consequences of social injustice. The play is sure to leave a lasting impression on anyone who experiences it.\"}\n",
"\n",
"\n",
"\u001b[1m> Finished SequentialChain chain.\u001b[0m\n"
"\u001b[1m> Finished chain.\u001b[0m\n"
]
}
],
@ -246,10 +257,91 @@
"review = overall_chain({\"title\":\"Tragedy at sunset on the beach\", \"era\": \"Victorian England\"})"
]
},
{
"cell_type": "markdown",
"id": "d2fac817",
"metadata": {},
"source": [
"### Memory in Sequential Chains\n",
"Sometimes you may want to pass along some context to use in each step of the chain or in a later part of the chain, but maintaining and chaining together the input/output variables can quickly get messy. Using `SimpleMemory` is a convenient way to do manage this and clean up your chains.\n",
"\n",
"For example, using the previous playwright SequentialChain, lets say you wanted to include some context about date, time and location of the play, and using the generated synopsis and review, create some social media post text. You could add these new context variables as `input_variables`, or we can add a `SimpleMemory` to the chain to manage this context:"
]
},
{
"cell_type": "markdown",
"id": "b2cf3098",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 12,
"id": "6b7b3a7a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new SequentialChain chain...\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'title': 'Tragedy at sunset on the beach',\n",
" 'era': 'Victorian England',\n",
" 'time': 'December 25th, 8pm PST',\n",
" 'location': 'Theater in the Park',\n",
" 'social_post_text': \"\\nSpend your Christmas night with us at Theater in the Park and experience the heartbreaking story of love and loss that is 'A Walk on the Beach'. Set in Victorian England, this romantic tragedy follows the story of Frances and Edward, a young couple whose love is tragically cut short. Don't miss this emotional and thought-provoking production that is sure to leave you in tears. #AWalkOnTheBeach #LoveAndLoss #TheaterInThePark #VictorianEngland\"}"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langchain.chains import SequentialChain\n",
"from langchain.chains.base import SimpleMemory\n",
"\n",
"llm = OpenAI(temperature=.7)\n",
"template = \"\"\"You are a social media manager for a theater company. Given the title of play, the era it is set in, the date,time and location, the synopsis of the play, and the review of the play, it is your job to write a social media post for that play.\n",
"\n",
"Here is some context about the time and location of the play:\n",
"Date and Time: {time}\n",
"Location: {location}\n",
"\n",
"Play Synopsis:\n",
"{synopsis}\n",
"Review from a New York Times play critic of the above play:\n",
"{review}\n",
"\n",
"Social Media Post:\n",
"\"\"\"\n",
"prompt_template = PromptTemplate(input_variables=[\"synopsis\", \"review\", \"time\", \"location\"], template=template)\n",
"social_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=\"social_post_text\")\n",
"\n",
"overall_chain = SequentialChain(\n",
" memory=SimpleMemory(memories={\"time\": \"December 25th, 8pm PST\", \"location\": \"Theater in the Park\"}),\n",
" chains=[synopsis_chain, review_chain, social_chain],\n",
" input_variables=[\"era\", \"title\"],\n",
" # Here we return multiple variables\n",
" output_variables=[\"social_post_text\"],\n",
" verbose=True)\n",
"\n",
"overall_chain({\"title\":\"Tragedy at sunset on the beach\", \"era\": \"Victorian England\"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6be70d27",
"id": "ee9bc09c",
"metadata": {},
"outputs": [],
"source": []
@ -271,7 +363,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
"version": "3.9.1"
}
},
"nbformat": 4,

View File

@ -28,7 +28,10 @@ class Memory(BaseModel, ABC):
@abstractmethod
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]:
"""Return key-value pairs given the text input to the chain."""
"""Return key-value pairs given the text input to the chain.
If None, return all memories
"""
@abstractmethod
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
@ -39,6 +42,29 @@ class Memory(BaseModel, ABC):
"""Clear memory contents."""
class SimpleMemory(Memory, BaseModel):
"""Simple memory for storing context or other bits of information that shouldn't
ever change between prompts.
"""
memories: Dict[str, Any] = dict()
@property
def memory_variables(self) -> List[str]:
return list(self.memories.keys())
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]:
return self.memories
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
"""Nothing should be saved or changed, my memory is set in stone."""
pass
def clear(self) -> None:
"""Nothing to clear, got a memory like a vault."""
pass
def _get_verbosity() -> bool:
return langchain.verbose

View File

@ -276,6 +276,12 @@ class ConversationEntityMemory(Memory, BaseModel):
}
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
if inputs is None:
raise ValueError(
"Inputs must be provided to save context from "
"ConversationEntityMemory."
)
"""Save context from this conversation to buffer."""
if self.input_key is None:
prompt_input_key = _get_prompt_input_key(inputs, self.memory_variables)

View File

@ -131,10 +131,12 @@ class LLMChain(Chain, BaseModel):
]
def _call(self, inputs: Dict[str, Any]) -> Dict[str, str]:
return self.apply([inputs])[0]
known_values = self.prep_inputs(inputs.copy())
return self.apply([known_values])[0]
async def _acall(self, inputs: Dict[str, Any]) -> Dict[str, str]:
return (await self.aapply([inputs]))[0]
known_values = self.prep_inputs(inputs.copy())
return (await self.aapply([known_values]))[0]
def predict(self, **kwargs: Any) -> str:
"""Format prompt with kwargs and pass to LLM.

View File

@ -1,5 +1,4 @@
"""Chain pipeline where the outputs of one step feed directly into next."""
from typing import Dict, List
from pydantic import BaseModel, Extra, root_validator
@ -9,7 +8,7 @@ from langchain.input import get_color_mapping
class SequentialChain(Chain, BaseModel):
"""Chain where the outputs of one step feed directly into next."""
"""Chain where the outputs of one chain feed directly into next."""
chains: List[Chain]
input_variables: List[str]
@ -24,7 +23,7 @@ class SequentialChain(Chain, BaseModel):
@property
def input_keys(self) -> List[str]:
"""Expect input key.
"""Return expected input keys to the chain.
:meta private:
"""
@ -43,7 +42,20 @@ class SequentialChain(Chain, BaseModel):
"""Validate that the correct inputs exist for all chains."""
chains = values["chains"]
input_variables = values["input_variables"]
known_variables = set(input_variables)
memory_keys = list()
if "memory" in values and values["memory"] is not None:
"""Validate that prompt input variables are consistent."""
memory_keys = values["memory"].memory_variables
if any(input_variables) in memory_keys:
overlapping_keys = input_variables & memory_keys
raise ValueError(
f"The the input key(s) {''.join(overlapping_keys)} are found "
f"in the Memory keys ({memory_keys}) - please use input and "
f"memory keys that don't overlap."
)
known_variables = set(input_variables + memory_keys)
for chain in chains:
missing_vars = set(chain.input_keys).difference(known_variables)
if missing_vars:
@ -56,6 +68,7 @@ class SequentialChain(Chain, BaseModel):
raise ValueError(
f"Chain returned keys that already exist: {overlapping_keys}"
)
known_variables |= set(chain.output_keys)
if "output_variables" not in values:
@ -70,6 +83,7 @@ class SequentialChain(Chain, BaseModel):
raise ValueError(
f"Expected output variables that were not found: {missing_vars}."
)
return values
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:

View File

@ -1,5 +1,5 @@
"""Test logic on base chain class."""
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
import pytest
from pydantic import BaseModel
@ -17,7 +17,9 @@ class FakeMemory(Memory, BaseModel):
"""Return baz variable."""
return ["baz"]
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]:
def load_memory_variables(
self, inputs: Optional[Dict[str, Any]] = None
) -> Dict[str, str]:
"""Return baz variable."""
return {"baz": "foo"}

View File

@ -0,0 +1,11 @@
from langchain.chains.base import SimpleMemory
def test_simple_memory() -> None:
"""Test SimpleMemory."""
memory = SimpleMemory(memories={"baz": "foo"})
output = memory.load_memory_variables({})
assert output == {"baz": "foo"}
assert ["baz"] == memory.memory_variables

View File

@ -4,7 +4,7 @@ from typing import Dict, List
import pytest
from pydantic import BaseModel
from langchain.chains.base import Chain
from langchain.chains.base import Chain, SimpleMemory
from langchain.chains.sequential import SequentialChain, SimpleSequentialChain
@ -56,6 +56,19 @@ def test_sequential_usage_multiple_inputs() -> None:
assert output == expected_output
def test_sequential_usage_memory() -> None:
"""Test sequential usage with memory."""
memory = SimpleMemory(memories={"zab": "rab"})
chain_1 = FakeChain(input_variables=["foo"], output_variables=["bar"])
chain_2 = FakeChain(input_variables=["bar"], output_variables=["baz"])
chain = SequentialChain(
memory=memory, chains=[chain_1, chain_2], input_variables=["foo"]
)
output = chain({"foo": "123"})
expected_output = {"baz": "123foofoo", "foo": "123", "zab": "rab"}
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"])