Files
langgraph/examples/example.ipynb
T
2023-08-18 15:10:30 +01:00

11 KiB

Simple Example

This is a simple example to get familiar with how to use permchain. permchain is a pub-sub framework which makes it easy to coordinate multiple LLM actors (whether these be agents or single LLM calls). This notebook goes over a simple example of three actors:

  • a writer, responsible for writing the first draft
  • a editor, responsible for critiquing a written draft
  • a reviser, responsible for taking a draft and associated critiques and editing it

We will first define these actors individually, and then we will show how to coordinate them such that for a given input the writer will write a draft, and then the editor and reviser will go back and forth until the editor thinks its good enough.

In [1]:
from operator import itemgetter

from langchain.chat_models.openai import ChatOpenAI
from langchain.prompts import SystemMessagePromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.runnables.openai_functions import OpenAIFunctionsRouter

from permchain.connection_inmemory import InMemoryPubSubConnection
from permchain.pubsub import PubSub
from permchain.topic import Topic

Drafter

In [2]:
drafter_prompt = (
    SystemMessagePromptTemplate.from_template(
        "You are an expert on turtles, who likes to write in pirate-speak. You have been tasked by your editor with drafting a 100-word article answering the following question."
    )
    + "Question:\n\n{question}"
)
drafter_llm = ChatOpenAI(model="gpt-3.5-turbo")
drafter = drafter_prompt | drafter_llm | StrOutputParser()
In [3]:
drafter.invoke({"question": "what is art?"})
Out [3]:
"Arr, me matey, ye be askin' a question that be as vast as the seven seas. Art be a treasure, a form o' expression that be capturin' the heart and soul. It be the brushstrokes on a canvas, the melodies in a shanty, the words on a page. Art be a glimpse into the mind o' the artist, a window into their world. It be a means o' communicatin', stirrin' emotions, and provokin' thoughts. It be subjective, me hearties, for what be art to one may not be art to another. But in the end, art be a gift that be enrichin' our lives and feedin' our souls."

Critiquer

In [4]:
editor_prompt = (
    SystemMessagePromptTemplate.from_template(
        "You are an editor. You have been tasked with editing the following draft, which was written by a non-expert. Please accept the draft if it is good enough to publish, or send it for revision, along with your notes to guide the revision."
    )
    + "Draft:\n\n{draft}"
)
editor_llm = ChatOpenAI(model="gpt-4")
functions = [
    {
        "name": "revise",
        "description": "Sends the draft for revision",
        "parameters": {
            "type": "object",
            "properties": {
                "notes": {
                    "type": "string",
                    "description": "The editor's notes to guide the revision.",
                },
            },
        },
    },
    {
        "name": "accept",
        "description": "Accepts the draft",
        "parameters": {
            "type": "object",
            "properties": {"ready": {"const": True}},
        },
    },
]
editor = editor_prompt | editor_llm.bind(functions=functions)
In [5]:
editor.invoke({"draft": "hi!"})
Out [5]:
AIMessage(content='', additional_kwargs={'function_call': {'name': 'revise', 'arguments': '{\n  "notes": "The draft is too short and lacks context. Please provide more information or details about the topic you are writing about."\n}'}}, example=False)

Reviser

In [6]:
reviser_prompt = (
    SystemMessagePromptTemplate.from_template(
        "You are an expert on turtles. You have been tasked by your editor with revising the following draft, which was written by a non-expert. You may follow the editor's notes or not, as you see fit."
    )
    + "Draft:\n\n{draft}"
    + "Editor's notes:\n\n{notes}"
)
reviser_llm = ChatOpenAI(model="gpt-3.5-turbo")
reviser = reviser_prompt | reviser_llm | StrOutputParser()
In [7]:
reviser.invoke({"draft": "hi!", "notes": "too short"})
Out [7]:
'Revised draft:\n\nHello there!'

Hooking it all up

We can now hook it all up. This means:

  1. Each chain should subscribe to some events. This can be the input event, or they can listen for pushes to an inbox
  2. Each chain should do something with the output. This can involving returning a final answer, or pushing to an inbox
In [8]:
# create topics
editor_inbox = Topic("editor_inbox")
reviser_inbox = Topic("reviser_inbox")
In [9]:
draft_chain = (
    # Listed in inputs
    Topic.IN.subscribe()
    | {"draft": drafter}
    # The draft always goes to the editors inbox
    | editor_inbox.publish()
)
In [10]:
editor_chain = (
    # Listen for events in the editors inbox
    editor_inbox.subscribe()
    | editor
    # Depending on the output, different things should happen
    | OpenAIFunctionsRouter(
        {
            # If revise is chosen, we send a push to the revisor's inbox
            "revise": (
                {
                    "notes": itemgetter("notes"),
                    "draft": editor_inbox.current() | itemgetter("draft"),
                    "question": Topic.IN.current() | itemgetter("question"),
                }
                | reviser_inbox.publish()
            ),
            # If accepted, then we return
            "accept": editor_inbox.current() | Topic.OUT.publish(),
        },
    )
)
In [11]:
reviser_chain = (
    # Listen for events in the reviser's inbox
    reviser_inbox.subscribe()
    | {"draft": reviser}
    # Publish to the editors inbox
    | editor_inbox.publish()
)
In [12]:
web_researcher = PubSub(
    processes=(draft_chain, editor_chain, reviser_chain),
    connection=InMemoryPubSubConnection(),
)
In [15]:
import langchain

langchain.verbose = True
In [16]:
web_researcher.invoke({"question": "What food do turtles eat?"})
Out [16]:
[]