From c31231f319b73f83c422ac607278d76a9b7a27b8 Mon Sep 17 00:00:00 2001 From: John Wilger Date: Wed, 21 Feb 2024 17:23:44 -0800 Subject: [PATCH] Add RunFailed event The agent will now publish a RunFailed event when it detects that the status of an ongoing run has transitioned to "failed". The event includes OpenAI's code and message values verbatim. --- lib/gpt_agent.ex | 15 +++++++- lib/gpt_agent/events/run_failed.ex | 16 +++++++++ lib/gpt_agent/types.ex | 3 ++ mix.exs | 2 +- test/gpt_agent_test.exs | 56 ++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 lib/gpt_agent/events/run_failed.ex diff --git a/lib/gpt_agent.ex b/lib/gpt_agent.ex index aa2f670..8cd50af 100644 --- a/lib/gpt_agent.ex +++ b/lib/gpt_agent.ex @@ -16,6 +16,7 @@ defmodule GptAgent do alias GptAgent.Events.{ AssistantMessageAdded, RunCompleted, + RunFailed, RunStarted, ToolCallOutputRecorded, ToolCallRequested, @@ -429,7 +430,19 @@ defmodule GptAgent do log("Run ID #{inspect(id)} failed with status #{inspect(status)}", :warning) log("Response: #{inspect(response)}") log("State: #{inspect(state)}") - noreply(%{state | running?: false, run_id: nil}) + + state + |> Map.put(:running?, false) + |> publish_event( + RunFailed.new!( + id: id, + thread_id: state.thread_id, + assistant_id: state.assistant_id, + code: response |> Map.get("last_error", %{}) |> Map.get("code") || "unknown", + message: response |> Map.get("last_error", %{}) |> Map.get("message") || "unknown" + ) + ) + |> noreply() end defmodule Impl do diff --git a/lib/gpt_agent/events/run_failed.ex b/lib/gpt_agent/events/run_failed.ex new file mode 100644 index 0000000..f30ad68 --- /dev/null +++ b/lib/gpt_agent/events/run_failed.ex @@ -0,0 +1,16 @@ +defmodule GptAgent.Events.RunFailed do + @moduledoc """ + An OpenAI Assistants run was completed + """ + + use GptAgent.Types + alias GptAgent.Types + + typedstruct enforce: true do + field :id, Types.run_id() + field :thread_id, Types.thread_id() + field :assistant_id, Types.assistant_id() + field :code, Types.run_error_code() + field :message, Types.run_error_message() + end +end diff --git a/lib/gpt_agent/types.ex b/lib/gpt_agent/types.ex index cb13cda..81e23c4 100644 --- a/lib/gpt_agent/types.ex +++ b/lib/gpt_agent/types.ex @@ -18,6 +18,9 @@ defmodule GptAgent.Types do @type thread_id() :: nonblank_string() @type file_id() :: nonblank_string() + @type run_error_code() :: nonblank_string() + @type run_error_message() :: nonblank_string() + @type message_metadata() :: %{optional(String.t()) => Jason.Encoder.t()} precond message_metadata: &validate_message_metadata/1 diff --git a/mix.exs b/mix.exs index 85f242e..bb38115 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule GptAgent.MixProject do def project do [ app: :gpt_agent, - version: "9.0.1", + version: "9.1.0", elixir: "~> 1.16", start_permanent: Mix.env() == :prod, aliases: aliases(), diff --git a/test/gpt_agent_test.exs b/test/gpt_agent_test.exs index 86cc564..7c25e85 100644 --- a/test/gpt_agent_test.exs +++ b/test/gpt_agent_test.exs @@ -10,6 +10,7 @@ defmodule GptAgentTest do alias GptAgent.Events.{ AssistantMessageAdded, RunCompleted, + RunFailed, RunStarted, ToolCallOutputRecorded, ToolCallRequested, @@ -1106,6 +1107,61 @@ defmodule GptAgentTest do assert :ok = GptAgent.add_user_message(pid, Faker.Lorem.sentence()) assert_receive {^pid, %UserMessageAdded{}}, 5_000 end + + @tag capture_log: true + test "when the run is fails, sends the RunFailed event to the callback handler", %{ + bypass: bypass, + assistant_id: assistant_id, + thread_id: thread_id, + run_id: run_id + } do + {:ok, pid} = + GptAgent.connect(thread_id: thread_id, last_message_id: nil, assistant_id: assistant_id) + + Bypass.expect_once(bypass, "GET", "/v1/threads/#{thread_id}/runs/#{run_id}", fn conn -> + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.resp( + 200, + Jason.encode!(%{ + "id" => run_id, + "object" => "thread.run", + "created_at" => 1_699_075_072, + "assistant_id" => assistant_id, + "thread_id" => thread_id, + "status" => "failed", + "started_at" => 1_699_075_072, + "expires_at" => nil, + "cancelled_at" => nil, + "completed_at" => nil, + "failed_at" => 1_699_075_073, + "last_error" => %{ + "code" => "rate_limit_exceeded", + "message" => + "You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors." + }, + "model" => "gpt-4-1106-preview", + "instructions" => nil, + "tools" => [], + "file_ids" => [], + "metadata" => %{} + }) + ) + end) + + :ok = GptAgent.add_user_message(pid, "Hello") + + assert_receive {^pid, + %RunFailed{ + id: ^run_id, + thread_id: ^thread_id, + assistant_id: ^assistant_id, + code: "rate_limit_exceeded", + message: + "You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors." + }}, + 5_000 + end end describe "submit_tool_output/3" do