# TypeChain An attempt to recreate [Langchain](https://www.langchain.com/) 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. ```haskell 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: ```haskell 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. ```haskell 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: ```python 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: ```haskell {-# 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: ```haskell 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 ```