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

Essay Writer

notebooks/essay-writer.livemd

Essay Writer

Mix.install([
  {:magus, path: "#{__DIR__}/.."},
  {:kino, "~> 0.12.0"}
])

Application.put_env(:magus, :model_provider, "openai")
Application.put_env(:magus, :openai_key, System.fetch_env!("LB_OPENAI_KEY"))

Building a graph agent to write essays

defmodule EssayWriterState do
  defstruct [
    :topic,
    :latest_revision,
    :latest_feedback,
    num_of_revisions: 0
  ]
end
alias Magus.GraphAgent
alias Magus.AgentChain
alias LangChain.PromptTemplate

first_draft_template = ~S|
You are a writer who is working on a three-paragraph essay on the following topic: <%= @topic %>.
| |> PromptTemplate.from_template!()

writer_with_revision_template = ~S|
You are a writer who is working on a three-paragraph essay on the following topic: <%= @topic %>.
This is a previous revision of the essay:

  <%= @latest_revision %>

  On the latest revision, you received the following feedback:

  <%= @latest_feedback %>

  Write a new revision of the essay, incorporating the feedback where applicable. Begin immediately below:
| |> PromptTemplate.from_template!()

feedback_template = ~S|
You are a professor grading and providing feedback on an essay on the following topic: <%= @topic %>.

This is the essay:

<%= @latest_revision %>

Provide feedback on this essay below:
| |> PromptTemplate.from_template!()

write_first_draft_node = fn chain, state ->
  {:ok, content, _response} =
    chain
    |> AgentChain.add_message(PromptTemplate.to_message!(first_draft_template, state))
    |> AgentChain.run()

  %EssayWriterState{state | latest_revision: content, num_of_revisions: 1}
end

write_node = fn chain, state ->
  {:ok, content, _response} =
    chain
    |> AgentChain.add_message(PromptTemplate.to_message!(writer_with_revision_template, state))
    |> AgentChain.run()

  %EssayWriterState{
    state
    | latest_revision: content,
      num_of_revisions: state.num_of_revisions + 1
  }
end

feedback_node = fn chain, state ->
  {:ok, content, _response} =
    chain
    |> AgentChain.add_message(PromptTemplate.to_message!(feedback_template, state))
    |> AgentChain.run()

  %EssayWriterState{state | latest_feedback: content}
end

should_continue = fn %EssayWriterState{num_of_revisions: num_of_revisions} = _state ->
  case num_of_revisions > 2 do
    true -> :end
    false -> :provide_feedback
  end
end

agent =
  %GraphAgent{
    name: "Essay Writer",
    final_output_property: :latest_revision,
    initial_state: %EssayWriterState{}
  }
  |> GraphAgent.add_node(:first_draft, write_first_draft_node)
  |> GraphAgent.add_node(:write, write_node)
  |> GraphAgent.add_node(:provide_feedback, feedback_node)
  |> GraphAgent.set_entry_point(:first_draft)
  |> GraphAgent.add_edge(:first_draft, :provide_feedback)
  |> GraphAgent.add_edge(:provide_feedback, :write)
  |> GraphAgent.add_conditional_edges(:write, [:end, :provide_feedback], should_continue)
import Kino.Shorts

topic = read_text("New topic: ")

if topic == "" do
  Kino.interrupt!(:error, "You must enter a topic")
end

agent = %{
  agent
  | initial_state: %EssayWriterState{
      topic: topic
    }
}

Magus.AgentExecutorLite.run(agent) |> Kino.Tree.new()