Skip to content

Commit

Permalink
update calculator tool (#132)
Browse files Browse the repository at this point in the history
* minor readme update

* updated the calculator tool
- docs
- tests
- how a tool is called returning success/fail
- exception handling
  • Loading branch information
brainlid authored Jun 6, 2024
1 parent 0643c16 commit 86cda47
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Elixir LangChain enables Elixir applications to integrate AI services and self-h

Currently supported AI services:
- OpenAI ChatGPT
- OpenAI DALL-e 2 - image generation
- Anthropic Claude
- Google AI - https://generativelanguage.googleapis.com
- Google Vertex AI - Gemini
Expand Down
34 changes: 20 additions & 14 deletions lib/tools/calculator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ defmodule LangChain.Tools.Calculator do
* 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)`
* OR run the chain using the `mode: :until_success` option like this:
`LangChain.LLMChain.run(chain, mode: :until_success)`
## Example
Expand All @@ -28,7 +28,7 @@ defmodule LangChain.Tools.Calculator do
Message.new_user!("Answer the following math question: What is 100 + 300 - 200?")
)
|> LLMChain.add_functions(Calculator.new!())
|> LLMChain.run(while_needs_response: true)
|> LLMChain.run(mode: :until_success)
Verbose log output:
Expand Down Expand Up @@ -165,18 +165,24 @@ defmodule LangChain.Tools.Calculator do
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()
@spec execute(args :: %{String.t() => any()}, context :: map()) ::
{:ok, String.t()} | {:error, 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"
try do
case Abacus.eval(expr) do
{:ok, number} ->
{:ok, to_string(number)}

{:error, reason} ->
Logger.warning(
"Calculator tool errored in eval of #{inspect(expr)}. Reason: #{inspect(reason)}"
)

{:error, "ERROR: #{inspect(expr)} is not a valid expression"}
end
rescue
err ->
{:error, "ERROR: An invalid expression raised the exception #{inspect(err)}"}
end
end
end
40 changes: 30 additions & 10 deletions test/tools/calculator_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule LangChain.Tools.CalculatorTest do

test "assigned function can be executed" do
{:ok, calc} = Calculator.new()
assert "3" == calc.function.(%{"expression" => "1 + 2"}, nil)
assert {:ok, "3"} == calc.function.(%{"expression" => "1 + 2"}, nil)
end
end

Expand All @@ -41,7 +41,7 @@ defmodule LangChain.Tools.CalculatorTest do

describe "execute/2" do
test "evaluates the expression returning the result" do
assert "14" == Calculator.execute(%{"expression" => "1 + 2 + 3 + (2 * 4)"}, nil)
assert {:ok, "14"} == Calculator.execute(%{"expression" => "1 + 2 + 3 + (2 * 4)"}, nil)
end

test "returns an error when evaluation fails" do
Expand All @@ -50,7 +50,16 @@ defmodule LangChain.Tools.CalculatorTest do
Calculator.execute(%{"expression" => "cow + dog"}, nil)
end)

assert "ERROR" == result
assert {:error, "ERROR: \"cow + dog\" is not a valid expression"} == result
end

test "handles when a partial expression is given" do
assert {:ok, "-200"} = Calculator.execute(%{"expression" => "- 200"}, nil)
end

test "handles when an invalid arithmetic expression is given" do
assert {:error, reason} = Calculator.execute(%{"expression" => "5 / 0"}, nil)
assert reason == "ERROR: \"5 / 0\" is not a valid expression"
end
end

Expand All @@ -59,20 +68,31 @@ defmodule LangChain.Tools.CalculatorTest do
test "performs repeated calls until complete with a live LLM" do
test_pid = self()

callback = fn %Message{} = msg ->
send(test_pid, {:callback_msg, msg})
end
llm_handler = %{
on_llm_new_message: fn _model, %Message{} = message ->
send(test_pid, {:callback_msg, message})
end
}

chain_handler = %{
on_tool_response_created: fn _chain, %Message{} = tool_message ->
send(test_pid, {:callback_tool_msg, tool_message})
end
}

model = ChatOpenAI.new!(%{seed: 0, temperature: 0, stream: false, callbacks: [llm_handler]})

{:ok, updated_chain, %Message{} = message} =
LLMChain.new!(%{
llm: ChatOpenAI.new!(%{seed: 0, temperature: 0, stream: false}),
verbose: true
llm: model,
verbose: true,
callbacks: [chain_handler]
})
|> LLMChain.add_message(
Message.new_user!("Answer the following math question: What is 100 + 300 - 200?")
)
|> LLMChain.add_tools(Calculator.new!())
|> LLMChain.run(while_needs_response: true, callback_fn: callback)
|> LLMChain.run(mode: :while_needs_response)

assert updated_chain.last_message == message
assert message.role == :assistant
Expand All @@ -86,7 +106,7 @@ defmodule LangChain.Tools.CalculatorTest do
assert [%ToolCall{name: "calculator", arguments: %{"expression" => _}}] = message.tool_calls

# the function result message
assert_received {:callback_msg, message}
assert_received {:callback_tool_msg, message}
assert message.role == :tool
assert [%ToolResult{content: "200"}] = message.tool_results

Expand Down

0 comments on commit 86cda47

Please sign in to comment.