Powered by AppSignal & Oban Pro

Match Assistant PoC

match-assistant.livemd

Match Assistant PoC

Mix.install(
  [
    {:firmowid, path: Path.join(__DIR__, "../../../../.."), env: :dev},
    {:kino, "~> 0.13.0"}
  ],
  config_path: :firmowid,
  lockfile: :firmowid,
  start_applications: false
)

# Boot minimal prerequisites
Application.ensure_all_started(:ecto_sql)
Application.ensure_all_started(:kino)
Application.ensure_all_started(:hackney)

# Supervise required processes under Livebook
{:ok, _} = Kino.start_child({Firmowid.Repo, []})
{:ok, _} = Kino.start_child(Firmowid.Invoicing.Matching.Assistant.SessionConversation)

Conversation Setup

# NOTE: put_org_id has been removed. Use tenant: org_id on Ash calls instead.
org_id = "01948dfc-1367-7e4f-90e2-54b685167481"
invoice =
  Firmowid.CostInvoices.list_cost_invoices(
    ~D[2024-01-01],
    ~D[2025-02-10]
  )
  |> hd()

"#{invoice.seller_display_name} #{invoice.issue_date} #{invoice.total_amount}"
mobile_vikings_invoice_id = "01957157-c00d-7433-bd41-3d8b719610a4"
mobile_vikings_invoice = Firmowid.CostInvoices.get_cost_invoice!(mobile_vikings_invoice_id)

invoice = mobile_vikings_invoice

"#{invoice.seller_display_name} #{invoice.issue_date} #{invoice.total_amount}"
hotel_invoice_id = "0197e013-19ac-7fd9-af84-6dd0aa4d3c39"
hotel_invoice = Firmowid.CostInvoices.get_cost_invoice!(hotel_invoice_id)

invoice = hotel_invoice
"#{invoice.seller_display_name} #{invoice.issue_date} #{invoice.total_amount}"
alias Firmowid.Invoicing.Matching.Assistant
alias Firmowid.Invoicing.Matching.Assistant.SessionConversation

frame = Kino.Frame.new()
inputs = [message: Kino.Input.text("Message")]
form = Kino.Control.form(inputs, submit: "Send", reset_on_submit: [:message])

Kino.render(frame)
Kino.render(form)

conversation_id = Assistant.start_conversation(invoice)

render_chat = fn ->
  # Get the full conversation history (list of LlmComposer.Message structs)
  conversation = SessionConversation.get(conversation_id)

  # Render the conversation as Markdown, showing user/assistant turns
  markdown =
    conversation
    |> Enum.map(fn
      %{type: :user, content: text} -> "**You**: #{text}"
      %{type: :assistant, content: text} -> "**AI**: #{text}"
      %{type: :system, content: text} -> "# System: \n\n #{text}"
      %{type: :meta, content: text, metadata: metadata} -> "`meta`: #{inspect(metadata)}"
      %{type: :function_result, content: text} -> "**`fcall result`**: #{text}"
      %{type: type, content: text} -> "**#{to_string(type)}: #{text}"
    end)
    |> Enum.join("\n\n")

  Kino.Frame.render(frame, Kino.Markdown.new(markdown))
end

render_chat.()

for %{data: %{message: message}} <- Kino.Control.stream(form) do
  _ = Assistant.send_message(conversation_id, message)
  render_chat.()
end
  • mobile_vikings: to faktura zbiorcza za wszystkie transakcje z poprzedniego miesiąca
  • hotel: opłaciliśmy tę fakturę, przez booking