feat: add configurable AgentRouter AI provider
This commit is contained in:
@@ -105,9 +105,13 @@ func TestAvalAIClientUsesBoundedStatelessRequest(t *testing.T) {
|
||||
if request["store"] != false || request["parallel_tool_calls"] != false {
|
||||
t.Fatalf("unsafe request controls: %#v", request)
|
||||
}
|
||||
if request["max_output_tokens"] != float64(1600) {
|
||||
if request["max_output_tokens"] != float64(4096) {
|
||||
t.Fatalf("max_output_tokens = %#v", request["max_output_tokens"])
|
||||
}
|
||||
reasoning, ok := request["reasoning"].(map[string]any)
|
||||
if !ok || reasoning["effort"] != "medium" {
|
||||
t.Fatalf("reasoning = %#v", request["reasoning"])
|
||||
}
|
||||
tools, ok := request["tools"].([]any)
|
||||
if !ok || len(tools) != 4 {
|
||||
t.Fatalf("tools = %#v", request["tools"])
|
||||
@@ -118,24 +122,38 @@ func TestAvalAIClientUsesBoundedStatelessRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testTools struct{ calls int }
|
||||
type testTools struct {
|
||||
calls int
|
||||
evidenceID string
|
||||
}
|
||||
|
||||
func (t *testTools) Execute(_ context.Context, name string, _ json.RawMessage) (json.RawMessage, error) {
|
||||
func (t *testTools) Execute(_ context.Context, name string, arguments json.RawMessage) (json.RawMessage, error) {
|
||||
var args struct {
|
||||
EvidenceID string `json:"evidenceId"`
|
||||
}
|
||||
if err := json.Unmarshal(arguments, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.calls++
|
||||
t.evidenceID = args.EvidenceID
|
||||
return json.RawMessage(`{"state":"READY"}`), nil
|
||||
}
|
||||
|
||||
func TestAvalAIClientExecutesAllowlistedToolThenStopsAfterSecondTurn(t *testing.T) {
|
||||
func TestAvalAIClientExecutesSequentialToolsThenReturnsFinalCopy(t *testing.T) {
|
||||
t.Parallel()
|
||||
turn := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
turn++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if turn == 1 {
|
||||
_, _ = w.Write([]byte(`{"status":"completed","output":[{"type":"function_call","call_id":"call_1","name":"get_growth_context","arguments":"{\"evidenceId\":\"event_teacher\"}"}],"usage":{"input_tokens":2,"output_tokens":1}}`))
|
||||
switch turn {
|
||||
case 1:
|
||||
_, _ = w.Write([]byte(`{"status":"completed","output":[{"type":"function_call","call_id":"call_1","name":"get_growth_context","arguments":"{}"}],"usage":{"input_tokens":2,"output_tokens":1}}`))
|
||||
return
|
||||
case 2:
|
||||
_, _ = w.Write([]byte(`{"status":"completed","output":[{"type":"function_call","call_id":"call_2","name":"get_event_evidence","arguments":"{\"evidenceId\":\"event_teacher\"}"}],"usage":{"input_tokens":3,"output_tokens":2}}`))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"status":"completed","output":[{"type":"message","content":[{"type":"output_text","text":"{\"eventRecommendations\":[{\"evidenceId\":\"event_teacher\",\"priorityPercent\":40,\"stance\":\"PROMOTE\",\"relevanceReason\":\"این مناسبت با الگوی فروش کسبوکار همخوان است.\",\"insightBasis\":\"BUSINESS_PROFILE\"},{\"evidenceId\":\"event_yalda\",\"priorityPercent\":35,\"stance\":\"PREPARE\",\"relevanceReason\":\"این رویداد با کانال فروش شما مرتبط است.\",\"insightBasis\":\"BUSINESS_PROFILE\"},{\"evidenceId\":\"event_nowruz\",\"priorityPercent\":25,\"stance\":\"CAUTION\",\"relevanceReason\":\"این بازه برای برنامهریزی عرضه مناسب است.\",\"insightBasis\":\"BUSINESS_PROFILE\"}]}"}]}],"usage":{"input_tokens":3,"output_tokens":4}}`))
|
||||
_, _ = w.Write([]byte(`{"status":"completed","output":[{"type":"message","content":[{"type":"output_text","text":"{\"eventRecommendations\":[{\"evidenceId\":\"event_teacher\",\"priorityPercent\":40,\"stance\":\"PROMOTE\",\"relevanceReason\":\"این مناسبت با الگوی فروش کسبوکار همخوان است.\",\"insightBasis\":\"BUSINESS_PROFILE\"},{\"evidenceId\":\"event_yalda\",\"priorityPercent\":35,\"stance\":\"PREPARE\",\"relevanceReason\":\"این رویداد با کانال فروش شما مرتبط است.\",\"insightBasis\":\"BUSINESS_PROFILE\"},{\"evidenceId\":\"event_nowruz\",\"priorityPercent\":25,\"stance\":\"CAUTION\",\"relevanceReason\":\"این بازه برای برنامهریزی عرضه مناسب است.\",\"insightBasis\":\"BUSINESS_PROFILE\"}]}"}]}],"usage":{"input_tokens":5,"output_tokens":4}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
executor := &testTools{}
|
||||
@@ -144,7 +162,28 @@ func TestAvalAIClientExecutesAllowlistedToolThenStopsAfterSecondTurn(t *testing.
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if executor.calls != 1 || turn != 2 || usage.InputTokens != 5 || usage.OutputTokens != 5 {
|
||||
if executor.calls != 2 || executor.evidenceID != "event_teacher" || turn != 3 || usage.InputTokens != 10 || usage.OutputTokens != 7 {
|
||||
t.Fatalf("calls=%d evidenceID=%q turns=%d usage=%+v", executor.calls, executor.evidenceID, turn, usage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvalAIClientRejectsToolCallsBeyondBoundedLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
turn := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
turn++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = fmt.Fprintf(w, `{"status":"completed","output":[{"type":"function_call","call_id":"call_%d","name":"get_growth_context","arguments":"{}"}],"usage":{"input_tokens":1,"output_tokens":1}}`, turn)
|
||||
}))
|
||||
defer server.Close()
|
||||
executor := &testTools{}
|
||||
client := NewAvalAIClient(AvalAIConfig{BaseURL: server.URL, APIKey: "key", Model: "model", Timeout: time.Second}, server.Client())
|
||||
|
||||
_, usage, err := client.GenerateWithTools(context.Background(), json.RawMessage(`{}`), profileKnownEvidence(), executor)
|
||||
if err == nil || !strings.Contains(err.Error(), "tool call limit exceeded") {
|
||||
t.Fatalf("GenerateWithTools() error = %v", err)
|
||||
}
|
||||
if executor.calls != 4 || turn != 5 || usage.ProviderRequests != 5 {
|
||||
t.Fatalf("calls=%d turns=%d usage=%+v", executor.calls, turn, usage)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user