ash-project / ash-project/ash_ai

Document how to set tenant for ash_ai

Open Beginner friendly
#61 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Elixir
Stars
189
Forks
89
Avg merge
8h 32m
Merged PRs (30d)
3

Description

Is your feature request related to a problem? Please describe.
I'm using multitenancy in my app and while the default ash_ai.gen.chat works nicely, it fails when trying to use tools in resources which require a tenant. The current documentation only mentions how to set the actor but not the tenant.

Describe the solution you'd like
Have an example how to set up the tenant or list all the available options for setup_ash_ai in the documentation.

Describe alternatives you've considered
Automatically set the basic tenant passing in e.g. setup_ash_ai based on your current app setup however don't think it can be generalized or if it's worth doing even.

Additional context
Below are few things I needed to adjust in the generated files.

# respond.ex

# ... some code ...
      messages =
        HireUp.Chat.Message
        |> Ash.Query.filter(conversation_id == ^message.conversation_id)
        |> Ash.Query.filter(id != ^message.id)
        |> Ash.Query.select([:text, :source, :tool_calls, :tool_results])
        |> Ash.Query.sort(inserted_at: :desc)
+       |> Ash.Query.set_tenant(context.tenant)
        |> Ash.read!()
        |> Enum.concat([%{source: :user, text: message.text}])

# ..more code...

|> AshAi.setup_ash_ai(
        otp_app: :hire_up,
        tools: [:list_of_my_tools],
        actor: context.actor,
+       tenant: context.tenant
      )
      |> LLMChain.add_callback(%{
        on_llm_new_delta: fn _model, data ->
          if data.content && data.content != "" do
            HireUp.Chat.Message
            |> Ash.Changeset.for_create(
              :upsert_response,
              %{
                id: new_message_id,
                response_to_id: message.id,
                conversation_id: message.conversation_id,
                text: data.content,
                organization_id: message.organization_id
              },
              actor: %AshAi{},
+             tenant: context.tenant
            )
            |> Ash.create!()
          end
        end,
        on_message_processed: fn _chain, data ->
          if (data.tool_calls && Enum.any?(data.tool_calls)) ||
               (data.tool_results && Enum.any?(data.tool_results)) ||
               data.content not in [nil, ""] do
            HireUp.Chat.Message
            |> Ash.Changeset.for_create(
              :upsert_response,
              %{
                id: new_message_id,
                response_to_id: message.id,
                conversation_id: message.conversation_id,
                organization_id: message.organization_id,
                complete: true,
                tool_calls:
                  data.tool_calls &&
                    Enum.map(
                      data.tool_calls,
                      &Map.take(&1, [:status, :type, :call_id, :name, :arguments, :index])
                    ),
                tool_results:
                  data.tool_results &&
                    Enum.map(
                      data.tool_results,
                      &Map.take(&1, [
                        :type,
                        :tool_call_id,
                        :name,
                        :content,
                        :display_text,
                        :is_error,
                        :options
                      ])
                    ),
                text: data.content || ""
              },
              actor: %AshAi{},
+             tenant: context.tenant
            )
            |> Ash.create!()
          end
        end
      })
# chat_live.ex
# Just pointing out the mount function as there are multiple other queries like this which need the tenant info.
# So just following the general practice as you'd normally do with Ash.

  def mount(_params, _session, socket) do
    HireUpWeb.Endpoint.subscribe("chat:conversations:#{socket.assigns.current_user.id}")

    socket =
      socket
      |> assign(:page_title, "Chat")
      |> stream(
        :conversations,
        HireUp.Chat.my_conversations!(
          actor: socket.assigns.current_user,
+         tenant: socket.assigns.current_tenant
        )
      )
      |> assign(:messages, [])

    {:ok, socket}
  end

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked AshAi.setup_ash_ai/2 documentation and compare it with the tenant-related call sites shown in respond.ex and chat_live.ex. Document an example for setting the tenant and list the available setup options, including the related query and changeset context where shown. Done means a newcomer can find and follow the tenant setup guidance.

Written by the indexing model from the issue text.

Assessment

Tech stack
elixir
Domain
documentation
Issue type
Documentation
Difficulty
2/5
Estimated time
Half a day
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.