defmodule LlmEx do @moduledoc """ LlmEx is a flexible client for interacting with various LLM providers. This library provides a unified API for working with different LLM services, including local models via Ollama and cloud services like Anthropic. ## Getting Started Add LlmEx to your dependencies in mix.exs: ```elixir def deps do [ {:llm_ex, "~> 0.1.0"} ] end ``` Configure your provider API keys (if needed) in your config.exs: ```elixir config :llm_ex, anthropic_api_key: System.get_env("ANTHROPIC_API_KEY") ``` ## Usage Create a client module and specify the provider: ```elixir defmodule MyApp.LlmClient do use LlmEx.Client, provider: LlmEx.Providers.OllamaClient def get_knowledge do with_tool(MyTool) |> with_prompt("What do you know about Elixir?") |> stream() end end ``` Process the streaming response: ```elixir def handle_info({:llm_response, _id, content}, socket) do # Handle the response content {:noreply, socket} end ``` """ @doc """ Creates a new client with the specified provider. ## Parameters * `provider` - The LLM provider module to use * `opts` - Additional configuration options ## Examples iex> client = LlmEx.client(LlmEx.Providers.OllamaClient) iex> is_map(client) true iex> client.provider == LlmEx.Providers.OllamaClient true """ def client(provider, opts \\ []) do %{ provider: provider, tools: [], prompt: nil, system: "", messages: [], options: opts } end @doc """ Adds a tool to the client configuration. ## Parameters * `client` - The client configuration * `tool` - The tool to add ## Examples iex> client = LlmEx.client(LlmEx.Providers.OllamaClient) iex> result = LlmEx.with_tool(client, :test_tool) iex> result.tools [:test_tool] """ def with_tool(client, tool) do Map.update(client, :tools, [tool], fn tools -> [tool | tools] end) end @doc """ Sets the prompt for the client. ## Parameters * `client` - The client configuration * `prompt` - The prompt text ## Examples iex> client = LlmEx.client(LlmEx.Providers.OllamaClient) iex> result = LlmEx.with_prompt(client, "What is Elixir?") iex> result.prompt "What is Elixir?" """ def with_prompt(client, prompt) when is_binary(prompt) do Map.put(client, :prompt, prompt) end @doc """ Sets the system message for the client. ## Parameters * `client` - The client configuration * `system` - The system message text ## Examples iex> client = LlmEx.client(LlmEx.Providers.OllamaClient) iex> result = LlmEx.with_system(client, "You are a helpful assistant.") iex> result.system "You are a helpful assistant." """ def with_system(client, system) when is_binary(system) do Map.put(client, :system, system) end @doc """ Adds a message to the conversation history. ## Parameters * `client` - The client configuration * `message` - The message to add ## Examples iex> client = LlmEx.client(LlmEx.Providers.OllamaClient) iex> message = %LlmEx.Types.Message{role: :user, content: "Hello"} iex> result = LlmEx.with_message(client, message) iex> length(result.messages) 1 """ def with_message(client, message) do Map.update(client, :messages, [message], fn messages -> messages ++ [message] end) end @doc """ Streams the response from the LLM provider. ## Parameters * `client` - The client configuration ## Examples iex> client = LlmEx.client(LlmEx.Providers.OllamaClient) |> LlmEx.with_prompt("Hello") iex> # Function will call provider.stream_chat which returns :ok iex> # This is just a mock example for doctest iex> client.provider != nil true """ def stream(client) do alias LlmEx.Types.Message # Initialize message list if needed messages = if client.messages == [] and client.prompt do [%Message{role: :user, content: client.prompt}] else client.messages end # Start streaming with provided options and tools message_id = "msg_#{System.unique_integer() |> abs()}" opts = [ tools: client.tools, system: client.system ] ++ client.options provider = client.provider provider.stream_chat(messages, message_id, self(), opts) end end