feat: Ollama adapter + chat rate limiter (30 req/hour)
Ollama adapter (internal/chat/ollama.go): - Implements model.LLM interface for ADK Go - Talks to Ollama's OpenAI-compatible API (/v1/chat/completions) - Full tool/function calling support (tested with Mistral Small 3.2) - Converts ADK types to OpenAI format (messages, tools, tool_calls) - Configurable via OLLAMA_HOST and OLLAMA_MODEL env vars Multi-provider handler: - MODEL_PROVIDER env: "gemini" (default) or "ollama" - Gemini: requires GOOGLE_API_KEY (pay-as-you-go recommended) - Ollama: connects to local or Tailscale-remote instance Rate limiter: - 30 requests/hour per IP on /api/chat endpoint - Uses existing middleware.NewRateLimiter pattern Tested: Ollama + Mistral Small 3.2 on M4 Pro 64GB — correct answers
This commit is contained in:
+60
-15
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/juanatsap/cv-site/internal/cache"
|
||||
|
||||
"google.golang.org/adk/agent"
|
||||
"google.golang.org/adk/model"
|
||||
"google.golang.org/adk/model/gemini"
|
||||
"google.golang.org/adk/runner"
|
||||
"google.golang.org/adk/session"
|
||||
@@ -26,26 +27,28 @@ type Handler struct {
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// NewHandler creates a chat handler. Returns a disabled handler if GOOGLE_API_KEY is not set.
|
||||
// NewHandler creates a chat handler. Returns a disabled handler if no model provider is configured.
|
||||
func NewHandler(dataCache *cache.DataCache) *Handler {
|
||||
apiKey := os.Getenv("GOOGLE_API_KEY")
|
||||
if apiKey == "" {
|
||||
log.Println("⚠️ GOOGLE_API_KEY not set — chat feature disabled")
|
||||
return &Handler{enabled: false}
|
||||
provider := os.Getenv("MODEL_PROVIDER")
|
||||
if provider == "" {
|
||||
provider = "gemini"
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
var llm model.LLM
|
||||
var providerLabel string
|
||||
|
||||
modelName := os.Getenv("MODEL_NAME")
|
||||
if modelName == "" {
|
||||
modelName = "gemini-2.5-flash"
|
||||
switch provider {
|
||||
case "ollama":
|
||||
llm, providerLabel = initOllamaProvider()
|
||||
default:
|
||||
var err error
|
||||
llm, providerLabel, err = initGeminiProvider()
|
||||
if err != nil {
|
||||
return &Handler{enabled: false}
|
||||
}
|
||||
}
|
||||
|
||||
llm, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{
|
||||
APIKey: apiKey,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to initialize Gemini model: %v — chat disabled", err)
|
||||
if llm == nil {
|
||||
return &Handler{enabled: false}
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ func NewHandler(dataCache *cache.DataCache) *Handler {
|
||||
return &Handler{enabled: false}
|
||||
}
|
||||
|
||||
log.Printf("💬 Chat agent enabled (model: %s)", modelName)
|
||||
log.Printf("💬 Chat agent enabled (%s)", providerLabel)
|
||||
|
||||
return &Handler{
|
||||
runner: r,
|
||||
@@ -77,6 +80,48 @@ func NewHandler(dataCache *cache.DataCache) *Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// initGeminiProvider initializes the Gemini LLM provider.
|
||||
func initGeminiProvider() (model.LLM, string, error) {
|
||||
apiKey := os.Getenv("GOOGLE_API_KEY")
|
||||
if apiKey == "" {
|
||||
log.Println("⚠️ GOOGLE_API_KEY not set — chat feature disabled")
|
||||
return nil, "", fmt.Errorf("no API key")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
modelName := os.Getenv("MODEL_NAME")
|
||||
if modelName == "" {
|
||||
modelName = "gemini-2.5-flash"
|
||||
}
|
||||
|
||||
llm, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{
|
||||
APIKey: apiKey,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to initialize Gemini model: %v — chat disabled", err)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return llm, fmt.Sprintf("gemini: %s", modelName), nil
|
||||
}
|
||||
|
||||
// initOllamaProvider initializes the Ollama LLM provider.
|
||||
func initOllamaProvider() (model.LLM, string) {
|
||||
host := os.Getenv("OLLAMA_HOST")
|
||||
if host == "" {
|
||||
host = "http://localhost:11434"
|
||||
}
|
||||
|
||||
modelName := os.Getenv("OLLAMA_MODEL")
|
||||
if modelName == "" {
|
||||
modelName = "mistral-small3.2"
|
||||
}
|
||||
|
||||
llm := NewOllamaModel(host, modelName)
|
||||
return llm, fmt.Sprintf("ollama: %s @ %s", modelName, host)
|
||||
}
|
||||
|
||||
// Enabled returns whether the chat feature is available.
|
||||
func (h *Handler) Enabled() bool {
|
||||
return h.enabled
|
||||
|
||||
Reference in New Issue
Block a user