An implementation of LangChain in Haskell.
Please see README.md for examples and usage.
TypeChain
An attempt to recreate Langchain in Haskell.
This is currently more a proof-of-concept than an actual functioning library.
Basic Model Prediction
Currently, only the GPT-3.5 Turbo model has been implemented and is the only model that can be used. Below is an example of a simple program that asks the model what 1 + 1 is.
module Main where
import DotEnv
import TypeChain.ChatModels.Types
import TypeChain.ChatModels.OpenAI
askOnePlusOne :: TypeChain OpenAIChat Message
askOnePlusOne = predict "What is 1 + 1?"
main :: IO ()
main = do
env <- loadEnv defaultEnv
let Just key = env `getKey` "OPENAI_API_KEY"
model = initOpenAIChat { chatModel = GPT35Turbo, apiKey = key }
response <- evalStateT askOnePlusOne model
mapM_ print response
This provides the output:
assistant: 1 + 1 equals 2.
Model Prediction With Context
Let's say we want to ask our model what 1 + 1 is after setting a rule that 1 + 1 is 3. We can do this by passing in an initial system message when we create the model. Here is an example:
askOnePlusOne :: TypeChain OpenAIChat [Message]
askOnePlusOne = predict ("What is 1 + 1?" :: String)
main :: IO ()
main = do
env <- loadEnv defaultEnv
let Just key = env `getKey` "OPENAI_API_KEY"
model = initOpenAIChat { chatModel = GPT35Turbo, apiKey = key }
response <- evalStateT askOnePlusOne model
mapM_ print response
This provides the output:
assistant: According to the new rule, 1 + 1 equals 3.
Model Prediction with Multiple Models
Let's say we want to have some sort of interaction between models. Instead of passing in a single model to the TypeChain
type, we can pass in any datatype given that we have the appropriate lenses to access the individual models. In this example, we will use a tuple with the _1
and _2
lenses to represent the two different models.
import Control.Lens
-- Helper function to turn assistant messages into user messages
-- We do this so we don't confuse the model and make it think it's talking
-- to itself
toUserMessage :: Message -> Message
toUserMessage msg = msg { _role = User }
convo :: TypeChain (OpenAIChat, OpenAIChat) [Message]
convo = do
let prompt = "Why does 1 + 1 = 2?"
-- Add appropriate context to model 2 so it thinks it asked this
-- question.
_2 `memorizes` [Message Assistant prompt]
-- Ask model 1 why 1 + 1 = 2
-- We do this to start a conversation between the two models
response1 <- _1 `predicts` ("Why does 1 + 1 = 2?" :: String)
-- Feed model 1's response into model2
response2 <- _2 `predicts` map toUserMessage response1
return response2
main :: IO ()
main = do
env <- loadEnv DefaultEnv
let Just key = env `getEnv` "OPENAI_API_KEY"
model1 = initOpenAIChat { chatModel = GPT35Turbo, apiKey = key }
model2 = initOpenAIChat { chatModel = GPT35Turbo, apiKey = key }
-- Return only the second model and print the entire conversation log
(_, model2) <- execStateT convo (model1, model2)
mapM_ print (messages model2)
This produces the output:
assistant: Why does 1 + 1 = 2?
user: The equation 1 + 1 = 2 is a fundamental concept in mathematics and follows the principles of arithmetic. In the base-10 number system, 1 represents
a single unit, and when another single unit (1) is added to it, we get a total of two units. This is the basic principle of addition, where combining two
quantities or numbers gives us the sum or total of those quantities. Consequently, 1 + 1 equals 2.
assistant: The equation 1 + 1 = 2 is derived from the principles of mathematical logic and the base-10 number system. In this system, we assign the numera
l 1 to represent a single unit or quantity. When we add two quantities or units of 1 together, we combine them to get a total of two units.
This concept of addition is a fundamental principle in mathematics, and it follows the properties and rules of arithmetic. It holds true not just in the b
ase-10 number system, but in any other number system as well. The equation 1 + 1 = 2 is a universally accepted fact in mathematics and forms the basis for
further mathematical operations and calculations.
Prompt Templates
One of the features of langchain is the ability to write chat prompt templates for conversations with models. Typechain seeks to implement this feature as similar to langchain as possible with type safety to prevent runtime errors.
For example, consider the following langchain code:
template = "You are a helpful assistant that translates {from} to {to}."
human_template = "{text}"
chat_prompt = ChatPromptTemplate.from_messages([
("system", template)
("human", human_template)
])
messages = chat_prompt.format_messages(from='English', to='French', text='Hello, World!')
The same code can be implemented in Typechain:
{-# LANGUAGE TemplateHaskell #-}
import Typechain.ChatModels
makeTemplate "Translate" [ system "You are an assistant that translates {lang1} to {lang2}."
, user "{text}."
]
-- Fill in the template quick and easy
messages :: [Message]
messages = mkTranslate "English" "French" "Hello, World!"
-- Fill in the template explicitly.
messages' :: [Message]
messages' = toPrompt $ Translate { lang1 = "English", lang2 = "French", text = "Hello!"}
The makeTemplate
function generates code for a record type at compile time where each field represents a placeholder in the specified string. makeTemplate
will also create an instance for ToPrompt
, allowing you to use the toPrompt
function to convert the record type into a list of messages. And for smaller templates, makeTemplate
also generates a helper function (in this case mkTranslate
) that takes in all of the prompt parameters and returns a String.
So for the above example, the makeTemplate
function would expand to the following code:
data Translate = Translate { lang1 :: String, lang2 :: String, text :: String}
instance ToPrompt Translate where
toPrompt template = [ Message System $ "You are an assistant that translates" ++ lang1 template ++ " to " ++ lang2 template ++ "."
, Message User $ text template
]
mkTranslate :: String -> String -> String -> [Messages]
mkTranslate lang1 lang2 text = toPrompt $ Translate lang1 lang2 text