defmodule LangChain.Tools.Calculator do @moduledoc """ Defines a Calculator tool for performing basic math calculations. This is an example of a pre-built `LangChain.Function` that is designed and configured for a specific purpose. This defines a function to expose to an LLM and provides an implementation for the `execute/2` function for evaluating when an LLM executes the function. When using the `Calculator` tool, you will either need to: * make repeated calls to run the chain as the tool is called and the results are then made available to the LLM before it returns the final result. * OR run the chain using the `while_needs_response: true` option like this: `LangChain.LLMChain.run(chain, while_needs_response: true)` ## Example The following is an example that uses a prompt where math is needed. What follows is the verbose log output. {:ok, updated_chain, %Message{} = message} = %{llm: ChatOpenAI.new!(%{temperature: 0}), verbose: true} |> LLMChain.new!() |> LLMChain.add_message( Message.new_user!("Answer the following math question: What is 100 + 300 - 200?") ) |> LLMChain.add_functions(Calculator.new!()) |> LLMChain.run(while_needs_response: true) Verbose log output: LLM: %LangChain.ChatModels.ChatOpenAI{ endpoint: "https://api.openai.com/v1/chat/completions", model: "gpt-3.5-turbo", temperature: 0.0, frequency_penalty: 0.0, receive_timeout: 60000, n: 1, stream: false } MESSAGES: [ %LangChain.Message{ content: "Answer the following math question: What is 100 + 300 - 200?", index: nil, status: :complete, role: :user, function_name: nil, arguments: nil } ] FUNCTIONS: [ %LangChain.Function{ name: "calculator", description: "Perform basic math calculations", function: #Function<0.108164323/2 in LangChain.Tools.Calculator.execute>, parameters_schema: %{ properties: %{ expression: %{ description: "A simple mathematical expression.", type: "string" } }, required: ["expression"], type: "object" } } ] SINGLE MESSAGE RESPONSE: %LangChain.Message{ content: nil, index: 0, status: :complete, role: :assistant, function_name: "calculator", arguments: %{"expression" => "100 + 300 - 200"} } EXECUTING FUNCTION: "calculator" FUNCTION RESULT: "200" SINGLE MESSAGE RESPONSE: %LangChain.Message{ content: "The answer to the math question \"What is 100 + 300 - 200?\" is 200.", index: 0, status: :complete, role: :assistant, function_name: nil, arguments: nil } """ require Logger alias LangChain.Function @doc """ Define the "calculator" function. Returns a success/failure response. """ @spec new() :: {:ok, Function.t()} | {:error, Ecto.Changeset.t()} def new() do Function.new(%{ name: "calculator", description: "Perform basic math calculations", parameters_schema: %{ type: "object", properties: %{ expression: %{type: "string", description: "A simple mathematical expression."} }, required: ["expression"] }, function: &execute/2 }) end @doc """ Define the "calculator" function. Raises an exception if function creation fails. """ @spec new!() :: Function.t() | no_return() def new!() do case new() do {:ok, function} -> function {:error, changeset} -> raise LangChain.LangChainError, changeset end end # @doc """ # Define the calculator tool using a JSON Schema. # """ # def define do # # JSON Schema definition of the function. The name, description, and the # # parameters it takes. # %{ # "name" => name(), # "description" => "Perform basic math calculations", # "parameters" => %{ # "type" => "object", # "properties" => %{ # "expression" => %{ # "type" => "string", # "description" => "A simple mathematical expression." # } # }, # "required" => ["expression"] # } # } # end @doc """ Performs the calculation specified in the expression and returns the response to be used by the the LLM. """ @spec execute(args :: %{String.t() => any()}, context :: map()) :: String.t() def execute(%{"expression" => expr} = _args, _context) do case Abacus.eval(expr) do {:ok, number} -> to_string(number) {:error, reason} -> Logger.warning( "Calculator tool errored in eval of #{inspect(expr)}. Reason: #{inspect(reason)}" ) "ERROR" end end end