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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews