324 lines
9.8 KiB
Go
324 lines
9.8 KiB
Go
package jsonparser
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
bifrost "github.com/maximhq/bifrost/core"
|
|
"github.com/maximhq/bifrost/core/schemas"
|
|
)
|
|
|
|
// BaseAccount implements the schemas.Account interface for testing purposes.
|
|
// It provides mock implementations of the required methods to test the JSON parser plugin
|
|
// with a basic OpenAI configuration.
|
|
type BaseAccount struct{}
|
|
|
|
// GetConfiguredProviders returns a list of supported providers for testing.
|
|
// Currently only supports OpenAI for simplicity in testing.
|
|
func (baseAccount *BaseAccount) GetConfiguredProviders() ([]schemas.ModelProvider, error) {
|
|
return []schemas.ModelProvider{schemas.OpenAI}, nil
|
|
}
|
|
|
|
// GetKeysForProvider returns a mock API key configuration for testing.
|
|
// Uses the OPENAI_API_KEY environment variable for authentication.
|
|
func (baseAccount *BaseAccount) GetKeysForProvider(ctx context.Context, providerKey schemas.ModelProvider) ([]schemas.Key, error) {
|
|
return []schemas.Key{
|
|
{
|
|
Value: *schemas.NewEnvVar("env.OPENAI_API_KEY"),
|
|
Models: []string{"gpt-4o-mini", "gpt-4-turbo"},
|
|
Weight: 1.0,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// GetConfigForProvider returns default provider configuration for testing.
|
|
// Uses standard network and concurrency settings.
|
|
func (baseAccount *BaseAccount) GetConfigForProvider(providerKey schemas.ModelProvider) (*schemas.ProviderConfig, error) {
|
|
return &schemas.ProviderConfig{
|
|
NetworkConfig: schemas.DefaultNetworkConfig,
|
|
ConcurrencyAndBufferSize: schemas.DefaultConcurrencyAndBufferSize,
|
|
}, nil
|
|
}
|
|
|
|
// TestJsonParserPluginEndToEnd tests the integration of the JSON parser plugin with Bifrost.
|
|
// It performs the following steps:
|
|
// 1. Initializes the JSON parser plugin with AllRequests usage
|
|
// 2. Sets up a test Bifrost instance with the plugin
|
|
// 3. Makes a test chat completion request with streaming enabled
|
|
// 4. Verifies that the plugin processes the streaming response correctly
|
|
//
|
|
// Required environment variables:
|
|
// - OPENAI_API_KEY: Your OpenAI API key for the test request
|
|
func TestJsonParserPluginEndToEnd(t *testing.T) {
|
|
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
|
|
// Check if OpenAI API key is set
|
|
if os.Getenv("OPENAI_API_KEY") == "" {
|
|
t.Skip("OPENAI_API_KEY is not set, skipping end-to-end test")
|
|
}
|
|
|
|
// Initialize the JSON parser plugin for all requests
|
|
plugin, err := Init(PluginConfig{
|
|
Usage: AllRequests,
|
|
CleanupInterval: 5 * time.Minute,
|
|
MaxAge: 30 * time.Minute,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error initializing JSON parser plugin: %v", err)
|
|
}
|
|
|
|
account := BaseAccount{}
|
|
|
|
// Initialize Bifrost with the plugin
|
|
client, err := bifrost.Init(ctx, schemas.BifrostConfig{
|
|
Account: &account,
|
|
LLMPlugins: []schemas.LLMPlugin{plugin},
|
|
Logger: bifrost.NewDefaultLogger(schemas.LogLevelDebug),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error initializing Bifrost: %v", err)
|
|
}
|
|
defer client.Shutdown()
|
|
|
|
// Make a test responses request with streaming enabled
|
|
// Request JSON output to test the parser
|
|
var responseFormat interface{} = map[string]interface{}{
|
|
"type": "json_object",
|
|
}
|
|
|
|
request := &schemas.BifrostChatRequest{
|
|
Provider: schemas.OpenAI,
|
|
Model: "gpt-4o-mini",
|
|
Input: []schemas.ChatMessage{
|
|
{
|
|
Role: schemas.ChatMessageRoleUser,
|
|
Content: &schemas.ChatMessageContent{
|
|
ContentStr: bifrost.Ptr("Return a JSON object with name, age, and city fields. Example: {\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}"),
|
|
},
|
|
},
|
|
},
|
|
Params: &schemas.ChatParameters{
|
|
ResponseFormat: &responseFormat,
|
|
},
|
|
}
|
|
// Make the streaming request
|
|
responseChan, bifrostErr := client.ChatCompletionStreamRequest(ctx, request)
|
|
|
|
if bifrostErr != nil {
|
|
t.Fatalf("Error in Bifrost request: %v", bifrostErr)
|
|
}
|
|
|
|
// Process streaming responses
|
|
if responseChan != nil {
|
|
t.Logf("Streaming response channel received")
|
|
|
|
// Read from the channel to see the streaming responses
|
|
responseCount := 0
|
|
|
|
for streamResponse := range responseChan {
|
|
responseCount++
|
|
|
|
if streamResponse.BifrostError != nil {
|
|
t.Logf("Streaming response error: %v", streamResponse.BifrostError)
|
|
}
|
|
|
|
if streamResponse.BifrostChatResponse != nil {
|
|
if streamResponse.BifrostChatResponse.Choices != nil {
|
|
for _, outputMsg := range streamResponse.BifrostChatResponse.Choices {
|
|
if outputMsg.ChatStreamResponseChoice != nil && outputMsg.ChatStreamResponseChoice.Delta.Content != nil {
|
|
content := *outputMsg.ChatStreamResponseChoice.Delta.Content
|
|
if content != "" {
|
|
t.Logf("Chunk %d: %s", responseCount, content)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
t.Logf("Stream completed after %d responses", responseCount)
|
|
} else {
|
|
t.Logf("No streaming response channel received")
|
|
}
|
|
|
|
t.Log("End-to-end test completed - check logs for JSON parsing behavior")
|
|
}
|
|
|
|
// TestJsonParserPluginPerRequest tests the per-request configuration of the JSON parser plugin.
|
|
// It tests how the plugin behaves when enabled via context for specific requests.
|
|
//
|
|
// Required environment variables:
|
|
// - OPENAI_API_KEY: Your OpenAI API key for the test request
|
|
func TestJsonParserPluginPerRequest(t *testing.T) {
|
|
ctx := context.Background()
|
|
// Check if OpenAI API key is set
|
|
if os.Getenv("OPENAI_API_KEY") == "" {
|
|
t.Skip("OPENAI_API_KEY is not set, skipping per-request test")
|
|
}
|
|
|
|
// Initialize the JSON parser plugin for per-request usage
|
|
plugin, err := Init(PluginConfig{
|
|
Usage: PerRequest,
|
|
CleanupInterval: 5 * time.Minute,
|
|
MaxAge: 30 * time.Minute,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error initializing JSON parser plugin: %v", err)
|
|
}
|
|
|
|
account := BaseAccount{}
|
|
|
|
// Initialize Bifrost with the plugin
|
|
client, err := bifrost.Init(ctx, schemas.BifrostConfig{
|
|
Account: &account,
|
|
LLMPlugins: []schemas.LLMPlugin{plugin},
|
|
Logger: bifrost.NewDefaultLogger(schemas.LogLevelDebug),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error initializing Bifrost: %v", err)
|
|
}
|
|
defer client.Shutdown()
|
|
|
|
// Test request with plugin enabled via context
|
|
var responseFormat interface{} = map[string]interface{}{
|
|
"type": "json_object",
|
|
}
|
|
|
|
request := &schemas.BifrostChatRequest{
|
|
Provider: schemas.OpenAI,
|
|
Model: "gpt-4o-mini",
|
|
Input: []schemas.ChatMessage{
|
|
{
|
|
Role: schemas.ChatMessageRoleUser,
|
|
Content: &schemas.ChatMessageContent{
|
|
ContentStr: bifrost.Ptr("Return a JSON object with name and age fields."),
|
|
},
|
|
},
|
|
},
|
|
Params: &schemas.ChatParameters{
|
|
ResponseFormat: &responseFormat,
|
|
},
|
|
}
|
|
|
|
// Create context with plugin enabled
|
|
newContext := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline).WithValue(EnableStreamingJSONParser, true)
|
|
|
|
// Make the streaming request
|
|
responseChan, bifrostErr := client.ChatCompletionStreamRequest(newContext, request)
|
|
|
|
if bifrostErr != nil {
|
|
t.Logf("Error in Bifrost request: %v", bifrostErr)
|
|
}
|
|
|
|
// Process streaming responses
|
|
if responseChan != nil {
|
|
t.Logf("Streaming response channel received for per-request test")
|
|
|
|
// Read from the channel to see the streaming responses
|
|
responseCount := 0
|
|
|
|
for streamResponse := range responseChan {
|
|
responseCount++
|
|
|
|
if streamResponse.BifrostError != nil {
|
|
t.Logf("Streaming response error: %v", streamResponse.BifrostError)
|
|
}
|
|
|
|
if streamResponse.BifrostChatResponse != nil {
|
|
for _, choice := range streamResponse.BifrostChatResponse.Choices {
|
|
if choice.ChatStreamResponseChoice != nil && choice.ChatStreamResponseChoice.Delta.Content != nil {
|
|
content := *choice.ChatStreamResponseChoice.Delta.Content
|
|
if content != "" {
|
|
t.Logf("Per-request chunk %d: %s", responseCount, content)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
t.Logf("Per-request stream completed after %d responses", responseCount)
|
|
} else {
|
|
t.Logf("No streaming response channel received for per-request test")
|
|
}
|
|
|
|
t.Log("Per-request test completed - check logs for JSON parsing behavior")
|
|
}
|
|
|
|
func TestParsePartialJSON(t *testing.T) {
|
|
plugin, err := Init(PluginConfig{
|
|
Usage: AllRequests,
|
|
CleanupInterval: 5 * time.Minute,
|
|
MaxAge: 30 * time.Minute,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error initializing JSON parser plugin: %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Already valid JSON object",
|
|
input: `{"name": "John", "age": 30}`,
|
|
expected: `{"name": "John", "age": 30}`,
|
|
},
|
|
{
|
|
name: "Partial JSON object missing closing brace",
|
|
input: `{"name": "John", "age": 30, "city": "New York"`,
|
|
expected: `{"name": "John", "age": 30, "city": "New York"}`,
|
|
},
|
|
{
|
|
name: "Partial JSON array missing closing bracket",
|
|
input: `["apple", "banana", "cherry"`,
|
|
expected: `["apple", "banana", "cherry"]`,
|
|
},
|
|
{
|
|
name: "Nested partial JSON",
|
|
input: `{"user": {"name": "John", "details": {"age": 30, "city": "NY"`,
|
|
expected: `{"user": {"name": "John", "details": {"age": 30, "city": "NY"}}}`,
|
|
},
|
|
{
|
|
name: "Partial JSON with string containing newline",
|
|
input: `{"message": "Hello\nWorld"`,
|
|
expected: `{"message": "Hello\nWorld"}`,
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
input: "",
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "Whitespace only",
|
|
input: " \n\t ",
|
|
expected: "{}",
|
|
},
|
|
{
|
|
name: "Non-JSON string",
|
|
input: "This is not JSON",
|
|
expected: "This is not JSON",
|
|
},
|
|
{
|
|
name: "Partial JSON with escaped quotes",
|
|
input: `{"message": "He said \"Hello\""`,
|
|
expected: `{"message": "He said \"Hello\""}`,
|
|
},
|
|
{
|
|
name: "Complex nested structure",
|
|
input: `{"data": {"users": [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"`,
|
|
expected: `{"data": {"users": [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]}}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := plugin.parsePartialJSON(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("parsePartialJSON(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|