feat: add DeepSeek, Moonshot, xAI, Groq, Ollama; drop v1; migrate TUI to v2
CI / Root Module (push) Failing after 30s
CI / Lint (push) Failing after 50s
CI / V2 Module (push) Successful in 2m14s

Five OpenAI-compatible providers join the library as first-class constructors
(llm.DeepSeek, llm.Moonshot, llm.XAI, llm.Groq, llm.Ollama). Their wire-level
implementation is shared via a new v2/openaicompat package which is the
extracted guts of the old v2/openai provider; each provider supplies its own
Rules value to declare per-model constraints (e.g., DeepSeek Reasoner rejects
tools and temperature, Moonshot/xAI accept images only on *-vision* models,
Groq rejects audio input). v2/openai itself becomes a thin wrapper that sets
RestrictTemperature for o-series and gpt-5 models.

A new provider registry (v2/registry.go) exposes llm.Providers() and drives
the TUI's provider picker so adding a provider in future is a single-file
change.

The TUI at cmd/llm was migrated from v1 to v2 and moved to v2/cmd/llm. With
nothing else depending on v1, the v1 code at the repo root (all .go files,
schema/, internal/, provider/, root go.mod/go.sum) is deleted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 13:34:39 +00:00
parent 9b91b2f794
commit 34119e5a00
58 changed files with 1921 additions and 4242 deletions
+114
View File
@@ -0,0 +1,114 @@
package main
import (
"context"
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"time"
llm "gitea.stevedudenhoeffer.com/steve/go-llm/v2"
)
// TimeParams is the parameter struct for the GetTime function.
type TimeParams struct{}
// GetTime returns the current time.
func GetTime(_ context.Context, _ TimeParams) (string, error) {
return time.Now().Format("Monday, January 2, 2006 3:04:05 PM MST"), nil
}
// CalcParams is the parameter struct for the Calculate function.
type CalcParams struct {
A float64 `json:"a" description:"First number"`
B float64 `json:"b" description:"Second number"`
Op string `json:"op" description:"Operation: add, subtract, multiply, divide, power, sqrt, mod"`
}
// Calculate performs basic math operations.
func Calculate(_ context.Context, params CalcParams) (string, error) {
var result float64
switch strings.ToLower(params.Op) {
case "add", "+":
result = params.A + params.B
case "subtract", "sub", "-":
result = params.A - params.B
case "multiply", "mul", "*":
result = params.A * params.B
case "divide", "div", "/":
if params.B == 0 {
return "", fmt.Errorf("division by zero")
}
result = params.A / params.B
case "power", "pow", "^":
result = math.Pow(params.A, params.B)
case "sqrt":
if params.A < 0 {
return "", fmt.Errorf("cannot take square root of negative number")
}
result = math.Sqrt(params.A)
case "mod", "%":
result = math.Mod(params.A, params.B)
default:
return "", fmt.Errorf("unknown operation: %s", params.Op)
}
return strconv.FormatFloat(result, 'f', -1, 64), nil
}
// WeatherParams is the parameter struct for the GetWeather function.
type WeatherParams struct {
Location string `json:"location" description:"City name or location"`
}
// GetWeather returns mock weather data (for demo purposes).
func GetWeather(_ context.Context, params WeatherParams) (string, error) {
weathers := []string{"sunny", "cloudy", "rainy", "partly cloudy", "windy"}
temps := []int{65, 72, 58, 80, 45}
idx := len(params.Location) % len(weathers)
out := map[string]any{
"location": params.Location,
"temperature": strconv.Itoa(temps[idx]) + "F",
"condition": weathers[idx],
"humidity": "45%",
"note": "This is mock data for demonstration purposes",
}
b, err := json.Marshal(out)
if err != nil {
return "", err
}
return string(b), nil
}
// RandomNumberParams is the parameter struct for the RandomNumber function.
type RandomNumberParams struct {
Min int `json:"min" description:"Minimum value (inclusive)"`
Max int `json:"max" description:"Maximum value (inclusive)"`
}
// RandomNumber generates a pseudo-random number (using current time nanoseconds).
func RandomNumber(_ context.Context, params RandomNumberParams) (string, error) {
if params.Min > params.Max {
return "", fmt.Errorf("min cannot be greater than max")
}
n := time.Now().UnixNano()
rangeSize := params.Max - params.Min + 1
result := params.Min + int(n%int64(rangeSize))
return strconv.Itoa(result), nil
}
// createDemoToolbox creates a toolbox with demo tools for testing.
func createDemoToolbox() *llm.ToolBox {
return llm.NewToolBox(
llm.Define[TimeParams]("get_time", "Get the current date and time", GetTime),
llm.Define[CalcParams]("calculate",
"Perform basic math operations (add, subtract, multiply, divide, power, sqrt, mod)",
Calculate),
llm.Define[WeatherParams]("get_weather",
"Get weather information for a location (demo data)", GetWeather),
llm.Define[RandomNumberParams]("random_number",
"Generate a random number between min and max", RandomNumber),
)
}