AiFlow.Ollama.Chat (AiFlow v0.1.0)

View Source

Handles chat interactions with the Ollama API and manages conversation history.

This module allows sending messages to Ollama chat models and automatically maintains a persistent chat history. It supports various options for customizing requests and responses.

Features

  • Automatic chat history persistence (JSON file)
  • Conversation management by chat_id and user_id
  • Automatic model pulling when a model is not found
  • Support for standard and raising (!) function variants
  • Configurable response formatting with :short and :field options
  • Debug logging

Examples

# Simple chat interaction
{:ok, response} = AiFlow.Ollama.Chat.chat("Hello!", "my_chat", "user1")

# Get full API response
{:ok, full_response} = AiFlow.Ollama.Chat.chat("Hello!", "my_chat", "user1", short: false)

# Extract specific field from API response
{:ok, model_name} = AiFlow.Ollama.Chat.chat("Hello!", "my_chat", "user1", short: false, field: "model")

# Extract specific field from message object
{:ok, role} = AiFlow.Ollama.Chat.chat("Hello!", "my_chat", "user1", field: "role")

# Use a specific model
{:ok, response} = AiFlow.Ollama.Chat.chat("Bonjour!", "french_chat", "user1", model: "mistral")

# Enable debug logging
{:ok, response} = AiFlow.Ollama.Chat.chat("Debug me", "debug_chat", debug: true)

# View chat history
{:ok, history} = AiFlow.Ollama.Chat.show_chat_history("my_chat", "user1")

# Raise on error instead of returning {:error, ...}
response = AiFlow.Ollama.Chat.chat!("Hello!", "my_chat", "user1")

Summary

Functions

Sends a chat message to the Ollama API and stores the conversation in the chat history.

Same as chat/4, but raises a RuntimeError if the request fails instead of returning an error tuple.

Checks the integrity and format of the chat data file {It will be deprecated later =)}

Clears chat history based on provided options.

Clears chat history based on provided options.

Loads the raw chat data from the configured file and converts its keys to atoms for easier debugging.

Retrieves and returns the message history for a specific chat and user, intended for debugging purposes.

Retrieves all stored chat data.

Retrieves all stored chat data, raising on error.

Retrieves the chat history or list of chats for a specific user.

Retrieves the chat history or list of chats for a specific user, raising on error.

Functions

chat(prompt, chat_id, user_id \\ "default_user", opts \\ [])

@spec chat(String.t(), String.t(), String.t(), keyword()) ::
  {:ok, term()} | {:error, AiFlow.Ollama.Error.t()}

Sends a chat message to the Ollama API and stores the conversation in the chat history.

Parameters

  • prompt: The user's message (string).
  • chat_id: Unique identifier for the chat session (string).
  • user_id: Identifier for the user (string, defaults to "default_user").
  • opts: Keyword list of options:
    • :model (string): The Ollama model to use (defaults to config model).
    • :debug (boolean): If true, logs request/response details (defaults to false).
    • :short (boolean): Controls response format.
      • true (default): Returns the assistant's message content or the value of :field if specified.
      • false: Returns the full Ollama API response map.
    • :field (string): Specifies a field to extract from the response.
      • If short: true (default): Extracts the field from the message object (e.g., "role", "content"). If the field is not found in message, it attempts to find it at the top level of the response.
      • If short: false: Extracts the field from the top-level API response (e.g., "model", "total_duration").
    • Other options are passed to the Ollama API (e.g., temperature, max_tokens).

Returns

  • {:ok, response}: The formatted response based on :short and :field options.
  • {:error, Error.t()}: An error with a reason (e.g., API failure, invalid response format).

Examples

# Send a message and get the response content (default behavior)
iex> AiFlow.Ollama.Chat.chat("Hello!", "chat123", "usr1")
{:ok, "Hi there! How can I help you today?"}

# Send a message with debug logging
iex> AiFlow.Ollama.Chat.chat("What's the weather?", "chat123", "usr1", debug: true)
12:00:00.000 [debug] Sending request to http://localhost:11434/api/chat
12:00:00.000 [debug] Request body: %{"messages" => [%{"content" => "What's the weather?", "role" => "user"}], "model" => "llama3.1", "stream" => false}
12:00:00.000 [debug] Ollama chat response: Status=200, Body=%{"model" => "llama3.1", "message" => %{"role" => "assistant", "content" => "It's sunny!"}, "done" => true}
{:ok, "It's sunny!"}

# Get the full API response
iex> AiFlow.Ollama.Chat.chat("Hello!", "chat123", "usr1", short: false)
{:ok, %{"model" => "llama3.1", "message" => %{"role" => "assistant", "content" => "Hi there!"}, "done" => true, "total_duration" => 123456789}}

# Extract a specific field from the message object (default short: true)
iex> AiFlow.Ollama.Chat.chat("Hello!", "chat123", "usr1", field: {:body, "model"})
{:ok, "llama3.1"}

# Extract a specific field from the message object (default short: true)
iex> AiFlow.Ollama.Chat.chat("Hello!", "chat123", "usr1", field: {:body, ["message", "role"]})
{:ok, "assistant"}

# Handle an API error
iex> AiFlow.Ollama.Chat.chat("Hi", "chat123", "usr1")
{:error, %AiFlow.Ollama.Error{type: :unknown, reason: "Connection timeout"}}

chat!(prompt, chat_id, user_id \\ "default_user", opts \\ [])

@spec chat!(String.t(), String.t(), String.t(), keyword()) ::
  term() | AiFlow.Ollama.Error.t()

Same as chat/4, but raises a RuntimeError if the request fails instead of returning an error tuple.

Parameters

Returns

  • String.t(): Assistant Answer.
  • term(): The processed response based on :short and :field options.
  • Error.t(): An error struct if the request fails.

Raises

  • RuntimeError if the chat request fails or the chat data cannot be loaded/saved.

Examples

# Successful chat
iex> AiFlow.Ollama.Chat.chat!("Hello!", "chat123", "usr1")
"Hi there!"

# Raises on error
iex> AiFlow.Ollama.Chat.chat!("Hi", "chat123", "usr1")
** (RuntimeError) Chat request failed: %AiFlow.Ollama.Error{type: :unknown, reason: "Connection timeout"}

check_chat_file()

@spec check_chat_file() :: {:ok, map()} | {:error, AiFlow.Ollama.Error.t()}

Checks the integrity and format of the chat data file {It will be deprecated later =)}

Returns

  • {:ok, map()}: The parsed chat data if the file is valid.
  • {:error, Error.t()}: An error if the file is missing or malformed.

clear_chat_history(opts \\ [])

@spec clear_chat_history(keyword()) ::
  {:ok, :deleted} | {:error, AiFlow.Ollama.Error.t()}
@spec clear_chat_history(keyword()) ::
  {:ok, :deleted} | {:error, AiFlow.Ollama.Error.t()}

Clears chat history based on provided options.

Options

  • :confirm (boolean): Must be true to proceed with deletion (defaults to false).
  • :user_id (string): The user ID whose chats to delete (optional).
  • :chat_id (string): The specific chat ID to delete (requires :user, optional).

Returns

  • {:ok, :success}: Confirmation of deletion.
  • {:ok, :deleted}: If selected chat has already been deleted
  • {:error, Error.t()}: An error if deletion failed or confirmation was not given.

Examples

# Delete all chat without confirm: true
AiFlow.Ollama.Chat.clear_chat_history()
{:error, "Please confirm deletion of all chats by passing 'confirm: true' as an option."}

# Delete all chat with confirm: true
AiFlow.Ollama.Chat.clear_chat_history(confirm: true)
{:ok, :success}

# Delete user chat without chat_id
AiFlow.Ollama.Chat.clear_chat_history(user_id: "user1")
{:error, "Please confirm deletion of all chats for user 'user1' by passing 'confirm: true' as an option, or specify a chat ID with 'chat: chat_id' to delete a specific chat."}

# Delete user chat
AiFlow.Ollama.Chat.clear_chat_history(chat_id: "my_chat", user_id: "user1")
{:ok, :success}

# If the selected chat has already been deleted
AiFlow.Ollama.Chat.clear_chat_history(chat_id: "my_chat", user_id: "user1")
{:ok, :deleted}

clear_chat_history!(opts \\ [])

Clears chat history based on provided options.

Options

  • :confirm (boolean): Must be true to proceed with deletion (defaults to false).
  • :user_id (string): The user ID whose chats to delete (optional).
  • :chat_id (string): The specific chat ID to delete (requires :user, optional).

Returns

  • {:ok, :success}: Confirmation of deletion.
  • {:ok, :deleted}: If selected chat has already been deleted
  • {:error, Error.t()}: An error if deletion failed or confirmation was not given.

Examples

# Delete all chat without confirm: true
AiFlow.Ollama.Chat.clear_chat_history!()
"Please confirm deletion of all chats by passing 'confirm: true' as an option."

# Delete user chat
AiFlow.Ollama.Chat.clear_chat_history!(chat_id: "my_chat", user_id: "user1")
:success

# If the selected chat has already been deleted
AiFlow.Ollama.Chat.clear_chat_history!(chat_id: "my_chat", user_id: "user1")
:deleted

debug_load_chat_data()

@spec debug_load_chat_data() :: {:ok, map()} | {:error, AiFlow.Ollama.Error.t()}

Loads the raw chat data from the configured file and converts its keys to atoms for easier debugging.

This function is intended for debugging purposes. It reads the chat data file specified in the configuration, parses the JSON content, and then recursively converts all string keys in the resulting map to atom keys. This can make the data structure easier to inspect and work with in an interactive debugging session (e.g., in iex).

Returns

  • {:ok, map()}: A map representing the chat data with atom keys.
  • {:error, Error.t()}: An error reason if the file could not be read or parsed.

Examples

# In a successful case, loads and atomizes the chat data
iex> AiFlow.Ollama.Chat.debug_load_chat_data()
{:ok,
%{
  chats: %{
    "default_user" => %{
      "my_chat" => %{
        "created_at" => "2023-10-27T10:00:00Z",
        "messages" => [
          %{"content" => "Hello", "role" => "user", "timestamp" => "2023-10-27T10:00:01Z"},
          %{"content" => "Hi there!", "role" => "assistant", "timestamp" => "2023-10-27T10:00:02Z"}
        ],
        "model" => "llama3.1",
        "name" => "my_chat",
        "updated_at" => "2023-10-27T10:00:02Z"
      }
    }
  },
  created_at: "2023-10-27T10:00:00Z"
}}

# In an error case (e.g., file not found or invalid JSON)
iex> # Assuming the chat file is corrupted
iex> AiFlow.Ollama.Chat.debug_load_chat_data()
{:error, %Jason.DecodeError{data: "<<", position: 0, token: nil}}

debug_show_chat_history(chat_id, user_id \\ "default_user")

@spec debug_show_chat_history(String.t(), String.t()) :: list()

Retrieves and returns the message history for a specific chat and user, intended for debugging purposes.

This function directly fetches the list of messages associated with a given chat ID and user ID from the chat data file. It's a simplified way to inspect the raw message history without any additional processing or error wrapping, making it useful for debugging chat state or history issues.

Parameters

  • chat_id: The unique identifier of the chat session (string).
  • user_id: The identifier of the user (string, defaults to "default_user").

Returns

  • list(): A list of message maps (e.g., [ %{"role" => "user", "content" => "...", ...}, ...]) belonging to the specified chat and user. Returns an empty list [] if the chat/user is not found or if there's an error loading the chat data.

Examples

# Retrieve messages for an existing chat
iex> AiFlow.Ollama.Chat.debug_show_chat_history("my_chat", "user1")
[
  %{"role" => "user", "content" => "Hello!", "timestamp" => "2023-10-27T10:00:00Z"},
  %{"role" => "assistant", "content" => "Hi there!", "timestamp" => "2023-10-27T10:00:01Z"}
]

# Retrieve messages for a non-existent chat (returns empty list)
iex> AiFlow.Ollama.Chat.debug_show_chat_history("unknown_chat", "user1")
[]

# Retrieve messages using the default user ID
iex> AiFlow.Ollama.Chat.debug_show_chat_history("default_chat")
[%{"role" => "user", "content" => "Default user message", ...}]

# If there's an error loading the chat file (e.g., corrupt JSON), it logs the error and returns []
# (Assuming the chat file is corrupted)
iex> AiFlow.Ollama.Chat.debug_show_chat_history("any_chat")
# 12:00:00.000 [error] Failed to load chat data for debug: %Jason.DecodeError{...}
[]

show_all_chats()

@spec show_all_chats() :: {:ok, map()} | {:error, AiFlow.Ollama.Error.t()}

Retrieves all stored chat data.

Returns

  • {:ok, map()}: The entire chat data map with atom keys.
  • {:error, Error.t()}: An error if the chat data could not be loaded.

show_all_chats!()

@spec show_all_chats!() :: map() | AiFlow.Ollama.Error.t()

Retrieves all stored chat data, raising on error.

Returns

  • map(): The entire chat data map with atom keys.
  • Error.t(): An error if the chat data could not be loaded.

Raises

show_chat_history(opts \\ [])

@spec show_chat_history(keyword()) ::
  {:ok, term()} | {:error, AiFlow.Ollama.Error.t()}

Retrieves the chat history or list of chats for a specific user.

Parameters

  • opts: Keyword list of options:
    • user_id: The ID of the user (string).
    • chat_id: The ID of a specific chat to retrieve (string, optional).
    • :short (boolean): Controls the response format.
      • true (default): Returns a list of chat names/IDs for the user (if chat_id is nil), or the content (list of messages) of the specific chat (if chat_id is provided).
      • false: Returns the full map(s) of chat data. If chat_id is nil, returns a map of all user's chats. If chat_id is provided, returns the full map of that specific chat.

Returns

  • {:ok, term()}: The requested data based on user_id, chat_id, and :short option.
  • {:error, Error.t()}: An error if the chat data could not be loaded or chat is not found.

Examples

# Get all users in short: true version
iex> AiFlow.Ollama.show_chat_history()
{:ok, %{users: ["default_user"]}}


# Get all users in short: false version
iex> AiFlow.Ollama.show_chat_history()
{:ok, %{users: ["default_user"]}}
iex(41)> AiFlow.Ollama.show_chat_history(short: false)
{:ok, %{"chats" => %{"default_user" => %{...}}2

# Get list of chat names for a user (short: true by default)
iex> AiFlow.Ollama.Chat.show_chat_history(user_id: "user1")
{:ok, ["chat1", "chat2"]}

# Get full map of all chats for a user
iex> AiFlow.Ollama.Chat.show_chat_history(user_id: "user1", short: false)
{:ok, %{"chat1" => %{"name" => "chat1", "messages" => [...], ...}, "chat2" => %{...}}}

# Get messages list for a specific chat (short: true by default)
iex> AiFlow.Ollama.Chat.show_chat_history(user_id: "user1", chat_id: "chat1")
{:ok, [%{"role" => "user", "content" => "..."}, %{"role" => "assistant", "content" => "..."}]}

# Get full map for a specific chat
iex> AiFlow.Ollama.Chat.show_chat_history(user_id: "user1", chat_id: "chat1", short: false)
{:ok, %{"name" => "chat1", "messages" => [...], "created_at" => "...", ...}}

# Handle user not found or no chats
iex> AiFlow.Ollama.Chat.show_chat_history(user_id: "unknown_user")
{:ok, []}

# Handle specific chat not found
iex> AiFlow.Ollama.Chat.show_chat_history(user_id: "user1", chat_id: "unknown_chat")
{:error, :chat_not_found}

show_chat_history!(opts \\ [])

@spec show_chat_history!(keyword()) :: term() | AiFlow.Ollama.Error.t()

Retrieves the chat history or list of chats for a specific user, raising on error.

Parameters

  • opts: Keyword list of options:
    • user_id: The ID of the user (string).
    • chat_id: The ID of a specific chat to retrieve (string, optional).
    • :short (boolean): Controls the response format.
      • true (default): Returns a list of chat names/IDs for the user (if chat_id is nil), or the content (list of messages) of the specific chat (if chat_id is provided).
      • false: Returns the full map(s) of chat data. If chat_id is nil, returns a map of all user's chats. If chat_id is provided, returns the full map of that specific chat.

Returns

  • term(): The requested data based on user_id, chat_id, and :short option.
  • Error.t(): An error if the chat data could not be loaded or chat is not found.

Raises

  • RuntimeError if the chat data could not be loaded or chat is not found.

Examples

# Get list of chat names for a user
iex> AiFlow.Ollama.Chat.show_chat_history!(user_id: "user1")
["chat1", "chat2"]

# Get messages list for a specific chat
iex> AiFlow.Ollama.Chat.show_chat_history!(user_id: "user1", chat_id: "chat1")
[%{"role" => "user", "content" => "..."}, ...]

# Raises on error
iex> AiFlow.Ollama.Chat.show_chat_history!(user_id: "unknown_user", chat_id: "unknown_chat")
** (RuntimeError) Failed to show chat history: :chat_not_found