Skip to content

Add support for multiple model APIs#23

Open
ParamThakkar123 wants to merge 7 commits intomainfrom
model_apis
Open

Add support for multiple model APIs#23
ParamThakkar123 wants to merge 7 commits intomainfrom
model_apis

Conversation

@ParamThakkar123
Copy link
Copy Markdown
Collaborator

Summary

This PR adds comprehensive support for multiple model APIs in HealthLLM.jl, enabling users to easily switch between different LLM providers for text generation and embeddings.

Key Changes

  • Provider Interface: Designed a common ModelProvider and EmbeddingProvider interface for consistent API usage.
  • Supported Providers: Implemented providers for HuggingFace, Groq, Gemini, OpenAI, Anthropic, and Ollama.
  • Configuration: API keys and settings configured via environment variables.
  • Error Handling: Added retry logic for API calls with exponential backoff for rate limits and timeouts.
  • Registry: Created a ModelRegistry for managing and switching providers dynamically.
  • Updated Modules: Modified Embedding, Query, and main modules to use the new provider system.
  • Demos: Added example scripts for OpenAI and Anthropic providers.
  • Tests: Included unit tests for providers and registry.
  • Documentation: Updated README with usage instructions.

Usage Example

using HealthLLM

# Create and use a provider
provider = OpenAIProvider()
response = generate(provider, "Hello, world!")

# Register for easy access
register_provider("my_openai", provider)

This enhancement significantly expands HealthLLM.jl's capabilities by supporting a wide range of LLM services.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a provider-based abstraction in HealthLLM.jl to support multiple LLM backends for text generation and embeddings, plus a small registry and new demos/tests to exercise provider construction and switching.

Changes:

  • Added Providers and Registry modules and wired them into the main HealthLLM module exports.
  • Updated query/index-building entry points to accept provider objects (with some backward-compat overloads).
  • Added RAG helpers (PromptTemplate, simple embedding/indexing, retrieval, and answer generation) plus demos and basic provider/registry tests.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/providers.jl Defines model + embedding provider abstractions and concrete providers (HF/Groq/Ollama/Gemini/OpenAI/Anthropic) with retry helper.
src/registry.jl Adds a simple global provider registry with register/get/list helpers.
src/rag.jl Adds simple RAG utilities (chunking, hashing embedder, retrieval, prompt templating, answer generation).
src/query.jl Refactors query generation to accept providers and adds a backward-compat overload.
src/embedding.jl Changes build_index_rag to require an EmbeddingProvider.
src/HealthLLM.jl Includes new modules and exports provider/registry/RAG APIs.
test/runtests.jl Runs new provider/registry tests.
test/providers_test.jl Adds basic tests for provider construction + registry.
demo/openai.jl Adds OpenAI demo script.
demo/anthropic.jl Adds Anthropic demo script.
demo/groq.jl Adds Groq demo script.
demo/gemini.jl Adds Gemini demo script.
README.md Documents provider usage and prompt templating.
Project.toml Adds HTTP dependency for direct API calls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/embedding.jl
Comment on lines +12 to +13
# Default to some embedder, but since RAGTools might expect different, perhaps error
error("Please provide an embedder::EmbeddingProvider")
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous build_index_rag(cfg, files; embedder_kwargs=...) method now unconditionally throws, which is a breaking change despite the “backward compatibility” comment. If you want to deprecate this overload, consider keeping the old behavior (or a reasonable default embedder) and emitting a deprecation warning, or introduce a new function name for the provider-based API.

Suggested change
# Default to some embedder, but since RAGTools might expect different, perhaps error
error("Please provide an embedder::EmbeddingProvider")
Base.depwarn(
"build_index_rag(cfg, files; embedder_kwargs=...) is deprecated; " *
"pass embedder::EmbeddingProvider explicitly to build_index_rag(cfg, files; embedder=..., embedder_kwargs=...)",
:build_index_rag,
)
return RAGTools.build_index(cfg, files; embedder_kwargs=embedder_kwargs)

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +24 to +32
Set your API keys in `.env` file or environment variables:

```bash
HUGGINGFACE_TOKEN=your_hf_token
GROQ_API_KEY=your_groq_key
GEMINI_API_KEY=your_gemini_key
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
```
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README instructs setting HUGGINGFACE_TOKEN, but the implementation reads ENV["HF_TOKEN"] for HuggingFace providers/embedders. This mismatch will lead to authentication failures when users follow the docs. Update the README to match the code’s env var name (or change the code to accept HUGGINGFACE_TOKEN).

Copilot uses AI. Check for mistakes.
Comment thread demo/openai.jl
Comment on lines +203 to +206
end

if !isinteractive()
main()
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demo calls main() under both if abspath(PROGRAM_FILE) == @__FILE__ and if !isinteractive(). When executed as a script, both conditions are typically true, so main() runs twice. Keep only one of these entry-point guards to avoid double execution.

Suggested change
end
if !isinteractive()
main()

Copilot uses AI. Check for mistakes.
Comment thread demo/gemini.jl
Comment on lines +69 to +72
end

if !isinteractive()
main()
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demo calls main() under both if abspath(PROGRAM_FILE) == @__FILE__ and if !isinteractive(). When executed as a script, both conditions are typically true, so main() runs twice. Keep only one of these entry-point guards to avoid double execution.

Suggested change
end
if !isinteractive()
main()

Copilot uses AI. Check for mistakes.
Comment thread src/providers.jl Outdated
Comment thread src/rag.jl
Comment on lines +78 to +84
chunks = String[]
chars = collect(text)
pos = 1
while pos <= length(chars)
endpos = min(pos + chunk_size - 1, length(chars))
push!(chunks, strip(join(chars[pos:endpos])))
pos = endpos + 1
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chunk_text accepts an overlap argument but never uses it when advancing pos, so there is no actual overlap between chunks. This makes the API misleading and reduces retrieval quality. Use overlap when computing the next pos (e.g., advance by chunk_size - overlap) or remove the parameter.

Suggested change
chunks = String[]
chars = collect(text)
pos = 1
while pos <= length(chars)
endpos = min(pos + chunk_size - 1, length(chars))
push!(chunks, strip(join(chars[pos:endpos])))
pos = endpos + 1
chunk_size > 0 || throw(ArgumentError("chunk_size must be positive"))
overlap >= 0 || throw(ArgumentError("overlap must be non-negative"))
overlap < chunk_size || throw(ArgumentError("overlap must be smaller than chunk_size"))
chunks = String[]
chars = collect(text)
pos = 1
step = chunk_size - overlap
while pos <= length(chars)
endpos = min(pos + chunk_size - 1, length(chars))
push!(chunks, strip(join(chars[pos:endpos])))
pos += step

Copilot uses AI. Check for mistakes.
Comment thread demo/groq.jl
Comment on lines +69 to +72
end

if !isinteractive()
main()
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demo calls main() under both if abspath(PROGRAM_FILE) == @__FILE__ and if !isinteractive(). When executed as a script, both conditions are typically true, so main() runs twice. Keep only one of these entry-point guards to avoid double execution.

Suggested change
end
if !isinteractive()
main()

Copilot uses AI. Check for mistakes.
Comment thread demo/anthropic.jl
Comment on lines +203 to +206
end

if !isinteractive()
main()
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demo calls main() under both if abspath(PROGRAM_FILE) == @__FILE__ and if !isinteractive(). When executed as a script, both conditions are typically true, so main() runs twice. Keep only one of these entry-point guards to avoid double execution.

Suggested change
end
if !isinteractive()
main()

Copilot uses AI. Check for mistakes.
Comment thread src/providers.jl
Comment on lines +23 to +35
function with_retry(f::Function, max_retries::Int=3, backoff::Float64=1.0)
for attempt in 1:max_retries
try
return f()
catch e
if attempt == max_retries
rethrow(e)
end
if isa(e, HTTP.ExceptionRequest.StatusError) && e.status in [429, 500, 502, 503, 504]
sleep(backoff * attempt)
else
rethrow(e)
end
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with_retry checks isa(e, HTTP.ExceptionRequest.StatusError), but HTTP.jl status exceptions are typically HTTP.Exceptions.StatusError. As written, this branch likely never matches, so rate-limit / 5xx responses won't be retried. Update the exception type check to the correct HTTP exception type (and keep the status filter) so retries actually occur.

Copilot uses AI. Check for mistakes.
Comment thread src/providers.jl
Comment on lines +8 to +20
export ModelProvider, EmbeddingProvider, HuggingFaceProvider, GroqProvider, OllamaProvider, GeminiProvider, OpenAIProvider, AnthropicProvider, HuggingFaceEmbedder

abstract type ModelProvider end
abstract type EmbeddingProvider end

# Abstract methods
function generate(provider::ModelProvider, prompt::String; kwargs...)
error("generate not implemented for $(typeof(provider))")
end

function embed(provider::EmbeddingProvider, texts::Vector{String}; kwargs...)
error("embed not implemented for $(typeof(provider))")
end
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate/embed are used unqualified in other modules (e.g., Query and RAG), but they are not exported from Providers. Either export generate and embed from this module or require callers to reference them as Providers.generate / Providers.embed; otherwise downstream modules will hit UndefVarError at load time.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants