From c86c7094b92b52c0e207db29366e0851ee2c7146 Mon Sep 17 00:00:00 2001 From: lebrunel <124721263+lebrunel@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:49:42 +0000 Subject: [PATCH] adds tests for handling responses with tool use. Supports #8 and #9 --- test/anthropix_test.exs | 57 +++++++++++++++++++++++++++++++++ test/support/mock.ex | 70 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/test/anthropix_test.exs b/test/anthropix_test.exs index a92d4aa..ed77883 100644 --- a/test/anthropix_test.exs +++ b/test/anthropix_test.exs @@ -71,6 +71,63 @@ defmodule AnthropixTest do assert get_in(last, ["usage", "output_tokens"]) == 34 end + test "generates a response with tool use" do + client = Mock.client(& Mock.respond(&1, :messages_tools)) + assert {:ok, res} = Anthropix.chat(client, [ + model: "claude-3-haiku-20240307", + messages: [ + %{role: "user", content: "What is the weather in London?"} + ], + tools: [ + %{ + name: "get_weather", + description: "Fetches the weather for the given location.", + input_schema: %{ + type: "object", + properties: %{ + location: %{type: "string", description: "Location name - town, city or area."} + }, + required: ["location"] + } + } + ] + ]) + + block = Enum.find(res["content"], & &1["type"] == "tool_use") + assert is_map(block) + assert get_in(block, ["name"]) == "get_weather" + assert get_in(block, ["input", "location"]) == "London" + end + + test "streams a response with tool use" do + client = Mock.client(& Mock.stream(&1, :messages_tools)) + assert {:ok, stream} = Anthropix.chat(client, [ + model: "claude-3-haiku-20240307", + messages: [ + %{role: "user", content: "What is the weather in London?"} + ], + tools: [ + %{ + name: "get_weather", + description: "Fetches the weather for the given location.", + input_schema: %{ + type: "object", + properties: %{ + location: %{type: "string", description: "Location name - town, city or area."} + }, + required: ["location"] + } + } + ], + stream: true + ]) + res = Enum.to_list(stream) + last = Enum.find(res, & &1["type"] == "message_delta") + assert is_list(res) + assert get_in(last, ["delta", "stop_reason"]) == "tool_use" + assert get_in(last, ["usage", "output_tokens"]) == 61 + end + test "returns error when model not found" do client = Mock.client(& Mock.respond(&1, 404)) assert {:error, %APIError{type: "not_found"}} = Anthropix.chat(client, [ diff --git a/test/support/mock.ex b/test/support/mock.ex index c64fbea..aed208f 100644 --- a/test/support/mock.ex +++ b/test/support/mock.ex @@ -19,6 +19,30 @@ defmodule Anthropix.Mock do "usage" => %{"input_tokens" => 18, "output_tokens" => 36} }, + :messages_tools => %{ + "content" => [ + %{"text" => "Okay, let me check the weather for London:", "type" => "text"}, + %{ + "id" => "toolu_01C8aPF8bZPusgu3LvVAf4Mo", + "input" => %{"location" => "London"}, + "name" => "get_weather", + "type" => "tool_use" + } + ], + "id" => "msg_01THGxy3R57vpDPKmqPmcALZ", + "model" => "claude-3-haiku-20240307", + "role" => "assistant", + "stop_reason" => "tool_use", + "stop_sequence" => nil, + "type" => "message", + "usage" => %{ + "cache_creation_input_tokens" => 0, + "cache_read_input_tokens" => 0, + "input_tokens" => 348, + "output_tokens" => 65 + } + }, + :batch_list => %{ "data" => [ %{ @@ -86,14 +110,14 @@ defmodule Anthropix.Mock do @stream_mocks %{ messages: [ %{"type" => "message_start", "message" => %{ - "content" => [], - "id" => "msg_01AgC6AM5riFWbgj1ZoSgD1b", - "model" => "claude-3-sonnet-20240229", - "role" => "assistant", - "stop_reason" => nil, - "stop_sequence" => nil, - "type" => "message", - "usage" => %{"input_tokens" => 18, "output_tokens" => 1} + "content" => [], + "id" => "msg_01AgC6AM5riFWbgj1ZoSgD1b", + "model" => "claude-3-sonnet-20240229", + "role" => "assistant", + "stop_reason" => nil, + "stop_sequence" => nil, + "type" => "message", + "usage" => %{"input_tokens" => 18, "output_tokens" => 1} }}, %{"type" => "content_block_start", "index" => 0, "content_block" => %{"text" => "", "type" => "text"}}, %{"type" => "content_block_delta", "index" => 0, "delta" => %{"text" => "Here", "type" => "text_delta"}}, @@ -125,6 +149,36 @@ defmodule Anthropix.Mock do %{"type" => "content_block_stop", "index" => 0}, %{"type" => "message_delta", "delta" => %{"stop_reason" => "end_turn", "stop_sequence" => nil}, "usage" => %{"output_tokens" => 34}}, %{"type" => "message_stop"} + ], + + messages_tools: [ + %{"type" => "message_start", "message" => %{ + "content" => [], + "id" => "msg_017cEgug4oBcJZ9BBpB4iFuS", + "model" => "claude-3-haiku-20240307", + "role" => "assistant", + "stop_reason" => nil, + "stop_sequence" => nil, + "type" => "message", + "usage" => %{"cache_creation_input_tokens" => 0, "cache_read_input_tokens" => 0, "input_tokens" => 348, "output_tokens" => 4} + }}, + %{"type" => "content_block_start", "index" => 0, "content_block" => %{"text" => "", "type" => "text"}}, + %{"type" => "content_block_delta", "index" => 0, "delta" => %{"text" => "Here is the weather", "type" => "text_delta"}}, + %{"type" => "content_block_delta", "index" => 0, "delta" => %{"text" => " for London:", "type" => "text_delta"}}, + %{"type" => "content_block_stop", "index" => 0}, + %{"type" => "content_block_start", "index" => 1, "content_block" => %{ + "id" => "toolu_01HkwGv3jQLx1fGJV8qfskQZ", + "input" => %{}, + "name" => "get_weather", + "type" => "tool_use" + }}, + %{"type" => "content_block_delta", "index" => 1, "delta" => %{"partial_json" => "", "type" => "input_json_delta"}}, + %{"type" => "content_block_delta", "index" => 1, "delta" => %{"partial_json" => "{\"location\"", "type" => "input_json_delta"}}, + %{"type" => "content_block_delta", "index" => 1, "delta" => %{"partial_json" => ": \"Lond", "type" => "input_json_delta"}}, + %{"type" => "content_block_delta", "index" => 1, "delta" => %{"partial_json" => "on\"}", "type" => "input_json_delta"}}, + %{"type" => "content_block_stop", "index" => 1}, + %{"type" => "message_delta", "delta" => %{"stop_reason" => "tool_use", "stop_sequence" => nil}, "usage" => %{"output_tokens" => 61}}, + %{"type" => "message_stop"} ] }