openaicompat_test.go

  1package providertests
  2
  3import (
  4	"net/http"
  5	"os"
  6	"testing"
  7
  8	"charm.land/fantasy"
  9	"charm.land/fantasy/providers/openai"
 10	"charm.land/fantasy/providers/openaicompat"
 11	"charm.land/x/vcr"
 12	"github.com/stretchr/testify/require"
 13)
 14
 15func TestOpenAICompatibleCommon(t *testing.T) {
 16	testCommon(t, []builderPair{
 17		{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
 18		{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
 19		{"groq-kimi-k2", builderGroq, nil, nil},
 20		{"zai-glm-4-7-flash", builderZAIGLM47Flash, nil, nil},
 21		{"huggingface-kimi-k2", builderHuggingFace, nil, nil},
 22		{"llama-cpp-gpt-oss", builderLlamaCppGptOss, nil, nil},
 23		{"avian-kimi", builderAvianKimi, nil, nil},
 24	})
 25}
 26
 27func TestOpenAICompatObjectGeneration(t *testing.T) {
 28	testObjectGeneration(t, []builderPair{
 29		{"xai-grok-4-fast", builderXAIGrok4Fast, nil, nil},
 30		{"xai-grok-code-fast", builderXAIGrokCodeFast, nil, nil},
 31		{"zai-glm-4-7-flash", builderZAIGLM47Flash, nil, nil},
 32	})
 33}
 34
 35func TestOpenAICompatibleThinking(t *testing.T) {
 36	opts := fantasy.ProviderOptions{
 37		openaicompat.Name: &openaicompat.ProviderOptions{
 38			ReasoningEffort: openai.ReasoningEffortOption(openai.ReasoningEffortHigh),
 39		},
 40	}
 41	testThinking(t, []builderPair{
 42		{"xai-grok-3-mini", builderXAIGrok3Mini, opts, nil},
 43		{"zai-glm-4-7-flash", builderZAIGLM47Flash, opts, nil},
 44		{"llama-cpp-gpt-oss", builderLlamaCppGptOss, opts, nil},
 45		{"avian-kimi", builderAvianKimi, nil, nil},
 46	}, testOpenAICompatThinking)
 47}
 48
 49func testOpenAICompatThinking(t *testing.T, result *fantasy.AgentResult) {
 50	reasoningContentCount := 0
 51	for _, step := range result.Steps {
 52		for _, msg := range step.Messages {
 53			for _, content := range msg.Content {
 54				if content.GetType() == fantasy.ContentTypeReasoning {
 55					reasoningContentCount += 1
 56				}
 57			}
 58		}
 59	}
 60	require.Greater(t, reasoningContentCount, 0, "expected reasoning content, got none")
 61}
 62
 63func builderXAIGrokCodeFast(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 64	provider, err := openaicompat.New(
 65		openaicompat.WithBaseURL("https://api.x.ai/v1"),
 66		openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
 67		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 68	)
 69	if err != nil {
 70		return nil, err
 71	}
 72	return provider.LanguageModel(t.Context(), "grok-code-fast-1")
 73}
 74
 75func builderXAIGrok4Fast(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 76	provider, err := openaicompat.New(
 77		openaicompat.WithBaseURL("https://api.x.ai/v1"),
 78		openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
 79		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 80	)
 81	if err != nil {
 82		return nil, err
 83	}
 84	return provider.LanguageModel(t.Context(), "grok-4-fast")
 85}
 86
 87func builderXAIGrok3Mini(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
 88	provider, err := openaicompat.New(
 89		openaicompat.WithBaseURL("https://api.x.ai/v1"),
 90		openaicompat.WithAPIKey(os.Getenv("FANTASY_XAI_API_KEY")),
 91		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
 92	)
 93	if err != nil {
 94		return nil, err
 95	}
 96	return provider.LanguageModel(t.Context(), "grok-3-mini")
 97}
 98
 99func builderZAIGLM47Flash(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
100	provider, err := openaicompat.New(
101		openaicompat.WithBaseURL("https://api.z.ai/api/coding/paas/v4"),
102		openaicompat.WithAPIKey(os.Getenv("FANTASY_ZAI_API_KEY")),
103		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
104	)
105	if err != nil {
106		return nil, err
107	}
108	return provider.LanguageModel(t.Context(), "glm-4.7-flash")
109}
110
111func builderGroq(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
112	provider, err := openaicompat.New(
113		openaicompat.WithBaseURL("https://api.groq.com/openai/v1"),
114		openaicompat.WithAPIKey(os.Getenv("FANTASY_GROQ_API_KEY")),
115		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
116	)
117	if err != nil {
118		return nil, err
119	}
120	return provider.LanguageModel(t.Context(), "moonshotai/kimi-k2-instruct-0905")
121}
122
123func builderHuggingFace(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
124	provider, err := openaicompat.New(
125		openaicompat.WithBaseURL("https://router.huggingface.co/v1"),
126		openaicompat.WithAPIKey(os.Getenv("FANTASY_HUGGINGFACE_API_KEY")),
127		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
128	)
129	if err != nil {
130		return nil, err
131	}
132	return provider.LanguageModel(t.Context(), "moonshotai/Kimi-K2-Instruct-0905:groq")
133}
134
135func builderLlamaCppGptOss(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
136	provider, err := openaicompat.New(
137		openaicompat.WithBaseURL("http://localhost:8080/v1"),
138		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
139	)
140	if err != nil {
141		return nil, err
142	}
143	return provider.LanguageModel(t.Context(), "openai/gpt-oss-20b")
144}
145
146func builderAvianKimi(t *testing.T, r *vcr.Recorder) (fantasy.LanguageModel, error) {
147	provider, err := openaicompat.New(
148		openaicompat.WithBaseURL("https://api.avian.io/v1"),
149		openaicompat.WithAPIKey(os.Getenv("FANTASY_AVIAN_API_KEY")),
150		openaicompat.WithHTTPClient(&http.Client{Transport: r}),
151	)
152	if err != nil {
153		return nil, err
154	}
155	return provider.LanguageModel(t.Context(), "moonshotai/kimi-k2.5")
156}