From b9223aa8fb1e2986d513519c51bbdb1c639cd111 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Fri, 10 Oct 2025 14:55:08 -0300 Subject: [PATCH] feat: add google vertex support --- anthropic/anthropic.go | 55 +++++- anthropic/google.go | 11 ++ go.mod | 23 ++- go.sum | 35 +++- google/README.md | 63 ++++++ google/auth.go | 13 ++ google/google.go | 58 +++++- providertests/.env.sample | 2 + providertests/common_test.go | 18 +- providertests/google_test.go | 34 +++- .../vertex-claude-3-7-sonnet/simple.yaml | 33 ++++ .../simple_streaming.yaml | 102 ++++++++++ .../vertex-claude-3-7-sonnet/tool.yaml | 63 ++++++ .../tool_streaming.yaml | 186 ++++++++++++++++++ .../vertex-gemini-2-5-flash/multi_tool.yaml | 154 +++++++++++++++ .../multi_tool_streaming.yaml | 65 ++++++ .../vertex-gemini-2-5-flash/simple.yaml | 70 +++++++ .../simple_streaming.yaml | 34 ++++ .../vertex-gemini-2-5-flash/tool.yaml | 142 +++++++++++++ .../tool_streaming.yaml | 65 ++++++ .../vertex-gemini-2-5-pro/multi_tool.yaml | 154 +++++++++++++++ .../multi_tool_streaming.yaml | 65 ++++++ .../vertex-gemini-2-5-pro/simple.yaml | 70 +++++++ .../simple_streaming.yaml | 34 ++++ .../vertex-gemini-2-5-pro/tool.yaml | 142 +++++++++++++ .../vertex-gemini-2-5-pro/tool_streaming.yaml | 65 ++++++ .../google-vertex-claude-sonnet.yaml | 3 + .../google-vertex-gemini-2.5-flash.yaml | 70 +++++++ .../google-vertex-gemini-2.5-pro.yaml | 70 +++++++ 29 files changed, 1868 insertions(+), 31 deletions(-) create mode 100644 anthropic/google.go create mode 100644 google/README.md create mode 100644 google/auth.go create mode 100644 providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple_streaming.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool.yaml create mode 100644 providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool_streaming.yaml create mode 100644 providertests/testdata/TestSimple/google-vertex-claude-sonnet.yaml create mode 100644 providertests/testdata/TestSimple/google-vertex-gemini-2.5-flash.yaml create mode 100644 providertests/testdata/TestSimple/google-vertex-gemini-2.5-pro.yaml diff --git a/anthropic/anthropic.go b/anthropic/anthropic.go index 8598db459f8f742a19e604d695e9ef1cec310cd1..508086c0ea66a210bfa6963de80f43b9c9d229b7 100644 --- a/anthropic/anthropic.go +++ b/anthropic/anthropic.go @@ -14,7 +14,9 @@ import ( "github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go/option" "github.com/anthropics/anthropic-sdk-go/packages/param" + "github.com/anthropics/anthropic-sdk-go/vertex" "github.com/charmbracelet/fantasy/ai" + "golang.org/x/oauth2/google" ) const ( @@ -28,6 +30,10 @@ type options struct { name string headers map[string]string client option.HTTPClient + + vertexProject string + vertexLocation string + skipGoogleAuth bool } type provider struct { @@ -61,6 +67,19 @@ func WithAPIKey(apiKey string) Option { } } +func WithVertex(project, location string) Option { + return func(o *options) { + o.vertexProject = project + o.vertexLocation = location + } +} + +func WithSkipGoogleAuth(skip bool) Option { + return func(o *options) { + o.skipGoogleAuth = skip + } +} + func WithName(name string) Option { return func(o *options) { o.name = name @@ -80,26 +99,46 @@ func WithHTTPClient(client option.HTTPClient) Option { } func (a *provider) LanguageModel(modelID string) (ai.LanguageModel, error) { - anthropicClientOptions := []option.RequestOption{} + clientOptions := make([]option.RequestOption, 0, 5+len(a.options.headers)) if a.options.apiKey != "" { - anthropicClientOptions = append(anthropicClientOptions, option.WithAPIKey(a.options.apiKey)) + clientOptions = append(clientOptions, option.WithAPIKey(a.options.apiKey)) } if a.options.baseURL != "" { - anthropicClientOptions = append(anthropicClientOptions, option.WithBaseURL(a.options.baseURL)) + clientOptions = append(clientOptions, option.WithBaseURL(a.options.baseURL)) } - for key, value := range a.options.headers { - anthropicClientOptions = append(anthropicClientOptions, option.WithHeader(key, value)) + clientOptions = append(clientOptions, option.WithHeader(key, value)) } - if a.options.client != nil { - anthropicClientOptions = append(anthropicClientOptions, option.WithHTTPClient(a.options.client)) + clientOptions = append(clientOptions, option.WithHTTPClient(a.options.client)) + } + if a.options.vertexProject != "" && a.options.vertexLocation != "" { + var credentials *google.Credentials + if a.options.skipGoogleAuth { + credentials = &google.Credentials{TokenSource: &googleDummyTokenSource{}} + } else { + var err error + credentials, err = google.FindDefaultCredentials(context.Background()) + if err != nil { + return nil, err + } + } + + clientOptions = append( + clientOptions, + vertex.WithCredentials( + context.Background(), + a.options.vertexLocation, + a.options.vertexProject, + credentials, + ), + ) } return languageModel{ modelID: modelID, provider: a.options.name, options: a.options, - client: anthropic.NewClient(anthropicClientOptions...), + client: anthropic.NewClient(clientOptions...), }, nil } diff --git a/anthropic/google.go b/anthropic/google.go new file mode 100644 index 0000000000000000000000000000000000000000..a4e4ece32947fbec0cd832e0e163c8265e191da1 --- /dev/null +++ b/anthropic/google.go @@ -0,0 +1,11 @@ +package anthropic + +import ( + "golang.org/x/oauth2" +) + +type googleDummyTokenSource struct{} + +func (googleDummyTokenSource) Token() (*oauth2.Token, error) { + return &oauth2.Token{AccessToken: "dummy-token"}, nil +} diff --git a/go.mod b/go.mod index 7a0a7fd9f32486faa5b6a86f2695fccc0e589433..f8b013a0b6368491ea09a45fe9a85c5eed723896 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/charmbracelet/fantasy go 1.24.5 require ( + cloud.google.com/go/auth v0.9.3 github.com/anthropics/anthropic-sdk-go v1.10.0 github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 github.com/charmbracelet/x/json v0.2.0 @@ -12,17 +13,21 @@ require ( github.com/openai/openai-go/v2 v2.3.0 github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v4 v4.0.0-rc.2 + golang.org/x/oauth2 v0.30.0 google.golang.org/genai v1.26.0 gopkg.in/dnaeon/go-vcr.v4 v4.0.6-0.20250923044825-7b4892dd3117 ) require ( cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/s2a-go v0.1.8 // indirect @@ -34,12 +39,28 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/crypto v0.40.0 // indirect golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/api v0.197.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +// NOTE(@andreynering): Temporarily pinning @fantasy branch with fixes: +// https://github.com/charmbracelet/anthropic-sdk-go/commits/fantasy/ +replace github.com/anthropics/anthropic-sdk-go => github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251010172108-7b952cdeeb9d + +// NOTE(@andreynering): Temporarily pinning @fantasy branch with fixes: +// https://github.com/charmbracelet/anthropic-sdk-go/commits/fantasy/ +replace google.golang.org/genai => github.com/charmbracelet/go-genai v0.0.0-20251009191514-c6fa9e37d847 diff --git a/go.sum b/go.sum index f73cb2995f10a854552a764c512cc7dfc58363d9..0395e7de5c3080227f03a6b3003fe0a112b14225 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= @@ -14,9 +16,11 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkY github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/anthropics/anthropic-sdk-go v1.10.0 h1:jDKQTfC0miIEj21eMmPrNSLKTNdNa3nHZOhd4wZz1cI= -github.com/anthropics/anthropic-sdk-go v1.10.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251010172108-7b952cdeeb9d h1:qP7F7r7aVY7AReYHHgkQ79weuUEZK7zXtDtSEydYb0w= +github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251010172108-7b952cdeeb9d/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/charmbracelet/go-genai v0.0.0-20251009191514-c6fa9e37d847 h1:Oyo6YZ59iygXWNUlRozIOFHO4WUG9cNFhiUYCTq4AnU= +github.com/charmbracelet/go-genai v0.0.0-20251009191514-c6fa9e37d847/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg= github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 h1:DTSZxdV9qQagD4iGcAt9RgaRBZtJl01bfKgdLzUzUPI= github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms= github.com/charmbracelet/x/json v0.2.0 h1:DqB+ZGx2h+Z+1s98HOuOyli+i97wsFQIxP2ZQANTPrQ= @@ -30,6 +34,13 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -48,6 +59,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -102,6 +115,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -121,6 +144,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -136,16 +161,18 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genai v1.26.0 h1:r4HGL54kFv/WCRMTAbZg05Ct+vXfhAbTRlXhFyBkEQo= -google.golang.org/genai v1.26.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= diff --git a/google/README.md b/google/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d7cc646c191e19e1204f699b7c3ba641ba6cbb2a --- /dev/null +++ b/google/README.md @@ -0,0 +1,63 @@ +# Google Provider + +This document describes how to get an API keys for Google Gemini and Vertex. + +## Gemini + +Simply navigate to [this page](https://aistudio.google.com/apikey) in the +Google AI Studio and create a new API key. + +## Vertex + +### Install `gcloud` + +Install the `gcloud` command line tool. Install via Homebrew, Nix, or download +it from [here](https://cloud.google.com/sdk/docs/install). + +```bash +# Homebrew +brew install --cask google-cloud-sdk + +# Nix +nix-env -iA nixpkgs.google-cloud-sdk +``` + +### Authenticate + +Then authenticate with your Google account: + +```bash +gcloud auth login +``` + +### Create And Setup Project + +Navigate here to create a new project if you haven't already: +https://console.cloud.google.com/projectcreate + +Alternatively, you can create a new project via the command line: + +```bash +gcloud projects create {YOUR_PROJECT_ID} --name="{YOUR_PROJECT_NAME}" +``` + +Set the project on your machine: + +```bash +gcloud config set project {YOUR_PROJECT_ID} +``` + +Enable the Vertex AI API: + +```bash +gcloud services enable aiplatform.googleapis.com +``` + +### Setup Env + +Finally, you need to run this command to ensure that libraries will be able to +find your credentials. + +```bash +gcloud auth application-default login +``` diff --git a/google/auth.go b/google/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..ab4d58825eeb9f80d50ab5150d0d2c597c08218a --- /dev/null +++ b/google/auth.go @@ -0,0 +1,13 @@ +package google + +import ( + "context" + + "cloud.google.com/go/auth" +) + +type dummyTokenProvider struct{} + +func (dummyTokenProvider) Token(ctx context.Context) (*auth.Token, error) { + return &auth.Token{Value: "dummy-token"}, nil +} diff --git a/google/google.go b/google/google.go index f82ca46bb1902b4f70bc6e83b3e0df343e9696af..2186c4974e332999b04f9e3e2800d4bd1465eceb 100644 --- a/google/google.go +++ b/google/google.go @@ -11,7 +11,9 @@ import ( "net/http" "strings" + "cloud.google.com/go/auth" "github.com/charmbracelet/fantasy/ai" + "github.com/charmbracelet/fantasy/anthropic" "github.com/charmbracelet/x/exp/slice" "github.com/google/uuid" "google.golang.org/genai" @@ -24,10 +26,14 @@ type provider struct { } type options struct { - apiKey string - name string - headers map[string]string - client *http.Client + apiKey string + name string + headers map[string]string + client *http.Client + backend genai.Backend + project string + location string + skipAuth bool } type Option = func(*options) @@ -47,9 +53,30 @@ func New(opts ...Option) ai.Provider { } } -func WithAPIKey(apiKey string) Option { +func WithGeminiAPIKey(apiKey string) Option { return func(o *options) { + o.backend = genai.BackendGeminiAPI o.apiKey = apiKey + o.project = "" + o.location = "" + } +} + +func WithVertex(project, location string) Option { + if project == "" || location == "" { + panic("project and location must be provided") + } + return func(o *options) { + o.backend = genai.BackendVertexAI + o.apiKey = "" + o.project = project + o.location = location + } +} + +func WithSkipAuth(skipAuth bool) Option { + return func(o *options) { + o.skipAuth = skipAuth } } @@ -92,10 +119,23 @@ type languageModel struct { // LanguageModel implements ai.Provider. func (g *provider) LanguageModel(modelID string) (ai.LanguageModel, error) { + if strings.Contains(modelID, "anthropic") || strings.Contains(modelID, "claude") { + return anthropic.New( + anthropic.WithVertex(g.options.project, g.options.location), + anthropic.WithHTTPClient(g.options.client), + anthropic.WithSkipGoogleAuth(g.options.skipAuth), + ).LanguageModel(modelID) + } + cc := &genai.ClientConfig{ - APIKey: g.options.apiKey, - Backend: genai.BackendGeminiAPI, HTTPClient: g.options.client, + Backend: g.options.backend, + APIKey: g.options.apiKey, + Project: g.options.project, + Location: g.options.location, + } + if g.options.skipAuth { + cc.Credentials = &auth.Credentials{TokenProvider: dummyTokenProvider{}} } client, err := genai.NewClient(context.Background(), cc) if err != nil { @@ -116,7 +156,7 @@ func (a languageModel) prepareParams(call ai.Call) (*genai.GenerateContentConfig if v, ok := call.ProviderOptions[Name]; ok { providerOptions, ok = v.(*ProviderOptions) if !ok { - return nil, nil, nil, ai.NewInvalidArgumentError("providerOptions", "anthropic provider options should be *anthropic.ProviderOptions", nil) + return nil, nil, nil, ai.NewInvalidArgumentError("providerOptions", "google provider options should be *google.ProviderOptions", nil) } } @@ -386,6 +426,8 @@ func toGooglePrompt(prompt ai.Prompt) (*genai.Content, []*genai.Content, []ai.Ca Parts: parts, }) } + default: + panic("unsupported message role: " + msg.Role) } } return systemInstructions, content, warnings diff --git a/providertests/.env.sample b/providertests/.env.sample index 7fced67ca899ac5031d9bb97680ee97eab8adaac..e94d94d9059e9c563979b0d214c31615a8895094 100644 --- a/providertests/.env.sample +++ b/providertests/.env.sample @@ -5,5 +5,7 @@ FANTASY_GEMINI_API_KEY= FANTASY_GROQ_API_KEY= FANTASY_OPENAI_API_KEY= FANTASY_OPENROUTER_API_KEY= +FANTASY_VERTEX_LOCATION=us-east5 +FANTASY_VERTEX_PROJECT=fantasy-playground-472418 FANTASY_XAI_API_KEY= FANTASY_ZAI_API_KEY= diff --git a/providertests/common_test.go b/providertests/common_test.go index f108a701f9c1e4e9950051c9ee4e1de5af9015c5..a37624436a7071df2700c6a3564aebdc4905d64c 100644 --- a/providertests/common_test.go +++ b/providertests/common_test.go @@ -2,16 +2,25 @@ package providertests import ( "context" + "os" "strconv" "strings" "testing" "github.com/charmbracelet/fantasy/ai" - _ "github.com/joho/godotenv/autoload" + "github.com/joho/godotenv" "github.com/stretchr/testify/require" "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) +func init() { + if _, err := os.Stat(".env"); err == nil { + godotenv.Load(".env") + } else { + godotenv.Load(".env.sample") + } +} + type testModel struct { name string model string @@ -94,7 +103,7 @@ func testTool(t *testing.T, pair builderPair) { }, ) checkResult := func(t *testing.T, result *ai.AgentResult) { - require.Len(t, result.Steps, 2) + require.GreaterOrEqual(t, len(result.Steps), 2) var toolCalls []ai.ToolCallContent for _, content := range result.Steps[0].Content { @@ -155,10 +164,13 @@ func testTool(t *testing.T, pair builderPair) { } func testMultiTool(t *testing.T, pair builderPair) { - // Apparently, Azure does not support multi-tools calls at all? + // Apparently, Azure and Vertex+Anthropic do not support multi-tools calls at all? if strings.Contains(pair.name, "azure") { t.Skip("skipping multi-tool tests for azure as it does not support parallel multi-tool calls") } + if strings.Contains(pair.name, "vertex") && strings.Contains(pair.name, "claude") { + t.Skip("skipping multi-tool tests for vertex claude as it does not support parallel multi-tool calls") + } type CalculatorInput struct { A int `json:"a" description:"first number"` diff --git a/providertests/google_test.go b/providertests/google_test.go index 98ce06565bf54555de50e717252b45679f73efe4..e7a88664622bcbf2eed0b01081cca7cd5832f9cd 100644 --- a/providertests/google_test.go +++ b/providertests/google_test.go @@ -12,15 +12,24 @@ import ( "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) -var googleTestModels = []testModel{ +var geminiTestModels = []testModel{ {"gemini-2.5-flash", "gemini-2.5-flash", true}, {"gemini-2.5-pro", "gemini-2.5-pro", true}, } +var vertexTestModels = []testModel{ + {"vertex-gemini-2-5-flash", "gemini-2.5-flash", true}, + {"vertex-gemini-2-5-pro", "gemini-2.5-pro", true}, + {"vertex-claude-3-7-sonnet", "claude-3-7-sonnet@20250219", true}, +} + func TestGoogleCommon(t *testing.T) { var pairs []builderPair - for _, m := range googleTestModels { - pairs = append(pairs, builderPair{m.name, googleBuilder(m.model), nil}) + for _, m := range geminiTestModels { + pairs = append(pairs, builderPair{m.name, geminiBuilder(m.model), nil}) + } + for _, m := range vertexTestModels { + pairs = append(pairs, builderPair{m.name, vertexBuilder(m.model), nil}) } testCommon(t, pairs) } @@ -36,11 +45,11 @@ func TestGoogleThinking(t *testing.T) { } var pairs []builderPair - for _, m := range googleTestModels { + for _, m := range geminiTestModels { if !m.reasoning { continue } - pairs = append(pairs, builderPair{m.name, googleBuilder(m.model), opts}) + pairs = append(pairs, builderPair{m.name, geminiBuilder(m.model), opts}) } testThinking(t, pairs, testGoogleThinking) } @@ -60,11 +69,22 @@ func testGoogleThinking(t *testing.T, result *ai.AgentResult) { require.Greater(t, reasoningContentCount, 0) } -func googleBuilder(model string) builderFunc { +func geminiBuilder(model string) builderFunc { + return func(r *recorder.Recorder) (ai.LanguageModel, error) { + provider := google.New( + google.WithGeminiAPIKey(cmp.Or(os.Getenv("FANTASY_GEMINI_API_KEY"), "(missing)")), + google.WithHTTPClient(&http.Client{Transport: r}), + ) + return provider.LanguageModel(model) + } +} + +func vertexBuilder(model string) builderFunc { return func(r *recorder.Recorder) (ai.LanguageModel, error) { provider := google.New( - google.WithAPIKey(cmp.Or(os.Getenv("FANTASY_GEMINI_API_KEY"), "(missing)")), + google.WithVertex(os.Getenv("FANTASY_VERTEX_PROJECT"), os.Getenv("FANTASY_VERTEX_LOCATION")), google.WithHTTPClient(&http.Client{Transport: r}), + google.WithSkipAuth(true), ) return provider.LanguageModel(model) } diff --git a/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple.yaml b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple.yaml new file mode 100644 index 0000000000000000000000000000000000000000..12857166aec4c66b453d26cd11d0f060e1567812 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple.yaml @@ -0,0 +1,33 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 210 + host: "" + body: '{"max_tokens":4000,"messages":[{"content":[{"text":"Say hi in Portuguese","type":"text"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"anthropic_version":"vertex-2023-10-16"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Anthropic/Go 1.13.0 + url: https://us-east5-aiplatform.googleapis.com/v1/projects/fantasy-playground-472418/locations/us-east5/publishers/anthropic/models/claude-3-7-sonnet@20250219:rawPredict + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: '{"id":"msg_vrtx_01JoBtryoxdRxCJ4GfXam6Yu","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"Olá! That''s \"hi\" in Portuguese. I can also say \"Oi\" which is a more casual greeting in Portuguese."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":16,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":33}}' + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 1.987505375s diff --git a/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..659cc5881c4092df9324beab9cad1c2fe55dd399 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/simple_streaming.yaml @@ -0,0 +1,102 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 224 + host: "" + body: '{"max_tokens":4000,"messages":[{"content":[{"text":"Say hi in Portuguese","type":"text"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"stream":true,"anthropic_version":"vertex-2023-10-16"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Anthropic/Go 1.13.0 + url: https://us-east5-aiplatform.googleapis.com/v1/projects/fantasy-playground-472418/locations/us-east5/publishers/anthropic/models/claude-3-7-sonnet@20250219:streamRawPredict + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: |+ + event: message_start + data: {"type":"message_start","message":{"id":"msg_vrtx_01551jxgCbyerbL44qCuV9kn","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":16,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":5}} } + + event: ping + data: {"type": "ping"} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Olá!"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\n\n\""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Olá\" is"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Portuguese way"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" to say"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" \"hi"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"\" or \"hello.\""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" It's a friendly"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" greeting"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" use"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d in both"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Portugal"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" and Brazil,"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" as"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" well as other"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Portuguese-speaking countries."} } + + event: content_block_stop + data: {"type":"content_block_stop","index":0} + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":47} } + + event: message_stop + data: {"type":"message_stop" } + + headers: + Content-Type: + - text/event-stream; charset=utf-8 + status: 200 OK + code: 200 + duration: 1.902656792s diff --git a/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool.yaml b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool.yaml new file mode 100644 index 0000000000000000000000000000000000000000..edeafd4c8dd701676a96f0b4a8a9bc6915c2aa92 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool.yaml @@ -0,0 +1,63 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 501 + host: "" + body: '{"max_tokens":4000,"messages":[{"content":[{"text":"What''s the weather in Florence,Italy?","type":"text"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"tool_choice":{"disable_parallel_tool_use":false,"type":"auto"},"tools":[{"input_schema":{"properties":{"location":{"description":"the city","type":"string"}},"required":["location"],"type":"object"},"name":"weather","description":"Get weather information for a location"}],"anthropic_version":"vertex-2023-10-16"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Anthropic/Go 1.13.0 + url: https://us-east5-aiplatform.googleapis.com/v1/projects/fantasy-playground-472418/locations/us-east5/publishers/anthropic/models/claude-3-7-sonnet@20250219:rawPredict + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: '{"id":"msg_vrtx_012JnDNruXUPVCywN7jYpwZ8","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"I''ll check the current weather in Florence, Italy for you."},{"type":"tool_use","id":"toolu_vrtx_0129YeW9ohuKMKgnDRAStbvo","name":"weather","input":{"location":"Florence,Italy"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":394,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":67}}' + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 1.557585167s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 881 + host: "" + body: '{"max_tokens":4000,"messages":[{"content":[{"text":"What''s the weather in Florence,Italy?","type":"text"}],"role":"user"},{"content":[{"text":"I''ll check the current weather in Florence, Italy for you.","type":"text"},{"id":"toolu_vrtx_0129YeW9ohuKMKgnDRAStbvo","input":{"location":"Florence,Italy"},"name":"weather","type":"tool_use"}],"role":"assistant"},{"content":[{"tool_use_id":"toolu_vrtx_0129YeW9ohuKMKgnDRAStbvo","content":[{"text":"40 C","type":"text"}],"type":"tool_result"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"tool_choice":{"disable_parallel_tool_use":false,"type":"auto"},"tools":[{"input_schema":{"properties":{"location":{"description":"the city","type":"string"}},"required":["location"],"type":"object"},"name":"weather","description":"Get weather information for a location"}],"anthropic_version":"vertex-2023-10-16"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Anthropic/Go 1.13.0 + url: https://us-east5-aiplatform.googleapis.com/v1/projects/fantasy-playground-472418/locations/us-east5/publishers/anthropic/models/claude-3-7-sonnet@20250219:rawPredict + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: '{"id":"msg_vrtx_015jg26txA4Uuayzue6Q8PP4","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[{"type":"text","text":"The current temperature in Florence, Italy is 40°C (104°F), which is extremely hot. If you''re in Florence or planning to visit soon, I''d recommend staying hydrated, seeking shade when possible, and perhaps planning indoor activities during the hottest parts of the day."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":475,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":63}}' + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 1.644774333s diff --git a/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..de7e476fc59ad62e60b1590b21ec5d038213bf0a --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-claude-3-7-sonnet/tool_streaming.yaml @@ -0,0 +1,186 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 515 + host: "" + body: '{"max_tokens":4000,"messages":[{"content":[{"text":"What''s the weather in Florence,Italy?","type":"text"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"tool_choice":{"disable_parallel_tool_use":false,"type":"auto"},"tools":[{"input_schema":{"properties":{"location":{"description":"the city","type":"string"}},"required":["location"],"type":"object"},"name":"weather","description":"Get weather information for a location"}],"stream":true,"anthropic_version":"vertex-2023-10-16"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Anthropic/Go 1.13.0 + url: https://us-east5-aiplatform.googleapis.com/v1/projects/fantasy-playground-472418/locations/us-east5/publishers/anthropic/models/claude-3-7-sonnet@20250219:streamRawPredict + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: |+ + event: message_start + data: {"type":"message_start","message":{"id":"msg_vrtx_01MtpPXF7gH6SFEyKNTJLWEw","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":394,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":3}} } + + event: ping + data: {"type": "ping"} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"I'll check"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the current weather in"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Florence, Italy for you"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"."} } + + event: content_block_stop + data: {"type":"content_block_stop","index":0 } + + event: content_block_start + data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_vrtx_01Tj3azsUbpPDwNvW3itTJ3L","name":"weather","input":{}} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"locatio"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"n\": \"Fl"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ore"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"nce,Italy\"}"} } + + event: content_block_stop + data: {"type":"content_block_stop","index":1 } + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":67} } + + event: message_stop + data: {"type":"message_stop" } + + headers: + Content-Type: + - text/event-stream; charset=utf-8 + status: 200 OK + code: 200 + duration: 790.875833ms +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 895 + host: "" + body: '{"max_tokens":4000,"messages":[{"content":[{"text":"What''s the weather in Florence,Italy?","type":"text"}],"role":"user"},{"content":[{"text":"I''ll check the current weather in Florence, Italy for you.","type":"text"},{"id":"toolu_vrtx_01Tj3azsUbpPDwNvW3itTJ3L","input":{"location":"Florence,Italy"},"name":"weather","type":"tool_use"}],"role":"assistant"},{"content":[{"tool_use_id":"toolu_vrtx_01Tj3azsUbpPDwNvW3itTJ3L","content":[{"text":"40 C","type":"text"}],"type":"tool_result"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"tool_choice":{"disable_parallel_tool_use":false,"type":"auto"},"tools":[{"input_schema":{"properties":{"location":{"description":"the city","type":"string"}},"required":["location"],"type":"object"},"name":"weather","description":"Get weather information for a location"}],"stream":true,"anthropic_version":"vertex-2023-10-16"}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Anthropic/Go 1.13.0 + url: https://us-east5-aiplatform.googleapis.com/v1/projects/fantasy-playground-472418/locations/us-east5/publishers/anthropic/models/claude-3-7-sonnet@20250219:streamRawPredict + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: |+ + event: message_start + data: {"type":"message_start","message":{"id":"msg_vrtx_0164AzFzxtcpgGX36csNs7ch","type":"message","role":"assistant","model":"claude-3-7-sonnet-20250219","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":475,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":2}} } + + event: ping + data: {"type": "ping"} + + event: content_block_start + data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"The"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" current temperature in Florence,"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Italy is 40°"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"C (104°F"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"),"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" which is very hot."} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" If"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" you're there"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" or"}} + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" planning to"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" visit, make"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" sure to stay hydrate"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d and try"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" to avoi"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d extende"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"d exposure to the sun during"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the hottest parts of"} } + + event: content_block_delta + data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the day."} } + + event: content_block_stop + data: {"type":"content_block_stop","index":0 } + + event: message_delta + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":59} } + + event: message_stop + data: {"type":"message_stop" } + + headers: + Content-Type: + - text/event-stream; charset=utf-8 + status: 200 OK + code: 200 + duration: 1.460643125s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2453c3370aa101a782243b1c6613fb8d1b5560b4 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool.yaml @@ -0,0 +1,154 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 826 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "functionCall": { + "name": "add", + "args": { + "b": 3, + "a": 2 + } + }, + "thoughtSignature": "CtYBAR/MhbZp5ZDuhyHbftCznQzuyaI5rPK4iL6DCOZOAmHm2hvqsydZ6Zw+07tzqcdyvt0Xj3Jnh9XktQUlSX7lLhgt9ChPZYfBbTUMPnBujEDNLtWhe8/7rZrBKchffl35uHsDpucQ+I+KC9Wj6NzQyaUr/TlGlyGsEXG6RotaEkVFjgxkbwLI7Ibh7CKjSQfqRd5cRz3ANCA9Td7OGCMRGXFGVwm8iC9lmhRtIOFUGxP2WyU/Nnsr3wGO9i4q+3gK17X2+rNbqMKHgb0W0anxkm4kfn8EDw==" + }, + { + "functionCall": { + "name": "multiply", + "args": { + "b": 3, + "a": 2 + } + } + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.949029541015625 + } + ], + "usageMetadata": { + "promptTokenCount": 60, + "candidatesTokenCount": 10, + "totalTokenCount": 129, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 60 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 10 + } + ], + "thoughtsTokenCount": 59 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-10-10T17:53:33.301138Z", + "responseId": "HUjpaNKwEoav1tEPtZXfiQc" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 1.033450667s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 1173 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"},{"parts":[{"functionCall":{"args":{"a":2,"b":3},"id":"add","name":"add"}},{"functionCall":{"args":{"a":2,"b":3},"id":"multiply","name":"multiply"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"add","name":"add","response":{"result":"5"}}},{"functionResponse":{"id":"multiply","name":"multiply","response":{"result":"6"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "The sum of 2 and 3 is 5.\nThe product of 2 and 3 is 6.", + "thoughtSignature": "CvMBAR/Mhba8Or3cizXO+bltJNPmX3Egn41Nag1M8hjUlogeXAA1V3QbRuM3otL2FTMLckFiUxhtWnp8SPfF6wzh2jMPbhKOt9YOgRzjxpqqpH+HmvDhiWiuc0mxsZiazLNjv6M98WD3sBPK4RGLV2QtnWKJAda6NdgAITqFljR/T2efQMopsaStKTWTu2vzO8mm5vUkuYo7uX1gJLJya6+Hp2BWDEliJBDksLt52BJCo9Y7es21bHfMYq1cAytPHX/PbWl975i7uogyW0tMaT+FID0RMAWAivgKum8b8dwb26UMv5jXaHnNLxvnulBGFWfynXYc" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.5109736633300781 + } + ], + "usageMetadata": { + "promptTokenCount": 76, + "candidatesTokenCount": 25, + "totalTokenCount": 151, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 76 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 25 + } + ], + "thoughtsTokenCount": 50 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-10-10T17:53:34.331007Z", + "responseId": "HkjpaP-ZFLqK1tEPkOGW2Qo" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 816.680917ms diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4268174b18bd046abfbcc42258816410a57cc44b --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/multi_tool_streaming.yaml @@ -0,0 +1,65 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 809 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. Always use both add and multiply at the same time."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"functionCall\": {\"name\": \"add\",\"args\": {\"b\": 3,\"a\": 2}},\"thoughtSignature\": \"Ci8BH8yFtuLxt45+UfeKxkMFQ0z1DpxuAVwZgBO1bkie02mw/IydtauigConMkI1BAphAR/Mhbbc1S4CtuarH8jYYzhaj52+9bv5u/bT2t69eSWaxX6cbCo2UFFP0B8X6OkhlSwlTD3SXQkRfjGJkVOBR+qvQ+hnWd2RgZj2MYk2F+USfyFM+246c7s7S90QHbJWpQpZAR/MhbbXo+/edJGN/ayzTQpejpOFNRxKqRWgLG9y6Mq/xR+XpOdSQmyIcEHZgcdz33JJnIf4bly4Uf2ImuxgPWglL7/OF/AqqzmJ6HV87cn8Gcm9d20UWpkKhQEBH8yFtqsVvzlx3eu7b6fwuVEtf2PYAlXtUqGwF9Z83wbQEGXLVQhHTP1q4VRvWWxnQOkRVQYAyOC2m6k8GKmfP5yrTKEulMhWRzIOgmGWd/hOp2ovZhBW1j+7JU2Def/rlvKRhqCWSK+lSb6foAdz2qO/ljV/LBYag6/K54zoCgcVYgrTCmgBH8yFto6oWP1fkj62haTIkAh7HANl76Cjr5WxN1V+LxU5cZ9MoMCo1MT/QttsDSYuCjbhUbWptDRrHa3sXeTfC9/BzB8hzo3lp8xX+jxw5KE0FYcS4ljTPRpM6Pcd9TS6kz8jEUY41A==\"},{\"functionCall\": {\"name\": \"multiply\",\"args\": {\"a\": 2,\"b\": 3}}}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 57,\"candidatesTokenCount\": 10,\"totalTokenCount\": 160,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 57}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 10}],\"thoughtsTokenCount\": 93},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:35.428458Z\",\"responseId\": \"H0jpaKqTGqLFr9wPqPX_-Aw\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 1.12027675s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 1156 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"},{"parts":[{"functionCall":{"args":{"a":2,"b":3},"id":"add","name":"add"}},{"functionCall":{"args":{"a":2,"b":3},"id":"multiply","name":"multiply"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"add","name":"add","response":{"result":"5"}}},{"functionResponse":{"id":"multiply","name":"multiply","response":{"result":"6"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. Always use both add and multiply at the same time."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"The sum of\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:36.542521Z\",\"responseId\": \"IEjpaLmOIdiK1tEPrt_M8Q4\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" 2 and 3 is 5, and the product of 2 and 3\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:36.542521Z\",\"responseId\": \"IEjpaLmOIdiK1tEPrt_M8Q4\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" is 6.\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 73,\"candidatesTokenCount\": 25,\"totalTokenCount\": 98,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 73}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 25}]},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:36.542521Z\",\"responseId\": \"IEjpaLmOIdiK1tEPrt_M8Q4\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 562.439458ms diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple.yaml new file mode 100644 index 0000000000000000000000000000000000000000..11b9cf2ffd61e986dde8a7c0441dcb9774ea624e --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple.yaml @@ -0,0 +1,70 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 202 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Say hi in Portuguese"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"}} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "Olá!" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -2.0520646572113037 + } + ], + "usageMetadata": { + "promptTokenCount": 9, + "candidatesTokenCount": 2, + "totalTokenCount": 37, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 9 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 2 + } + ], + "thoughtsTokenCount": 26 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-10-10T17:53:28.119129Z", + "responseId": "GEjpaNmiB8Kh18kP2devgQ4" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 1.410897667s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4bc89922229ba8656a7cbe9f75b227a7bcbd7eab --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/simple_streaming.yaml @@ -0,0 +1,34 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 202 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Say hi in Portuguese"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"}} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"Olá! (pronounced \\\"oh-LAH\\\")\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 9,\"candidatesTokenCount\": 9,\"totalTokenCount\": 45,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"thoughtsTokenCount\": 27},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:29.051002Z\",\"responseId\": \"GUjpaLqOA4av1tEPtZXfiQc\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 780.130208ms diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5ff1f88947fed205a4876a40933eec96f94fcee5 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool.yaml @@ -0,0 +1,142 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 509 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "functionCall": { + "name": "weather", + "args": { + "location": "Florence,Italy" + } + }, + "thoughtSignature": "CvoBAR/MhbYk0qvoO1yRgdHDwwyxH/IbvJEJWkExyjhCp5q/0S0Zf9zBcxa2OWwWdQJR8+Z0JMwJ0PLQnf58ln2HT24wU1p3Nr2gMxAVoZUaB4Uxo/g5rq5C5sFVqfrdzeMXIDxhtrC0Q8OzSEcQy2XIjrrmu0XZLF3BVU58ewt4Ok4/HRGzoworbz23/JLZxUEmB4Jz3TmQD3+tCNhENGIko9zcXctE/R06mFmVF/WaihIM1ibpNichDjYZqmHq5NGSE36wpBaxwJgfxxNwspek3hjVT4EsDn97FJsMeL5I3W1j0Tu8XjF7BX92tWcmP9jZO4iF5O2nda6bbQ==" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -1.0515016555786132 + } + ], + "usageMetadata": { + "promptTokenCount": 28, + "candidatesTokenCount": 5, + "totalTokenCount": 84, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 28 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 5 + } + ], + "thoughtsTokenCount": 51 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-10-10T17:53:30.097682Z", + "responseId": "GkjpaJL7BYXg1tEPj-il0Ak" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 765.1515ms +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 734 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"},{"parts":[{"functionCall":{"args":{"location":"Florence,Italy"},"id":"weather","name":"weather"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"weather","name":"weather","response":{"result":"40 C"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "The weather in Florence, Italy is 40 degrees Celsius." + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.048118614233457126 + } + ], + "usageMetadata": { + "promptTokenCount": 38, + "candidatesTokenCount": 13, + "totalTokenCount": 51, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 38 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 13 + } + ] + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-10-10T17:53:30.858865Z", + "responseId": "GkjpaPG1NOnqr9wPkPmEqQc" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 613.84925ms diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d1b064574f9e392788efd75822f3de3e9e1a0327 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-flash/tool_streaming.yaml @@ -0,0 +1,65 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 509 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"functionCall\": {\"name\": \"weather\",\"args\": {\"location\": \"Florence,Italy\"}},\"thoughtSignature\": \"CiwBH8yFtgEWrsswuiJKtTm0O71xdqArwu946YzZi5DrhqMCR1W/dnTDzrpqMwp3AR/MhbYwgf419d8Qiz1fWmXv2jiaOpg20ejYd0inos2QznyEobLAsVaijF3/F0+uly2ejv4gv9c4eCkyZB95YISvFsv/HVG/vToYgknRhyavgulI5qOX5DPM8HxiDvVqZbEXkPxavasO3DpfsEL0sr/9fJSt+p0KZwEfzIW2wd0x5PgFQS9gGldYmeJXJWKeR6jHti1+zWHy/kJyLU1yI+wmSDNbirg8PdKclw9LoSAAI5UZ7kuf6J4q0AOe+cOU24O/6EhbIKUah0ogHw8mdejEACapOYFI0zkQkloLx0wKtAEBH8yFtpcCgetdQOOK19Pam07q0m8lriGrD+sEGpwmDIHld20IWbN5uLNmzF0zo7Kew6cMg08xPO1VrOXNJyJV+URM82AcshrAX1syE3U5RpN7z7Drj66OR8htZH9vUKX/t9ypZ8HcRCBmoFNyEGScyDE55v19l0CwNEgQMpHUpBGUtl42USTyQmjC8p+S1QLEQtGd7wb8q7Pgn3YDz9EbSvWXqbz6glpVM0vzRyOs/sYwVN4KKwEfzIW2sqGeUzyn2WpY7x08z6MROjREGmnXIOZ9NUa71OVNaFLAOOMa858=\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 28,\"candidatesTokenCount\": 5,\"totalTokenCount\": 110,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 28}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 5}],\"thoughtsTokenCount\": 77},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:31.720272Z\",\"responseId\": \"G0jpaJD7K7qK1tEPkOGW2Qo\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 849.035958ms +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 734 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"},{"parts":[{"functionCall":{"args":{"location":"Florence,Italy"},"id":"weather","name":"weather"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"weather","name":"weather","response":{"result":"40 C"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-flash:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"The weather in Florence, Italy\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:32.552899Z\",\"responseId\": \"HEjpaMPfIcKh18kP2devgQ4\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" is 40 C.\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 38,\"candidatesTokenCount\": 12,\"totalTokenCount\": 50,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 38}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 12}]},\"modelVersion\": \"gemini-2.5-flash\",\"createTime\": \"2025-10-10T17:53:32.552899Z\",\"responseId\": \"HEjpaMPfIcKh18kP2devgQ4\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 429.281708ms diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool.yaml new file mode 100644 index 0000000000000000000000000000000000000000..be25f2f950656ed8127ffe87f1a327011e9f510c --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool.yaml @@ -0,0 +1,154 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 826 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "functionCall": { + "name": "add", + "args": { + "a": 2, + "b": 3 + } + }, + "thoughtSignature": "CqQCAR/MhbZLwIC6tafwHAkCxScqjr2T0r1NLWoahLzRTjgt+N9/7szY8vWOM7QZWZZ88VI9n2FshjX2uMxOuA17pXgQkyvSD3RFCTbzN46tFVYUY+ZyOLMM5YXSyHV40LjXUjbuzMAl/zy+m3ePoYHjQa6xnYhjBPSb/By2HvrCQeDkvRJlvh0uG6xxUr2+0mH0T6ml3Dz6M1LIBHIQiQb20vaPtYn2AyboMVIZA2wFPr14kQhVrOec6fwLc9EtVhoEQO+l+/wPIKfbSdxoX1iA9WqgcdarYtREK7FMcM7afsFKpxziZzPw8BEOAmR2piTTSLPpRckvvB0AkRs25Cnq8IYd2A6D543MpTWrZVcvMcLxL9SJ1DGitNCwc3T7NrPRxR6M+w==" + }, + { + "functionCall": { + "name": "multiply", + "args": { + "a": 2, + "b": 3 + } + } + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.074593436717987058 + } + ], + "usageMetadata": { + "promptTokenCount": 60, + "candidatesTokenCount": 10, + "totalTokenCount": 149, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 60 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 10 + } + ], + "thoughtsTokenCount": 79 + }, + "modelVersion": "gemini-2.5-pro", + "createTime": "2025-10-10T17:54:09.984997Z", + "responseId": "QUjpaKWPPNOJ0ekPr4ebwAs" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 2.03884825s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 1173 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"},{"parts":[{"functionCall":{"args":{"a":2,"b":3},"id":"add","name":"add"}},{"functionCall":{"args":{"a":2,"b":3},"id":"multiply","name":"multiply"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"add","name":"add","response":{"result":"5"}}},{"functionResponse":{"id":"multiply","name":"multiply","response":{"result":"6"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. CRITICAL: Always use both add and multiply at the same time ALWAYS."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "The sum of 2 and 3 is 5, and their product is 6.", + "thoughtSignature": "Cp4IAR/Mhba0Bn7Mh4n08b+PXOBE1CdnRh+TKHsZaAOWoWHY99KDr1mrl6vTj7pPhTKHdFrEoyWag0Rq/sOLjWIkFu6lk+vKx0KUdBcYsGbRZFHAgExlW5jfRi2AL67vei71c5kHvY0BIxjCD/NLTNkM+toaZMxs8ScPCyPojqZKZ5M5VpzXqEZW1+qUI3DkbsVjGKoEp7sCn5CBVF5a+JUakLa3fprGMojBkc78BwFUHeph6CaBuXS+yUA/8HpCvs0qdZfCf5JEh4XE5PUl5Nu5Ak3gI76GJe5pJqP0goXZywRA8306sVi8Ooh5cVKraXGStRZRMtTO8ctiy7ASorUaIZ1o6/p4MacQorW1MxS/aJbe6FM9Uz/8r8Shfa8xhCnKJXbHoeGh/X5UA/m7PyEE66EwnmSqm2jy5u169qRQvlUoNVt3cKHQuk3/wxYNzzheseQoOti6TU46H8NbVG324VwQzw8wurLZstMTMfHwHDN0F/O3m1h9X43OvbGxxLE0nhBEhqQ66IChFT4vNf38TPd2Z5KVnPRcRpK0+oi6BhrDJbyW8UWjRzHuQbPrD9MAJz0bBBXHxfMgIjFkTi1+DYAF4dSLAL+l1dVAhZSof0MaPbiPX+nnmnNdOknwVPtFQ8gs1uuFE0q5vv2VzkLeh1fiz46UmfvhXg60cpVmlYuzg3L85saJ1jpCj3g3W/fPlCaeUs4jm/xEkMpmjx4HBQ26BvrEyQjU07B5KSceLtau3yb6LzwLTHHPyhE7a/TKsfcmmP2c4K8xFeetOoWA1pHfqwOEt0zcmuUTZgTh1nGGPtho1/lwZ8mz9NdUhuPKphuF7tJsPsxjva6AUoJ63Pr/B716/cstkJ7cnrG5jwTZrN2SPbbEPO/KJOW8lbjjQHZ/XyIR+wtWyY7xihTd2CcKQqJQA+q1PoSmTT7ZO2zSCFn/tEaKIFUBruBBkU4H2TL3+QRTjQfrcB1yzdk5Z0nKeLLqbA0iiq+X5D1MypbLwhU072t3QX/Fg5cvxUg0b04PSQ12lAtifhX9AsUJbEZKQCyNa2b4wvkQSmlGDjkhLob3CKTkSYkyFQ19B/yu25HMW7wD2DF03SMwq3esujWiLTunOzLx/0eU3qT4gbiSU1+ajZ3bCXWBYRSEihbbk3CzbJBWpal+7PgE9MiQUaZGZYpXBbPO027pZUxXX5LeRJj1KiyP7g7cmVON5ATkcWYnkuXgS6oMyXybgu+R0asLhahwtVt7f9z5o4rLBSyu/FSeTJhR+E85cupqEzyg3Vqb9CsdsILryOCFfDCbir2ijwnf8U4/xJ25ke+davAy7Tu/Sx/Y9uxatVQa9nRicNDjqzcrk6RJ+FEjKiAwDRNFfXQ6uRScNZLkUpOZQWJSarSNccFQQzeMPhzo3A==" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.80596758189954254 + } + ], + "usageMetadata": { + "promptTokenCount": 76, + "candidatesTokenCount": 19, + "totalTokenCount": 352, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 76 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 19 + } + ], + "thoughtsTokenCount": 257 + }, + "modelVersion": "gemini-2.5-pro", + "createTime": "2025-10-10T17:54:12.024491Z", + "responseId": "REjpaKu_AeWJ0ekP4czIwAs" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 4.195487584s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..77774ba81c5a27f0467c28aab91d8b99a31b0123 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/multi_tool_streaming.yaml @@ -0,0 +1,65 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 809 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. Always use both add and multiply at the same time."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"functionCall\": {\"name\": \"add\",\"args\": {\"a\": 2,\"b\": 3}},\"thoughtSignature\": \"CiQBH8yFtiOcSPrtiNL/5MXYrS703YDiQC/ZycyRiAmPUlpi6l0KXgEfzIW29A9U1otPsCUT7fad8To1donPd/baxgZRwk/fEs0gO/09Op57nJ05ewa7TOEObY4lWHdPf1sa+xYyeZz6g9pZvN5wUeRoasi0bXmjorjsnfOCCnB1axBa9NoKawEfzIW2oHaOoL5wS/Cl8MTLXRGopnupUTLoAEoxQ059IF3Ac/KK4gzVUgR0zU32nPxctzscNnSV/fhTkHoTUPJ1fi4jKnnBAaHBrIO9+wdGJ6K0IUcDI6AwdJQoCdcZc2KoCnq2DveJsALPCocBAR/MhbZ5vtT1vqjHBw7JBLescYfie2bV9pWagSRmJeht2TpVC6kHSJzaq9bSmxoogznwAQQQ9Viv4Sk7Noh4nT/4RYb8VxQf73EO7x/ZWIqBiXeJH1/UGtRqf39sTPf41t8BgDFXd4GWMDD5LfIDg8A4+t8f9LJnfoaS1Q5Mq5iQF0WDt7LECjcBH8yFtiJotlgofhgSjsotMNdEZlax36o6OLNnE++1a+1x+kxfG3nfV7Ck5ulR1olMb9coOPNt\"},{\"functionCall\": {\"name\": \"multiply\",\"args\": {\"b\": 3,\"a\": 2}}}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 57,\"candidatesTokenCount\": 10,\"totalTokenCount\": 147,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 57}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 10}],\"thoughtsTokenCount\": 80},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:54:16.458370Z\",\"responseId\": \"SEjpaIL9G6GI0ekPqveHmA4\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 1.744036208s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 1156 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Add and multiply the number 2 and 3"}],"role":"user"},{"parts":[{"functionCall":{"args":{"a":2,"b":3},"id":"add","name":"add"}},{"functionCall":{"args":{"a":2,"b":3},"id":"multiply","name":"multiply"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"add","name":"add","response":{"result":"5"}}},{"functionResponse":{"id":"multiply","name":"multiply","response":{"result":"6"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant. Always use both add and multiply at the same time."}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Add two numbers","name":"add","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}},{"description":"Multiply two numbers","name":"multiply","parameters":{"properties":{"a":{"description":"first number","type":"INTEGER"},"b":{"description":"second number","type":"INTEGER"}},"required":["a","b"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"The sum of 2 and 3 is 5 and the product is 6.\",\"thoughtSignature\": \"CiIBH8yFtlXMzNL6Mlcd9Oh4/T39qsj/so81HRuh5ja754RbCmQBH8yFtkiXilnsEhjqc+/oAV73czI/IVX63MIHx3fP6R6ym+AMNejgj5D0Mnk0MrxaOjFENDK4uUSK40L73j4kyIyZK0pVYoiNDAk0u+MwyI1kpCgdBnFuz9/8lIuOyStAZEPNCmwBH8yFtny8MMG09k0CweELugi0Zh/wRbjCIr9hWcbbVLLYypxNsFa6mEmLukfA/a+ot+kC03T15CEXfgDzmG5AOMQM2PQPErmfWU6Bee6ig3dtgVR6Fbb+97giKMq58tA2ZCZEQomBQ0xT1gMKkAEBH8yFtja3OB/r3IDwXUfQZkMMyHb7gkS1G/2TT0xbJ+7Yf5AdJ/E9wMLbVBGTu9hcnvHtB5A1Avp1cHBuXIWAlXtmayNEdausKCknw6zTkJ+iKilGhfOuRkzB8zEkH6RZlOoLSVRbOWZxV/7U5pS9LHdemdNe9h0Ajfny7Q6YmqmfuHTQtkdl39IcYLCyFw8KmwEBH8yFtqPbXbtcY7mvM4kZoiCqTAkr5uJKtLZ0tB7jJEBpj8d4goZ9np2GFMQpTElWrg0E6xCHj3GR+KjoalO1bzMBoCTHXsoDZzqBEr+69r8i/LgtEhpes1KSaCquu+/eNAt3O8HmNKwAE032DsW7PLjIEtCWcmD+tsEV9Y/t0JiJomPxcxtQ5ZRbRUHFlU3hf0XSl5jISDFhFAq9AQEfzIW2EQtf8LwsjffveUDSoK4t5JPiM6P2uaYyGJoaLVL6yyn1qJ4cnJuidaFCSoAxWuuTyLAnf1SXrRRpPrnXEp8viKP2kFoETmHnvD20exLG/oKHqLu/iwAz2WAYy+jI2wk7A0WfOT+c+RJ7UJC7g3j0g+Lg6nnDYJyyS1dHp6wuuLHmcmzqbHPcbDxIFp28GromOMoIdTYZLIxQKUuS9MYvVv6TV9p/Tjkj5IdfZMRFnlCOY5DcEu7leQrPAQEfzIW2862vzTEGXazlJpXbYzn3L5gjv7pDkYxkU59vHMsbZcMZ+sV1S/fB2ginwKk7JPw8DoTBWyWzLIh9c2Tzq6DlrJaFqkwXU3SFYlrOAUyweYIf0w4toI6ztgvfPkLbW5os2eIi167eW67iOZ33oE/0x5alqU751Pt470hP6DBL8gEvK0sPAmQarQHyBwdcK3tDij97YTS43kdYMBtpVFUA0minG9eFwROm8hEfPvFdl9B+UFD83lkPbwQTfjhXjtnhDtvDJEifmu41YgqtAgEfzIW2Hk/ImHb4xRcLbbD0bEsKXpx/5Ko1X8arWTvI50jugUkAW8GTLLZC6I8tvARLBNQQH3xNuA7wUoFLJ4zVx9MLT4Rd42XbAK3C+pvjr8pZwdx1vVKOnZaBpbzTXcgYiS2/7hTjGWMapLhSoWOsaAfEyRD6bxEcFfLolAxD6nTR4sroz6vTOCyvfIzC/ebpMbUn8jZU1hhk6Pud9asWOl96tn9NNREEmQDqN4CZp168YHoPqgpiNO4K1ZzpcBq8VsjeytE0c9RRfgEsLalwNHLByjtlh6WkmQfEOBQ9Au2gm+Z7HdaWfjl7NfADGTnUBEADNUzLa2f2gNIUqfer85QfjZGUCLU9PWgepAf0eyMwqw47XB4cjz0Nigz5meNqt8sYwD9Vnxn3C18KawEfzIW2J+5eifzMPjA70QprJPL4wejGz5LaslO4rJZtnN1RnfNmsoWWaq6+V9mzvISPD9me2lVRWCnfXc0hCj4qFUPaH51l6/K56Q6JAdccLX379ipjSLjlI8reh13IYdC5ZrfRx1dcXC2t\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 73,\"candidatesTokenCount\": 18,\"totalTokenCount\": 373,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 73}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 18}],\"thoughtsTokenCount\": 282},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:54:18.206739Z\",\"responseId\": \"SkjpaJPPDNOJ0ekPr4ebwAs\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 4.37384875s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple.yaml new file mode 100644 index 0000000000000000000000000000000000000000..15367be8152d0bdc096aac6e8a21c2099fbec0b9 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple.yaml @@ -0,0 +1,70 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 202 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Say hi in Portuguese"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"}} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "Of course!\n\nThe most common ways to say \"hi\" in Portuguese are:\n\n* **Olá!** (This is the direct translation of \"hello\" and can be used in any situation, formal or informal.)\n* **Oi!** (This is more informal, like \"hi,\" and is extremely common, especially in Brazil.)\n\nYou can also use greetings that change with the time of day:\n\n* **Bom dia** (Good morning)\n* **Boa tarde** (Good afternoon)\n* **Boa noite** (Good evening / Good night)" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -1.9789696365106302 + } + ], + "usageMetadata": { + "promptTokenCount": 9, + "candidatesTokenCount": 122, + "totalTokenCount": 1356, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 9 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 122 + } + ], + "thoughtsTokenCount": 1225 + }, + "modelVersion": "gemini-2.5-pro", + "createTime": "2025-10-10T17:53:37.635488Z", + "responseId": "IUjpaODkJunqr9wPkPmEqQc" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 14.255680041s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f19fd6958fba93b539f2f61f2890f2eb29fbc18b --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/simple_streaming.yaml @@ -0,0 +1,34 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 202 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Say hi in Portuguese"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"}} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"Of course! Here are a few ways to say \\\"hi\\\" in Portuguese, from the most common to other useful greetings.\\n\\nThe most common ways are:\\n\\n* **Olá** (oh-LAH) - This is the direct translation of \\\"hello\\\" and can be used in any situation, formal or informal.\\n*\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:53:52.753834Z\",\"responseId\": \"MEjpaKqBLuWJ0ekP4czIwAs\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" **Oi** (oy) - This is the most common, casual way to say \\\"hi,\\\" especially in Brazil. It's friendly and used everywhere.\\n\\nYou can also use these common greetings:\\n\\n* **Tudo bem?** (TOO-doo beng?) - Means \\\"How are you?\\\" or\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:53:52.753834Z\",\"responseId\": \"MEjpaKqBLuWJ0ekP4czIwAs\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" \\\"Is everything well?\\\" and is often used as a greeting on its own.\\n* **Bom dia** (bong JEE-ah) - \\\"Good morning.\\\"\\n* **Boa tarde** (BOH-ah TARD-jay) - \\\"Good afternoon.\\\"\\n* **Boa noite** (B\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:53:52.753834Z\",\"responseId\": \"MEjpaKqBLuWJ0ekP4czIwAs\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"OH-ah NOY-tchee) - \\\"Good evening\\\" or \\\"Good night.\\\"\\n\\nSo, a simple **\\\"Olá\\\"** or **\\\"Oi\\\"** is perfect\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 9,\"candidatesTokenCount\": 233,\"totalTokenCount\": 946,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 9}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 233}],\"thoughtsTokenCount\": 704},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:53:52.753834Z\",\"responseId\": \"MEjpaKqBLuWJ0ekP4czIwAs\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 8.862185916s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d3149046d91d66ac489ee7712b83c995bb9cf42c --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool.yaml @@ -0,0 +1,142 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 509 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "functionCall": { + "name": "weather", + "args": { + "location": "Florence, Italy" + } + }, + "thoughtSignature": "Cv4DAR/MhbZ2LpWbGDKTyyTnSEI5Mbxsa8KH+9LlZ7C1whClIZwMMYizqs+M3qhEkbCwG4lqJXruVsWCWHhA8Mth+s3XzLc4cq3DChXa/vjuNuTGi7NWDzjAOA5ZY+BnK658YQvva+1dFinvrKu1H928HLMQkthqikGFX/oEJw+VJo0tKTWAEKU00oL5D3BL10AWE+D7xcuc/sxoEsJXq+cJaZf4TqNuQ2WvsPi0bx+CIqDmCVJ8Zr7S94jYj0CiAX+taFLoPyvpjG79R5kcnqI6euqhckUnsSbG3nXOeHBiNvsaTD3eHjl+lIs6rWOCVu7sQDOQhO2IkZlrVl+/uYzWFLz/4JJYODYtECgJCz7VK5OSeqIx/YOxADIMpCCgF5S74pGz+9o33foKqukPtPHiJ22YMOs37RYFZDR8f5cnvkomHwIChRQ1T723wsGHLK/KD2c6mb2I8eeVtYQwODyP11ryI5r7G7aMUKM6NKu9K/FzlSkURUlJ18hZ1tPaIyFYGys3+WI0Q7CbxP9Yc7M6luXxy8UdiMqZkk7fegM4EO3IVHp4yYB9exU9Y1QjhY1jTsZcgYZd8QMLpI6mRtwnqtsPF6ymqvo9jfxi1FXCAjhyiNjmP1IPjt1hPoYXbOb+Ntbb9XvGxWtGv1ZhVSveggLzbI53wZFTGd6IuhWd" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.83615646362304685 + } + ], + "usageMetadata": { + "promptTokenCount": 28, + "candidatesTokenCount": 5, + "totalTokenCount": 150, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 28 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 5 + } + ], + "thoughtsTokenCount": 117 + }, + "modelVersion": "gemini-2.5-pro", + "createTime": "2025-10-10T17:54:02.708890Z", + "responseId": "OkjpaJqiK6GI0ekPqveHmA4" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 2.841282292s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 735 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"},{"parts":[{"functionCall":{"args":{"location":"Florence, Italy"},"id":"weather","name":"weather"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"weather","name":"weather","response":{"result":"40 C"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "The weather in Florence, Italy is 40 degrees Celsius.\n" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.07556527001517159 + } + ], + "usageMetadata": { + "promptTokenCount": 38, + "candidatesTokenCount": 14, + "totalTokenCount": 52, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 38 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 14 + } + ] + }, + "modelVersion": "gemini-2.5-pro", + "createTime": "2025-10-10T17:54:05.547479Z", + "responseId": "PUjpaJe1IbqD0ekPpPnL0A0" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 1.102223375s diff --git a/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool_streaming.yaml b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool_streaming.yaml new file mode 100644 index 0000000000000000000000000000000000000000..95240e13239639a4f3fccc72fc349f17d5fe9e86 --- /dev/null +++ b/providertests/testdata/TestGoogleCommon/vertex-gemini-2-5-pro/tool_streaming.yaml @@ -0,0 +1,65 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 509 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"functionCall\": {\"name\": \"weather\",\"args\": {\"location\": \"Florence,Italy\"}},\"thoughtSignature\": \"CiIBH8yFtqL+SsLCddk/4VoOe5DT5r8R3xnYhp5FOfJ6nVFACmUBH8yFtufUQzXlrerRxV8pyDnVz+0YNJJPyN4CSlW1pXY/r2CjlepDUpqwYGqNY67zKQsEgIDsC5ItwTe5zdj12Ciq3Cj38c+Ig8D1owiSgHDOWkLgzn27ZMLJ1DEc/RBULRZumwpjAR/MhbbT0a16XvU3Gc/6vCBsiVbKrZEiEbCgX9uCVjCSJW4ejWv+6LfHk31Msi4FcYhEDDwfZWpzopUjARW9eAr34TvnT/MNfa5Pkw8VBtWVcnAV0GCSmBt6qAssoQg+hPWuCqMBAR/Mhba1Dz5g8+pNt30JVQoYkqnaXjkPFZZFi53PfD1702ZTTyBuEHkAfaGA5xkc1DGFOOJBIKcvS+g93dimA9CPl6WqxPjNIS09hArslMsSBlijTvxEfA+be3bTZ5ic0bgMcghEmtG3o22hBxeArg6bIp9E1ZygA1Djd6pWwv2Iq5AoZgy+5EqIaXqCtFYxLjrBrAH30bHm4QmQTU9r/vVHnwquAQEfzIW2ecG1QOUJU5BlGtXHsahNSDLbVvFKEuH24OnkVDTlOwiZ+XcaoiSujahdKJTh9F82arYSoPorcamcw8O1n0NP1Z59gIflpY73BqlJbt4hCBK0DH+6/KfqHvF533gWM5f7Kce4+GrU580MOC1DEeeivoCApF6HORDdgbi9UtfLHBG7MI476E+walwqcpGJvsVDY10NmynGjQM85IlfhTMyKmRYXsQL7GeROQp2AR/MhbZZNS7io3fAV86vVIwJkLgi1uxiTGOuXp3ZO2Iee5rIWVvSookdboinzffqMQCW9Br2pgzWA2uuF0KJV5Py3wXoYVTMQ/o7ZIvZMGTNNW0xzj1fZITx8w8lthHRA1iFG2C4rPjpu13QL9k+xWQ/0i0XUg==\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 28,\"candidatesTokenCount\": 5,\"totalTokenCount\": 158,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 28}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 5}],\"thoughtsTokenCount\": 125},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:54:06.896572Z\",\"responseId\": \"PkjpaLzcNrGM0ekPl93MkA0\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 1.922630417s +- id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 734 + host: us-east5-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"What's the weather in Florence,Italy?"}],"role":"user"},{"parts":[{"functionCall":{"args":{"location":"Florence,Italy"},"id":"weather","name":"weather"}}],"role":"model"},{"parts":[{"functionResponse":{"id":"weather","name":"weather","response":{"result":"40 C"}}}],"role":"user"}],"generationConfig":{"maxOutputTokens":4000},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"},"toolConfig":{"functionCallingConfig":{"mode":"AUTO"}},"tools":[{"functionDeclarations":[{"description":"Get weather information for a location","name":"weather","parameters":{"properties":{"location":{"description":"the city","type":"STRING"}},"required":["location"],"type":"OBJECT"}}]}]} + form: + alt: + - sse + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.29.0 gl-go/go1.25.0 + url: https://us-east5-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-east5/publishers/google/models/gemini-2.5-pro:streamGenerateContent?alt=sse + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + body: "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"The\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:54:08.815625Z\",\"responseId\": \"QEjpaInkMcCC0ekPm6v86As\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" weather in Florence, Italy is 40 degrees Celsius.\\n\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:54:08.815625Z\",\"responseId\": \"QEjpaInkMcCC0ekPm6v86As\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 38,\"candidatesTokenCount\": 14,\"totalTokenCount\": 52,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 38}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 14}]},\"modelVersion\": \"gemini-2.5-pro\",\"createTime\": \"2025-10-10T17:54:08.815625Z\",\"responseId\": \"QEjpaInkMcCC0ekPm6v86As\"}\r\n\r\n" + headers: + Content-Type: + - text/event-stream + status: 200 OK + code: 200 + duration: 656.704042ms diff --git a/providertests/testdata/TestSimple/google-vertex-claude-sonnet.yaml b/providertests/testdata/TestSimple/google-vertex-claude-sonnet.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2797c38e00eb2b06c9590e44b16e0de030e4126a --- /dev/null +++ b/providertests/testdata/TestSimple/google-vertex-claude-sonnet.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/providertests/testdata/TestSimple/google-vertex-gemini-2.5-flash.yaml b/providertests/testdata/TestSimple/google-vertex-gemini-2.5-flash.yaml new file mode 100644 index 0000000000000000000000000000000000000000..211a2c94bea084f8d194a1f03a531a523a1f089e --- /dev/null +++ b/providertests/testdata/TestSimple/google-vertex-gemini-2.5-flash.yaml @@ -0,0 +1,70 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 180 + host: us-central1-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Say hi in Portuguese"}],"role":"user"}],"generationConfig":{},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"}} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.25.0 gl-go/go1.25.0 + url: https://us-central1-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-central1/publishers/google/models/gemini-2.5-flash:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "Olá!" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.57995378971099854 + } + ], + "usageMetadata": { + "promptTokenCount": 9, + "candidatesTokenCount": 2, + "totalTokenCount": 37, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 9 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 2 + } + ], + "thoughtsTokenCount": 26 + }, + "modelVersion": "gemini-2.5-flash", + "createTime": "2025-09-24T19:46:03.607780Z", + "responseId": "e0rUaKSMJb-gm9IP0PuYmQo" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 1.489268833s diff --git a/providertests/testdata/TestSimple/google-vertex-gemini-2.5-pro.yaml b/providertests/testdata/TestSimple/google-vertex-gemini-2.5-pro.yaml new file mode 100644 index 0000000000000000000000000000000000000000..48a671285abf201e0105640743ea25d8ae0482f5 --- /dev/null +++ b/providertests/testdata/TestSimple/google-vertex-gemini-2.5-pro.yaml @@ -0,0 +1,70 @@ +--- +version: 2 +interactions: +- id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 180 + host: us-central1-aiplatform.googleapis.com + body: | + {"contents":[{"parts":[{"text":"Say hi in Portuguese"}],"role":"user"}],"generationConfig":{},"systemInstruction":{"parts":[{"text":"You are a helpful assistant"}],"role":"user"}} + headers: + Content-Type: + - application/json + User-Agent: + - google-genai-sdk/1.25.0 gl-go/go1.25.0 + url: https://us-central1-aiplatform.googleapis.com/v1beta1/projects/fantasy-playground-472418/locations/us-central1/publishers/google/models/gemini-2.5-pro:generateContent + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + content_length: -1 + uncompressed: true + body: | + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "Of course! The most common ways to say \"hi\" in Portuguese are:\n\n* **Olá** (oh-LAH) - This is the equivalent of \"hello\" and can be used in any situation.\n* **Oi** (oy) - This is more informal, like \"hi,\" and is extremely common, especially in Brazil.\n\nYou can also use a greeting depending on the time of day:\n\n* **Bom dia** (bohm JEE-ah) - Good morning\n* **Boa tarde** (BOH-ah TAR-jee) - Good afternoon\n* **Boa noite** (BOH-ah NOY-tchee) - Good evening / Good night" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.93591674386638479 + } + ], + "usageMetadata": { + "promptTokenCount": 9, + "candidatesTokenCount": 146, + "totalTokenCount": 905, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 9 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 146 + } + ], + "thoughtsTokenCount": 750 + }, + "modelVersion": "gemini-2.5-pro", + "createTime": "2025-09-24T19:46:04.550048Z", + "responseId": "fErUaKDJIaChmecP8LzZwQs" + } + headers: + Content-Type: + - application/json; charset=UTF-8 + status: 200 OK + code: 200 + duration: 8.665756459s