Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Swarm_Ex - Instructor Integration

swarm_ex_instructor_integration.livemd

Swarm_Ex - Instructor Integration

Mix.install(
  [
    {:instructor, "~> 0.0.5"},
    {:openai_ex, "~> 0.8.4"},
    {:swarm_ex, git: "https://github.com/nrrso/swarm_ex.git", branch: "main"},
    {:jason, "~> 1.4"}
  ],
  config: [
    instructor: [
      adapter: Instructor.Adapters.OpenAI,
      openai: [api_key: System.fetch_env!("LB_OPENAI_API_KEY")]
    ]
  ]
)

Define Example Modules

defmodule StarWarsClassifier do
  use Ecto.Schema
  use Instructor.Validator

  @doc """
  ## Field Descriptions:
  - is_star_wars: Whether or not the message is related to star wars
  - reason: A short, less than 10 word rationalization for the classification.
  - score: A confidence score between 0.0 and 1.0 for the classification.
  """
  @primary_key false
  embedded_schema do
    field(:is_star_wars, :boolean)
    field(:reason, :string)
    field(:score, :float)
  end

  @impl true
  def validate_changeset(changeset) do
    changeset
    |> Ecto.Changeset.validate_number(:score,
      greater_than_or_equal_to: 0.0,
      less_than_or_equal_to: 1.0
    )
  end

  def is_star_wars?(text) do
    Instructor.chat_completion(
      model: "gpt-4o-mini",
      response_model: __MODULE__,
      max_retries: 3,
      messages: [
        %{
          role: "user",
          content: """
          Your purpose is to classify if an incoming message is related to star wars or not.
  
          Classify the following message: 
          ```
          #{text}
          ```
          """
        }
      ]
    )
  end
end
StarWarsClassifier.is_star_wars?("Christmas cookies are the best!")
StarWarsClassifier.is_star_wars?("I have a bad feeling about this.")
defmodule YodaReply do
  use Ecto.Schema
  use Instructor.Validator

  @doc """
  ## Field Descriptions:
  - quote: A fitting reply in the tone and style of Yoda.
  """
  @primary_key false
  embedded_schema do
    field(:reply, :string)
  end

  def yoda_reply?(text) do
    Instructor.chat_completion(
      model: "gpt-4o-mini",
      response_model: __MODULE__,
      max_retries: 3,
      messages: [
        %{
          role: "user",
          content: """
          Your purpose is to reply in the tone and style of Master Yoda.
  
          Reply to the following message: 
          ```
          #{text}
          ```
          """
        }
      ]
    )
  end
end
# Message Classification Tool
defmodule ClassifyMessageTool do
  @behaviour SwarmEx.Tool

  @impl true
  def execute(%{message: message}) do
    # For now, simple keyword matching
    {:ok, %{is_star_wars: _is_star_wars}} = StarWarsClassifier.is_star_wars?(message)
  end

  @impl true
  def validate(%{message: _}), do: :ok
  def validate(_), do: {:error, :invalid_args}

  @impl true
  def cleanup(_), do: :ok
end
# Star Wars Response Tool
defmodule StarWarsResponseTool do
  @behaviour SwarmEx.Tool

  @impl true
  def execute(%{message: message}) do
    {:ok, %{reply: _reply}} = YodaReply.yoda_reply?(message)
  end

  @impl true
  def validate(%{message: _}), do: :ok
  def validate(_), do: {:error, :invalid_args}

  @impl true
  def cleanup(_), do: :ok
end
defmodule TriageAgent do
  use SwarmEx.Agent

  @impl true
  def init(opts) do
    {:ok, opts}
  end

  @impl true
  def terminate(_reason, _state), do: :ok

  @impl true
  def handle_message(message, state) when is_binary(message) do
    # Directly execute the ClassifyMessageTool instead of using execute_tool
    case ClassifyMessageTool.execute(%{message: message}) do
      {:ok, %{is_star_wars: true}} ->
        Logger.info("Message classified as Star Wars related. Handing off to StarWarsAgent.")
        handoff_to_star_wars(message, state)
      
      {:ok, %{is_star_wars: false}} ->
        Logger.info("Message not Star Wars related. Providing general response.")
        {:ok, "Hi, how can I help?", state}
      
      {:error, reason} ->
        Logger.error("Classification failed: #{inspect(reason)}")
        {:error, "Sorry, I couldn't process your message."}
    end
  end

  @impl true
  def handle_tool(:respond, _args, state) do
    {:ok, state}
  end

  defp handoff_to_star_wars(message, state) do
    case state[:star_wars_agent] do
      nil -> 
        {:error, "Star Wars agent not configured"}
      agent_pid -> 
        case SwarmEx.send_message_to_pid(agent_pid, message) do
          {:ok, response} -> {:ok, response, state}
          error -> error
        end
    end
  end
end
# Star Wars Specialist Agent
defmodule StarWarsAgent do
  use SwarmEx.Agent

  @impl true
  def init(opts) do
    {:ok, opts}
  end

  @impl true
  def terminate(_reason, _state), do: :ok

  @impl true
  def handle_message(message, state) when is_binary(message) do
    case StarWarsResponseTool.execute(%{message: message}) do
      {:ok, %{reply: reply}} -> 
        Logger.info("Generated a response from Yoda.")
        {:ok, reply, state}
      {:error, reason} -> 
        Logger.error("Reply failed: #{inspect(reason)}")
        {:error, "Sorry, I couldn't reply to your message."}
    end
  end

  @impl true
  def handle_tool(:respond, _args, state) do
    {:ok, state}
  end
end

Instantiate Tools and Agents

# Create network and agents
{:ok, network} = SwarmEx.create_network()
# Register our tools
SwarmEx.register_tool(ClassifyMessageTool)
SwarmEx.register_tool(StarWarsResponseTool)
# Create the Star Wars agent first
{:ok, star_wars_agent} = SwarmEx.create_agent(network, StarWarsAgent, name: "star_wars_agent")
SwarmEx.Client.list_agents(network)
# Create the triage agent with reference to the Star Wars agent
{:ok, triage_agent} = SwarmEx.create_agent(
  network, 
  TriageAgent, 
  name: "triage_agent",
  star_wars_agent: star_wars_agent
)
SwarmEx.Client.list_agents(network)

Sending Message

# Test with non-Star Wars message
{:ok, regular_response} = SwarmEx.send_message(network, "triage_agent", "Hello, what's the weather like?")
IO.puts("Regular response: #{regular_response}")
# Test with Star Wars message
{:ok, star_wars_response} = SwarmEx.send_message(network, "triage_agent", "Tell me about the Jedi")
IO.puts("Star Wars response: #{star_wars_response}")