vkryukov

vkryukov

Hello,

I hope someone can clarify the semantics of calculation return value. The following calculation of a last response from an LLM that is stored in a relationship works:

  calculations do
    calculate :llm_response, :string do
      calculation fn llm_response, _context ->
        [llm_response] = Ash.load!(llm_response, run: [:messages])
        last_message = llm_response.run.messages |> Enum.at(-1)
        [last_message.content]
      end
    end
  end

It loads a nested relationship and extract the content of the last message. After the definitions above, calling

{:ok, run} = LLMRun |> Ash.get("27face7e-6443-43bd-bb69-e7529eedd20c", load: [:llm_response])

will return a run resource where run.llm_response is a string, as expected.

However, I don’t fully understand why I should wrap the content (which is a string) in a list, given that I defined the return type as a :string in calculate :llm_response, :string? (If I don’t wrap it in a list, I get (Protocol.UndefinedError) protocol Enumerable not implemented for type BitString.; I decided to experiment with wrapping the return value in a list and it worked, to my surprise).

I’m using AshSqlite.DataLayer (not sure if that matters or not).

Thank you!

Showing Posts 1 to 9

ahey

ahey

Calculations operate on a list of records for efficiency reasons. The calculate function is passed in a list of records and must return a list of calculated terms.

For example, if you were selecting 100 records, and needed to load some associated data from the database for each record, this could be done in a single load instead of 100.

zachdaniel

zachdaniel

Creator of Ash

Also worth pointing out that that only works correctly because you are using Ash.get and so only have a single record.

If you did a_list_of_records |> Ash.load(:llm_response) your [single_thing] = ... match would fail.

vkryukov

vkryukov OP

Thank you both, it’s very clear now!

vkryukov

vkryukov OP

Indeed, I changed my code (after refactoring some attribute names) to

  calculations do
    calculate :llm_response, :string do
      calculation fn runs, _context ->
        runs
        |> Ash.load!(chain: [:messages])
        |> Enum.map(fn run ->
          last_message = run.chain.messages |> List.last()
          last_message.content
        end)
      end
    end
  end

and now the above Ash.get as well as

LLMRun |> Ash.Query.for_read(:read) |> Ash.read!(load: :llm_response)

work as expected.

zachdaniel

zachdaniel

Creator of Ash

You can optimize this further:

  calculations do
    calculate :llm_response, :string do
      calculation fn runs, _context ->
        message_query =
          Message
          |> Ash.Query.sort(created_at: :desc)
          |> Ash.Query.limit(1)

        runs
        |> Ash.load!(chain: [messages: message_query])
        |> Enum.map(fn run ->
          last_message = run.chain.messages |> List.last()
          last_message.content
        end)
      end
    end
  end

Furthermore, if you break it out into a module-based calculation, you can take advantage of the more optimized dependency loading for calculations.

defmodule MyApp.MyDomain.MyResource.Calculations.LastRun do
  use Ash.Resource.Calculation

  def load(_, _, _) do
    message_query =
      Message
      |> Ash.Query.sort(created_at: :desc)
      |> Ash.Query.limit(1)

    [chain: [messages: message_query]]
  end

  def calculate(_, _, _) do
    Enum.map(runs, fn run ->
      last_message = run.chain.messages |> List.last()
      last_message.content
    end)
  end
end

Then you can do calculate :llm_response, :string, MyApp.MyDomain.MyResource.Calculations.LastRun

vkryukov

vkryukov OP

Great suggestion, thank you! Reading the documentation more closely, I see that anonymous lambdas are not recommended, and I should be really using module-based approach everywhere.

vkryukov

vkryukov OP

And while we are on this topic: I assume that I cannot use expressions here because of the need to refer to (nested) relationships?

zachdaniel

zachdaniel

Creator of Ash

In this case, actually, you can :slight_smile:

calculate :llm_response, :string, expr(first(chain.messages, field: :content, query: [sort: [created_at: :desc]]))

Something like that should work.

vkryukov

vkryukov OP

Wow! I keep getting impressed by Ash.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
widianto
I think I’ve found a small improvement I could contribute to <%= web_namespace %>.CoreComponents (installer/templates/phx_web/compo...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews