Skip to content

Document how to set tenant for ash_ai #61

@laznic

Description

@laznic

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions