From 95b7f07998fe4cf520c420599e8a05230fb2dea9 Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Fri, 14 Feb 2025 20:21:49 +0100 Subject: [PATCH 1/2] feat(genai sdk): generate text using tools --- genai/go.mod | 58 ++++++++ genai/go.sum | 129 ++++++++++++++++++ genai/tools/code_exec_with_txt.go | 89 ++++++++++++ genai/tools/code_exec_with_txt_local_img.go | 111 +++++++++++++++ genai/tools/func_desc_with_txt.go | 128 +++++++++++++++++ .../testdata/640px-Monty_open_door.svg.png | Bin 0 -> 24719 bytes genai/tools/tools_test.go | 71 ++++++++++ go.work | 3 +- 8 files changed, 588 insertions(+), 1 deletion(-) create mode 100644 genai/go.mod create mode 100644 genai/go.sum create mode 100644 genai/tools/code_exec_with_txt.go create mode 100644 genai/tools/code_exec_with_txt_local_img.go create mode 100644 genai/tools/func_desc_with_txt.go create mode 100644 genai/tools/testdata/640px-Monty_open_door.svg.png create mode 100644 genai/tools/tools_test.go diff --git a/genai/go.mod b/genai/go.mod new file mode 100644 index 0000000000..feb4a76b5e --- /dev/null +++ b/genai/go.mod @@ -0,0 +1,58 @@ +module github.com/GoogleCloudPlatform/golang-samples/genai + +go 1.23.0 + +require ( + github.com/GoogleCloudPlatform/golang-samples v0.0.0-20250201051611-5fb145d1e974 + google.golang.org/genai v0.3.0 +) + +require ( + cel.dev/expr v0.19.1 // indirect + cloud.google.com/go v0.118.0 // indirect + cloud.google.com/go/auth v0.14.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/iam v1.3.1 // indirect + cloud.google.com/go/monitoring v1.23.0 // indirect + cloud.google.com/go/storage v1.50.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // 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/google/go-cmp v0.6.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/api v0.217.0 // indirect + google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect +) diff --git a/genai/go.sum b/genai/go.sum new file mode 100644 index 0000000000..0d6ca71543 --- /dev/null +++ b/genai/go.sum @@ -0,0 +1,129 @@ +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ= +cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM= +cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= +cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E= +cloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= +cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= +cloud.google.com/go/monitoring v1.23.0 h1:M3nXww2gn9oZ/qWN2bZ35CjolnVHM3qnSbu6srCPgjk= +cloud.google.com/go/monitoring v1.23.0/go.mod h1:034NnlQPDzrQ64G2Gavhl0LUHZs9H3rRmhtnp7jiJgg= +cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= +cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= +github.com/GoogleCloudPlatform/golang-samples v0.0.0-20250201051611-5fb145d1e974 h1:PS8ehvuB87wYj3/1LJk6x9T2W1wg9T1yqLsBXWvwELM= +github.com/GoogleCloudPlatform/golang-samples v0.0.0-20250201051611-5fb145d1e974/go.mod h1:qMsuU2f349s5vt0u1hUeU0Vu6JWLQ5dAoKM+0Lm7fkQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= +github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU= +google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI= +google.golang.org/genai v0.3.0 h1:xSYQAFmZvHbQhK8Ay9FvpecMcqVhTGZbLSRiDyxEVBs= +google.golang.org/genai v0.3.0/go.mod h1:yPyKKBezIg2rqZziLhHQ5CD62HWr7sLDLc2PDzdrNVs= +google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f h1:387Y+JbxF52bmesc8kq1NyYIp33dnxCw6eiA7JMsTmw= +google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:0joYwWwLQh18AOj8zMYeZLjzuqcYTU3/nC5JdCvC3JI= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/genai/tools/code_exec_with_txt.go b/genai/tools/code_exec_with_txt.go new file mode 100644 index 0000000000..6d5f7ed879 --- /dev/null +++ b/genai/tools/code_exec_with_txt.go @@ -0,0 +1,89 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tools shows examples of various tools that Gemini model can use to generate text. +package tools + +// [START googlegenaisdk_tools_code_exec_with_txt] +import ( + "context" + "fmt" + "io" + + genai "google.golang.org/genai" +) + +// generateWithCodeExec shows how to generate text using the code execution tool. +func generateWithCodeExec(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + prompt := "Calculate 20th fibonacci number. Then find the nearest palindrome to it." + contents := []*genai.Content{ + {Parts: []*genai.Part{ + {Text: prompt}, + }}, + } + config := &genai.GenerateContentConfig{ + Tools: []*genai.Tool{ + {CodeExecution: &genai.ToolCodeExecution{}}, + }, + Temperature: genai.Ptr(0.0), + } + modelName := "gemini-2.0-flash-001" + + resp, err := client.Models.GenerateContent(ctx, modelName, contents, config) + if err != nil { + return fmt.Errorf("failed to generate content: %w", err) + } + + for _, p := range resp.Candidates[0].Content.Parts { + if p.Text != "" { + fmt.Fprintf(w, "Gemini: %s", p.Text) + } + if p.ExecutableCode != nil { + fmt.Fprintf(w, "Language: %s\n%s\n", p.ExecutableCode.Language, p.ExecutableCode.Code) + } + if p.CodeExecutionResult != nil { + fmt.Fprintf(w, "Outcome: %s\n%s\n", p.CodeExecutionResult.Outcome, p.CodeExecutionResult.Output) + } + } + + // Example response: + // Gemini: Okay, I can do that. First, I'll calculate the 20th Fibonacci number. Then, I need ... + // + // Language: PYTHON + // + // def fibonacci(n): + // ... + // + // fib_20 = fibonacci(20) + // print(f'{fib_20=}') + // + // Outcome: OUTCOME_OK + // fib_20=6765 + // + // Now that I have the 20th Fibonacci number (6765), I need to find the nearest palindrome. ... + // ... + + return nil +} + +// [END googlegenaisdk_tools_code_exec_with_txt] diff --git a/genai/tools/code_exec_with_txt_local_img.go b/genai/tools/code_exec_with_txt_local_img.go new file mode 100644 index 0000000000..0ca6a697ab --- /dev/null +++ b/genai/tools/code_exec_with_txt_local_img.go @@ -0,0 +1,111 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tools shows examples of various tools that Gemini model can use to generate text. +package tools + +// [START googlegenaisdk_tools_code_exec_with_txt_local_img] +import ( + "context" + "fmt" + "io" + "os" + + genai "google.golang.org/genai" +) + +// generateWithCodeExecAndImg shows how to generate text using the code execution tool and a local image. +func generateWithCodeExecAndImg(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + // Image source: + // https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Monty_open_door.svg/640px-Monty_open_door.svg.png + imgBytes, err := os.ReadFile("./testdata/640px-Monty_open_door.svg.png") + if err != nil { + return fmt.Errorf("failed to read input file: %w", err) + } + + prompt := ` +Run a simulation of the Monty Hall Problem with 1,000 trials. +Here's how this works as a reminder. In the Monty Hall Problem, you're on a game +show with three doors. Behind one is a car, and behind the others are goats. You +pick a door. The host, who knows what's behind the doors, opens a different door +to reveal a goat. Should you switch to the remaining unopened door? +The answer has always been a little difficult for me to understand when people +solve it with math - so please run a simulation with Python to show me what the +best strategy is. +Thank you!` + contents := []*genai.Content{ + {Parts: []*genai.Part{ + {Text: prompt}, + {InlineData: &genai.Blob{ + Data: imgBytes, + MIMEType: "image/png", + }}, + }}, + } + config := &genai.GenerateContentConfig{ + Tools: []*genai.Tool{ + {CodeExecution: &genai.ToolCodeExecution{}}, + }, + Temperature: genai.Ptr(0.0), + } + modelName := "gemini-2.0-flash-001" + + resp, err := client.Models.GenerateContent(ctx, modelName, contents, config) + if err != nil { + return fmt.Errorf("failed to generate content: %w", err) + } + + for _, p := range resp.Candidates[0].Content.Parts { + if p.Text != "" { + fmt.Fprintf(w, "Gemini: %s", p.Text) + } + if p.ExecutableCode != nil { + fmt.Fprintf(w, "Language: %s\n%s\n", p.ExecutableCode.Language, p.ExecutableCode.Code) + } + if p.CodeExecutionResult != nil { + fmt.Fprintf(w, "Outcome: %s\n%s\n", p.CodeExecutionResult.Outcome, p.CodeExecutionResult.Output) + } + } + + // Example response: + // Language: PYTHON + // + // import random + // + // def monty_hall_simulation(num_trials): + // ... + // + // # Run the simulation for 1000 trials + // num_trials = 1000 + // switch_win_percentage, stay_win_percentage = monty_hall_simulation(num_trials) + // ... + // Outcome: OUTCOME_OK + // Switching Doors Win Percentage: 63.80% + // Staying with Original Door Win Percentage: 36.20% + // + // Gemini: The results of the simulation clearly demonstrate that switching doors is the better strategy. ... + + return nil +} + +// [END googlegenaisdk_tools_code_exec_with_txt_local_img] diff --git a/genai/tools/func_desc_with_txt.go b/genai/tools/func_desc_with_txt.go new file mode 100644 index 0000000000..47c8aae758 --- /dev/null +++ b/genai/tools/func_desc_with_txt.go @@ -0,0 +1,128 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tools shows examples of various tools that Gemini model can use to generate text. +package tools + +// [START googlegenaisdk_tools_func_desc_with_txt] +import ( + "context" + "fmt" + "io" + + genai "google.golang.org/genai" +) + +// generateWithFuncCall shows how to submit a prompt and a function declaration to the model, +// allowing it to suggest a call to the function to fetch external data. Returning this data +// enables the model to generate a text response that incorporates the data. +func generateWithFuncCall(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + weatherFunc := &genai.FunctionDeclaration{ + Description: "Returns the current weather in a location.", + Name: "getCurrentWeather", + Parameters: &genai.Schema{ + Type: "object", + Properties: map[string]*genai.Schema{ + "location": {Type: "string"}, + }, + Required: []string{"location"}, + }, + } + config := &genai.GenerateContentConfig{ + Tools: []*genai.Tool{ + {FunctionDeclarations: []*genai.FunctionDeclaration{weatherFunc}}, + }, + Temperature: genai.Ptr(0.0), + } + + modelName := "gemini-2.0-flash-001" + contents := []*genai.Content{ + {Parts: []*genai.Part{ + {Text: "What is the weather like in Boston?"}, + }}, + } + + resp, err := client.Models.GenerateContent(ctx, modelName, contents, config) + if err != nil { + return fmt.Errorf("failed to generate content: %w", err) + } + + var funcCall *genai.FunctionCall + for _, p := range resp.Candidates[0].Content.Parts { + if p.FunctionCall != nil { + funcCall = p.FunctionCall + fmt.Fprint(w, "The model suggests to call the function ") + fmt.Fprintf(w, "%q with args: %v\n", funcCall.Name, funcCall.Args) + // Example response: + // The model suggests to call the function "getCurrentWeather" with args: map[location:Boston] + } + } + if funcCall == nil { + return fmt.Errorf("model did not suggest a function call") + } + + // Use synthetic data to simulate a response from the external API. + // In a real application, this would come from an actual weather API. + funcResp := &genai.FunctionResponse{ + Name: "getCurrentWeather", + Response: map[string]any{ + "location": "Boston", + "temperature": "38", + "temperature_unit": "F", + "description": "Cold and cloudy", + "humidity": "65", + "wind": `{"speed": "10", "direction": "NW"}`, + }, + } + + // Return conversation turns and API response to complete the model's response. + contents = []*genai.Content{ + {Parts: []*genai.Part{ + {Text: "What is the weather like in Boston?"}, + }}, + {Parts: []*genai.Part{ + {FunctionCall: funcCall}, + }}, + {Parts: []*genai.Part{ + {FunctionResponse: funcResp}, + }}, + } + + resp, err = client.Models.GenerateContent(ctx, modelName, contents, config) + if err != nil { + return fmt.Errorf("failed to generate content: %w", err) + } + + respText, err := resp.Text() + if err != nil { + return fmt.Errorf("failed to convert model response to text: %w", err) + } + fmt.Fprintln(w, respText) + + // Example response: + // The weather in Boston is cold and cloudy with a temperature of 38 degrees Fahrenheit. The humidity is ... + + return nil +} + +// [END googlegenaisdk_tools_func_desc_with_txt] diff --git a/genai/tools/testdata/640px-Monty_open_door.svg.png b/genai/tools/testdata/640px-Monty_open_door.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..90f83375e36651f988e99720e3274fc4c2b0f878 GIT binary patch literal 24719 zcmagFcRbbq`#*l{WRty;m5hU9MM#o8ict36n{4OEjEs=ILS&R z^KcyBr`P-Q`}=q6c2viCKA+d)y6)GyU)OnKpr=lClkFx51ftT^c>D|mB7}iJ1ok&b zfp_2s3HN}%h@Dh)RY0J+6bhUT3Gn~hb{fxgL7)%;5C|3x0-XbI!L~u50C5oLk2MG+ zpA7;rd*^?9`T+O^sjas9W6;(0pW=>+bl@E_e@$IAvTagYN;>kvn8RWa$SPg)v5Ha9 z++IOo5Xa12|sL55o_FzVIvHye0bachQJY1 z2xQS(o|M^r-Sfr%y{|Qv%_FD%HB)V_ZPnj40yon-hbPG zmSQ{8i8F5eZhd?9w3*qP?13k#F%e0ZW0#SS8jU6aw1S}2h_8f*kfm1HHGzyyD17}u zfuAT?zVQyFJFNQ@)FQajN?J^7%5XbIH$_w__*MrW>^~_NRql!RoyvWj^-Jm+1@Ta3 z^n8Y?_X-l+x)v|&9sTXN#yfE&4#Wya+=J&gb^WLAt=1ECLf9EuF^lt=uyd3%RS~>%L zx46VvxLnrNQ)Q6E@r-N4<>1ZAA7hj^#Z#H755xu<_~=v|`L8y~HH{DG0|^v0dv|u! zX~GAW*Fzg+_4Z^N3KVyjpFYHn65e}t3*Kfey)iGda{PEiL%g!fe!291q#dUWI{UZq z|761&t}!IUbU^QNMQ^|Oi=_6AOX6{wVR^#YXhPWm>)$J{3d-Y;QK0zbevW{{>P6jr zWUp^^eUl=`i8qr%AA>)yvej@6E!wsKP{1-g!U#Urv2751r|3=@9-C|vtxw%S&=f~C zx$#%XRCjh$+UUFoUIm8@dhGg4s_Qygk$KQKjW3zTxxqrni??O4hQArolzXFS>ol~C z#Z=2j91j|2RcxIJsh)2bVWZ(;hpvFnefS24RdKTWOsb7+Kl#unFH&+GNReSeMD_gaaW(!Tdat;8Nz* zpzeTG)J4vf=o)!h*pb^jB?#yxnP6u8X;4ox{fPb{Bf0j&X<*elmo{&e>d&n|MHdRcQg#Fi4RoT(Sd1sN){+-YXkzfiFqyS)PLabrm z|15py=7#til!~1On^f$POTXC&GAY0)mIo%oScC#+;DnX|dDc{J&{=7K_@QaaJlJ#` zIS(J~&N~zhvM3d&iI1iwkJ~5EvBpv~Z~iC5Es96+XOE;|Y0B3hP`eFHAm@oDHsFiK zxtt60U=2lXTwhPHp&txLY?S%v26=J33RV>($OkRRyawMLE(5_-;&;lExc}*bC)a?g z7zHlF3GMt(7#@OKZv>!&L60yML9~xFKrw*AkVV)3Qp-ScTik^X20y~YITF!5>S6pt zOdp4telHEx0zUq~_c*HnrNuNOX#ca*Bf`6U&_{g0-S0-z(m#B615?3D8;z>n2>nm& zqbeNWSl4Hvee@V6AKBA_1N^xttuPfQL>*20FUIx%^g>~XzeRI>7>&DFLqj;A8p0Uh ze!#_dBsTQ_-HuKLB@|{V1JA-tSO15u9wII0(Q&8-u< zC}e`mvANLMGsg02q4YUb=$UK#yJ?C#x&zU;6y2dtNBz~Q;;n{rD*L0x932IWJ+;!_ zR+*!HZ!=Da3D84VkM-doItcfT;;xt}b0a>!2w$a5EI#K32=Hd8kKgKGq9{TWnMvj0 zY1rY!EX+LWwDAMmeu8E{+o1~d+sUpGmI%gs4-SyAOreS_v4}Xi{QGC!t z-7bRm;49H~Os(4`CM$C`&T0uOl7-u4{VjRnUyY;d#Z;89^sMGh7nM{s_8=C#X6{BD z4$O<9z^V9dbw6xWEeUdIe~-l75^E7QcD=#oS2vgI?0 zQgeL9A$=4-8o|*RhH;QhODkmcMcAxYh+}`Qzn=0iKfm?~z|ZO;Z5n)r!4X3N;4oy@ zyY-OAc;K9OyQFlr0^O-!${v48?_Po)MUat+n(W_8(EcCN2o{N09xn6nwru9;vqD!^ zE%HqHp?8?XA}FI2l;)GGKNJrBw!Ig~{0`#nJabV|=0Ggk#>Ngn`#%*YBhF|ntg7kB zFcoE4rhFCUzo2K%=QP}$zakuZ=kqKKHm;+_9j&Jf-m>&VoJs(spWW=d5^xS4#y70# z;r+RbY42D90H7JN(wPF{_#0Y4C!A)+qW{2Klx%&`IqJP1{|7GEwfFIC=#%R=#!atp zViD%7jr)zu3kfz2*Utca8;DnLf>1FCi{4Cbri-k8J>`s9Cku{dOhxv80prm?5q+UE zfQQyM`bE$IME)XFJZtfSe#RXLcG2;+{xsIIL)>Y~ba6q7o`tPg$Au@h?w(GaV8X^kd+E2)T|Hj~~I0qD-!x)q83hJFTmH;vW&b<;Pd?QTtlaeFoqa zJr?WsS7*);m&9mPsUUuY*JOk$4Y3Z!6CpaUCV_klWUT+3uKyCeR(1!=P}kR%f=i#i z3SVCme$rOp=lLzAbhP6EPv8F~fRb?Xw@z5YDQyjt%HUxHTUMD}G@fx6OAOLA!{^sn}W&vjUP~)tS zViibWnL-8%eI{x0;4BuUUl9vvBRdG=^c8E58!Do=A}l~#f>SI)l>(k`-~;@;&SWn} zl`Mg92}E0@)hA-6KuiT?Hmyi7nqMaDGzS{IJlpSg6l|h}zeV~eUQYECbUxGRNs?z0 zHor*|zB4+aGNc@KPTkhb+^8W1YE@MicR6|E!wb#5ye)AMvd|v9EUL0gnpD{31Rixe z{1HdX8Aq*jN%%K8qXkGHs*g7MSG#DfZ;Mk75dM8w1ly~Me7S7L3}>AINqxEzLo7~e zdbkmH9TmEl@aYw3m&4ZZlm|JQ4f+|lH`#4B?#^%Zne|E zKb9J=Wk=PHOn~yYLl>ypK??+^U2y=^V971qviMf}GT#y<-~+36J*b_9wicahpMBFa z>Mc8rM6T8}jIO5id6JZ$~r={BA>^S=Fas(6l2A0=tAY)a{dpQOqn( z>CHELZGD~hv>VJ2%J=`RDPjsQ9+72{uq9?T_a*=%aFQe>Bn#oEi#qvoby&x6SZHKm zBwk+Rf02e9X_Wz#OFUvOAY;N>5R@$9?Q>x{f)?5{@MkIJymDkkQN*B2MM2-+*@hft zXhfS=T0z0c$1KaiG=A-%Ka~|M3a{J`b@f1*1nB`9(Zuk$4WPunPG8 z^bnCFVRMr})KI3V-Tp#r{;N3`_qoQ(%O1A|pOaaqhM<48o-M1SX=$(b`v!@Wh#Wti z#Q=9gjs3MRi8ldC{B$5>+@c|5Fr{38Nw*IPbt#PKzagPu^8Tqp=F`s^xI!h-_{`Py z9u$9Agk6cEVUck+T`|0jOo8{^x%G%}W)V}7tIV%(U8iCMe{`*xa4kUeXYSY0CT$ zuAg6b`%(Q3xf?Ci-m;&~5fO?N`Mx*XUhUn)5{6%#Y&1<<+6|=gL{{q)M^vsc(szkv z0QJ{(Eq5*XH)`3bzg&W#kGjILUQ5F}6-9ah0o$|7u{7MHeq)Mpi=H&*;mpRh_P`?x z-YW;3#p3|v02e+s4I|{xdFZveF_h(p29K#?m@ zDgn>3Qa@$BLwOUP>m{6PJY1w)v{e`?R#R|wRG};pc6QM9Jj?c2iRl_YyR6vpzhu|q zt*Q9OKks@$d_>!c-OQ`3uBK?U0}LX&6lAwjb!%n%-m~lA`hR6&Ns&H`ERQK6At7k( zjo^>Df9;2(ykIccdol*5s&V?!BO(6Mz!nhzONVX6$VCGBPrHNSMuO zTU=?UW}<+=()|uN=ypC3wKW0p6G(muX+=8aGEIFp>(7;T18)9<0^H+Bp*2}iB?q7? zF)aZF9xel4pS1V2b7FWTBogWI;Of!JkJls=KSF+eP>qwcd-AifORyyJLkzLOHICB| zT$#!w(Kj5450s4KQcS|Hl&2EP!YL-CL~Bgz?);UN!=J4aF_RHpRL-dVpeRPwlA(YM z4_s;}5>s z!8LVQ5K9huKNZEC<1O)y*@z}x=)}ZC^rhp@&dys-1sU&2ygcz89uk9*PDNp;R>=Ph zNBqz4c7uz}$?c$X4LPWJ;+~%K*Zj>ZUQy8!+589hKY1ZdISJo+|5up&*WMgSUJ^3n z#Tg~jtZijba&wFiuOeQ&ctO{(3b#?2OeBKwagx3hzlH}tkZ!MiSL)%n8}<$>KZ-IR zer9(k<#-_O+@=b9I6hyolk%%nUHvFFij0QZyzzq1AfB25VnVw(yV~8Q`U)c>nU**J z&q}oc^GsUm)jv%f+2ompd(Jx_+1uC}pphS^AgQj?2KN@bh5VAguj?k8VGTRAA7C5C zHNM(GST)t``St|HE#G!qs?k%;Bt1!UPYTjy*Ygr6cPX8Mz}QZ$T^ERi0+T=dcn_HQ z->^cLZ`kSD=SrAxXKWlzR1{eknF4N}pU4pA1@o`rF$b|IM}#>M(=NL)?ij?kFm-+T zf*sNmvv!^fq|~v{L!gdP@)%7?HhCO&p*%`4>B7JWp=|bt+9~u>B)`xfNP*;72&F91 zGh>LIGyFU-DY>A>r+4mT_=!oA^$+=E^ieC~}RP4Z38F7-*O%u*AVzM3+b^k_K`=6^* zYG372hH5s*N_^%HTn4wZj@EYAoZXr2@|2PkcVHwkG_JE6Z3r5%mzm~7Rt+}X{iH~r zlEX2Msc2`0VzL~C?Tj`FpOk~Go&N;#&AaBsM8DZtY%|%;7SF{;+N{8zLmnKP#a2WN zeGlg@`ISEM?N7R?ML1K;@kMV9(3L#~u}7xY#lA)op0&_+E|1$P)S|E2yWRs>rt5g& z>+75N9KfR|2^@lPT5$-P+xnY+EFbJrl;&PNpZ7p_Trhl3IC;F_^?Y75@N;C7AL;!r z|1aO{X8j4xd9f0w6s3D?dL1hTOI{u9<=8`}tjn|w#;p}eLw;zzz8woo z$ycuT zdH2k7>FBCh_j`YQD?Nw%q>_xGI8IGH-`}UH1(u_!H3gKrZ3T3#B$T5|JKAN|*48XC zl7HLeT)ULzKA|6UE?kBh8{vt?ZME=HH$2^ktdi|`(;-&YVVmr7e<$&rx?b9=Zr<&90euep~Q_Z?C9*x{}@IKLt-STwszY41#9}I8W zjRgx(l9ttM9NCz_+kEbDzh4+mK92&Pbn9jbD>n#1%Jt#+eu$TsM}^1y*E)}R8Ko&qP z*Dq!KKbkKcCojc1&urag+~CP`b90+*6P4R)=H%98($Jl+^wa~bDdF{&{f;d!`RB$u%;Yl+VWZDr?HK^k+{R-&2Hq#-Szl7Zoi&0eQ!Qln2YU< zJL+S2e@yAmJ*y-gh<)Kyc80J8+gc{Z)BD zY%BWHb)$D6LEFM{rflu}DG8Epqr+scaJ$!$irvth#>}m2{%_e_ogEqCa?dMxGsh|D z;5%84q{Ge0$;lC``j>GJdy+s;?m%VOa?&n|#~Zi>kjB%GaDUh!FX{timYFz^Uvj^D z&}`4CVWz{vgLSQzOnqc1rttCW6~kMf{!zelxG1}y)PGJMkF)44QLM)LpK^Ce@j*+* zFC|8QK`9nI(2>(+0A&4$&bOz= zMSVLLh~W&LA&Cx4%I@2;!NLfZ02wdlMdY`tb$~p`MH1sdxOcGGhlF*k;-A68Y#@eu z8+UG&+@*3q znOE@QlIy!o^=8zX87L2>@2`QU?Cxz;N@;+ebQ^nFE`O%cU2Aq23sQO!dUH3iVy*Ya z{h7eP0jrg9wfCyQXXDk^X58dD6L}%@-1=et=fd$g%igec8RO@8IoAz+pLcMU&bdwm zC@NRkMRw)jyZ5kxoPY82ac1fr&8dhun%Ljrt2tpoE|8Y6J2O9jb}^cmeUvxR8MWqD-gw0e&6Z5)9OEg7~BUmIhMk_xg(7D>ij?-=fBy&A%E_t zQ%jaN(nrubMkIh{yy6EWAP0YQ!=DS*t`{#Tn3+>DX6T_xrIj7Wzr5iYiz-Y*bj5s} zr?xfRk&BSNAb4^t461!ILRvxH!335uHYvP^W%%_&Zbf1xepCNQJ91q8M^34=!ON|s zGUz>==S^G?AI{@`jmZ4PLcPNfq3q&b=nc9y!kKr}+`5S2@|}<0-;e$X5IpSdTlWb~ zf7$LSHDEOk74woD6Z;mwHpr9TdZiuIVOd1n`7p;%D)GepRsp7>hPesD-1vDq(IsTo z5Rw{bJmN5!%=%%yCJQwNt$u7F7AV>hnMe~Fz0;iqn~t)HGK^h%7~?0W@TXk82q(lv zPfpZw^Gsn%hb!WouRG?VYpf4enke|3Go2)5G?r|ts{#AD(06&~XM8qO*C;iH5^j5; z!}LYDa&kJ!2;ar0(DC{$>gBUEHVv&;zqhqMF6HQbhD3#!uzBh~i$ekBv$~sp*{}7L zeAcmZzMrGNqC6{FZ0OBgozw~dT3ii*Bf)(C18LL2|CN!Qb$MTQ-&Iqys^9bQ*_KY} zXpTyOaKX2}x1$z63BLIVPJETheHLeKI=QmamRkE}t4#c17uTz!(xZ zRy0@oRW!#*x86o3p49Qd+u-cplbz!7LseAnQ&0JN1uNuv)&7!On+#aFF|WTHu(}$y zc+1(6Tz5GQk!wqhe4!?n#x{=|)=P=~Vn2^C2hIRTg-6yAO%WL^rjD3k)iG-Lrz+h;(P;>Q zCCgxW)rLU$)qz8Hc~(BEfJ5;>Y)Sar{VJ4xFgRjk%+ar5tXNuy`=96q^$V5UQkj}; z@i1<2-jA8#abK*LKL~^FRh0uwYm3`e(MT!p&`-%%G)uk!{=s8Jy$HAWo+z z8uY4nq4V9KRfw-BL>>CQMxsdKc6E@~O^7?bRr|A0^<3+qEa6o{pV4mm%icGJOKUAi zbiMbbPkX~3|9mlZQw-UPG*#i{31J`Af<}2MX*V?)3ZLSo1ukEZHV0aw#31;A_eL$$ zkvuN>BAQ4&LiLGOKtr}T&d;Jb6qjVpDy5swb6){FtD^7oXTl`duXi@AW5-+PZ7OE^ zNYRxY67*+25xe;7im;Zs+tF#X7~1gV+*D^=49l=M^1RF9tUQ2dJC6{WM*tx^+9y*G z8e@|^_|SxZ{%Xa=`raSfJ0%t%h2N{C5RB|6eIE6{hddBWC(nIktL%5ySTIt0@&TXW zpZo!?Ez?1ByUM`h(qDO+r8#N|Pt_7S5{%49E)8YGr94=}(_Y!KbYAMF^X0|a33l{P z*bHIOStjrXTT>0Jo7TO_r0^hz9doec{$Zpp&$awA29l#eipv1X^PyFd#l2%#1C%fe^I?#Vhn1l+8lG=deT*1 zL2A$Fvw&*OE<7zzMzY3Ib;Gd?<}q%3*h#_q&v@FK?2ap#ve+ymm`JEZdV`fy$mpTH z0ArL5+4xX#%^iHu!gA2rVfBoGq(m7`TCv%A=tF)Bx{Y$*ZcXQ#V$oj!mzzIA^1xElhXkr@)UND^BJAkVuXU5$&i(s za;HpQ+wc1_v*JiK1Nlo+ezVw9$P!4N7gJltIjDfx^F-Nd}#7y@@Uat1@zBZ;J@%+id z_>>o>$OBca5A+@(m-WF+WNB)8_K2S(SXKHFqL~!C{LGeZo^OnKhm7CBf1nX`F=-_| zT=?oVYpFbL)t0vNX>$uwM6Hbnu`LRTEY~3dAc_LrGBHP3D9VC`f&sN;ZKW_t9yd+uvE1qyr+>RwxfgqT?s_d$L_FM7`i zcI6z`;%t!IUFT`xcWZZd5qH@N>NEf%FhOdCim>mjfz^8)@4eJAb4G~Bp==dWss2Jt zy8Sc3lDX^4p94DAd*$yg4|j@QytbKv&;DhRiPAFRLNhN9U489Pix?sQ`4ZeCmWJqv znk_#ZOpX%@OgiF!5LKy~1@SqUgYWZ8MmF`P_;=5Xd9<|5M=Q&`EQY?SzVBCi;~w3DoVdG;IHlg)smJed)saR)sK8YC zo81IK+eHUwNhy!_0Z%6qF%_oq6p}D(*Bf2oG1>j%(1k7lL#(NBBrPQ_C z&k5YYmHx-DFUI4Ulfx*<@iWIUba2FODA1Xc*ShTDXu^p#+rBTx1>U?`6@IbXky3CL z0VE-KXHdyiFat~OS9ze%84sNGnV2xC^2sj^TFLY!GzE@DOakH_W1xuS)ky7Nr3 zI;*>jh_6cTZBjGM`~UhB#bhALFQ>C~j%piO#YjKa?1lrg#-4Au4l0ht#yhj5S8}w; z+)SZXn>9~GL+)A;P)^t|b=-k1PX3(_gA%=VmHRp|x;pE@U0;TqZqeMS=RVg+Kl%DC z$GuANhjQi|>U%X(?=z(v*Xp7)k#`6uzA4=B>ysFuQtOS!vBq98-2lJJ%)Cr+YK9KP z=B9r;dJymLxuoWh1@K;VA**)cZWGf0HXhnc{w$`tD0klrxE4?35*E{yU(QI)zHon> z&G|l)&rA)q@CflK8>j29_3MDPrT3u;d>Elwg2F( zTmsZD7@pyEfQh=v@$D;ya89%Tj1MNH?CyfI8p)|2{uyOH`}UX3d}+e+y)uJ$@VKc( zu&?2aq*SP2E9(Z&22F9xAoI8`vs$DCuuiZF=@EpINq^R8>RxPEfvfzw_)u+ea3a&< z!M^|}#vc=fH8QY02yMRK;2^rs(a~Gb$tN6k#xn5k)%-tdw<_pw59K3H%{db3 zI2+r;5plxOa}R1EsO~Pnn&8IDXYZ9f!e<3CmS zeByl-!`}62;dLs;iBLL;wWS@24{$jdv;f|*s2Czr$ zI1>vFfnI$-l6lr)YW-$qhk}yn*8XP__bDHK206nxi=%kZE4R25=#SQwZ^#ss)mnRf@39c*wWdNIXdQ@__ml^1mi9FH;3S`n3~T99q9wyK|Kl z*sj;)rjdgC87Sp4-9nm{-p(LC;5`w|e*>hKVu$`-C#wNuAs#T{Jqz9-n+pY?0$GgU z@^54?NfK_U;_rUSo{1x9c|hGYr!;E}2JC8WeH?$OZe7TpO-jQ>SlUo>8iiZ>E^iZ)%RJM!YoN%5{unS4*GYnT{Rro)XFfCRrvge|FO3F@$`J{cSV$zP{wt#)Zde@OFswB; z{qH^;HRjI5pyw4*J6L4PBftV`Ct9Gp3uUNZMe>%I;r}QetjI^z4C`!;6``|~LCy0? z{p;WNUO_w;yP#IpUCAF%AU1Wrv@Y{mA5-9cxI*7L`1Q4EQ`eI=5kE~wCM?ziOpxB z+A%6^yiPIHQAl@^uW7&Y>H8zK{*AGt5Ft}M)%_j;qHMkAul{UHuf*x?BH2uCiwFv`LX6)P4EgU4AXE2mlXvb? zXGH)}b58`Tt^K8$_~)tW^WQBgC~)7#*oH5rmxAW-duj92=Pwu5q}@;WP89Za^ssx& zEaJXl=7Zc1EjQDO8O?^&V=|N(Sl%^wcqNc&vGr-=A2UQnj>Acn3#V{M6_M)Ul)0_V~E`w#y0Fudh(X}Z|Q?8&-4Oa-kmtgHK1 z#p_X6%V}-^lkts71w3qe-Q_SqtJ!NUhdttZT43SUYXz&@(!zqCC>m?u6NOF(x8CzK zazMq_MpvSpn!6%GVpT;fikeoBMWBpri9x8r@!5Jzn~YWeuFv7+1^8dLpckgWY|H0o z3Y0DoaD|JL;OWz5OsoWP&zE*(Dbn@N-G_mgyd!7U+hg`>#&{9{-<3!FI$t4 zo&c?23ebz1b;tba;mdtH{^!%Ji=O!NX~a1o#OjZju+zmrIgY80OWPx50{O*+;l^P6h^Ns!@@zc?YK81K9F^V69w?c$T0Q>mhM@LaGU zhzxW=5dMgD%_eGP5Gnpe9~~Z<7V^pGd$tljVHeKoc)Vc?z>$(wvImN=0;UUz9z#Pz zLc+ogC)%fsg^OujYN6^cXlbX8Ub zB+hgup}T%$TJm&p@D(epThM36biDq!|4*I5&(l+9>$`NX^!#U5qF23_Oj+g=*P6f3 zeqjb^}cD(fliNpXe#mAHBZ%oo?;-PG&M zh%*vU9PzND z2ZlK6vs!dHH2tL$X_03>P!5ROT)kT!mo25F+->&Kovceku)IeEEQ?rAI-&iyKUySF z4I{Ql#X8YR*;&f4OJ7SH`*U09-tycU^44EgAgk%D%)H??lUC@=Hhmt@jsl-&lehmG z_maOo$#$_1@Q(uzH5yJmc^)9h_pOM&z|5AKC%TRkS6fSepkcm0K{JBvmJwKoQRS2)hU4*%=1+HL=??$XlQJ7d7J%~6n#3N?Ye&C34e>VP zWircY%<~H;mW=!A@Jh!_6!^P1tesNs)u+kE&}E*3)i(!AV%p=CSvmF4Q! zK+}@+`~aUk04VSxk~6&s?=M!1z{_)`T=S+>vI|}6+~GY8`VYPXix(|B<#_|XEVu}* z1Gl$G*I!2u)SS|`gQ%?IxzC?lwNLNR)NdXJ%3%YW`^#{wLH9kjk14fYpsRNd>RbIO zDW=EBwei$!t+IxqUTgSqLR;X$MMC#02{RCu<3;8#}B(~asQUaz;=IAjJiBRR+@ z^&0ja*mG?zl^+nrmaA4NhA3b8<*lqXi5f#GIg%$)BW`A^W$g!6ep+I<@K|I~)}$XE zyXAMi)G;j4|FhOg`FceF-EnH#_DVFIgEVkdinWlg9U4MUsTud9Lv>JZGDkpO1onZx zw{VZ70DoJzmd*`B=KXhN9+)stm%G@8bWGfboQ-8=tx9|FY=EU(BmHQ?2*kxH*~?wl z$1M;EN?5~93hn5sp}=tivqfys%v0J1OqRWr_h`H}Yid0bs?z8%=4^R|uYsey{1GD@ ziZ(nf#yUoAWbnqnHJ@<0wB8pgsqZP9JP(L7lIAG_JO^~X=03dOGgI^*PZwDodevhI zUVPYjFEZ4m+4NF>MZc?|s}&@C^6+T^zr(8Uk-@-_RI3wXSaXmBFhn>}c7m*Ae*joi z0oD1S5ufna#0Ju^5{Y^chIn_X{PaK} z-rrg0;P@)`hM?`lA)ny%M-j$xi%9-B#mJEZYZl|lv9_4$^`ml4d`ZUT`J=0ep7YUp zx7G5GQ@7j+7RVR2FD_yP2&5v%5sagTbz1sPaYxdPd|d z3=J=hiUip7QR81cKuh=3{+M%ShbFCXQFarB&W^mYeRUPwsmIMFu3Knz?$^1jq$q+_ zus^z!%yH5BeB7qt>DK6KM~NFBRN@{qJ9U<%4wO3UWuP;-+!}nY4~A**xf;yK2~bN-b%*hUJB?KZ>8 zZrT6|{Edps6fgbDN)HY`f?Fdyz)JU~a%#7L&h8Nzt7L?$J94=gDiQh+j_YW}^oOA7~Y@dG_HTq?9twfz1zg+0STC(KE_PAZuY;*9)a$B|j+Q|Eg zxaW?umG5!*>^4nZ*Dm-AY0l-0!v4X8bU=Y3P~D;Ng_Wq#Kh*Zv)wRAbz-n6%~HYVmsao?wim78TjqZj;8YaI zh!on9;DI8)%8#-K`xuNAjpq$_6j_d7I4|P(q22Wqt*)b&eDez`%#W9|3fKw|$N^!#+bWrUcn1kkOGw^TugU;IWK-*OOVpspiQt+MD6@*-|{oT@UNVE|HV4B z{h#m5{ZEJFo{jUKgZ#uglUXYYxrs2RfsV)FHyI$GHofpwhCk8}05L6`9F|(C8gZkg zUnokj`GHo99~=eNL)E@n2fm>M(UTV}nI0L~8&f;&(A08#H8EtB_PNkc-Ewf?h(B~= z%nrGnTY?0H^?Y)FnlqH_QgX1%v0I!HsHP7WGA|CYk-vnX32{t(j~EW>6%Xbzi=Vf_ ztYYe_5lryno3CV#hPRrUhyQBI8>;maiw{4R@Z98xxHw`y8e?b2@N>Gb<_C{==ldUf zJOx7qsj(Df7aL=26DJ$!G3vI>F;73gUOF0y@H5{c-0Z(> zZKH-yte~<)m5}9)IAu{72v}B#ZNMLac*{8^O8hiNQ|UN-@aPs#JSlZf=nDYp!<}QK z$Yt%Jh{4cm{hNqQbbQ?X+g>kHaf;0w5rD1PAef4t>ApHobr#C*zBeZLnB67Q-kOF8 zO-IjznN*p)f;uwJ-uumpLtaG}nfV_|Z6D!B*?lB}cb3V9mIX~3z69BxNkjXWzZch} zbu>R}AqA#lPL=-Dng0RE31egC&76Ymy85vYZc`iv9Ku^zF`3=JJYn;(46T3m4{QBb z-ZS@2u3rWw&s7|d)cHwN_B1S7Q!pN>5hIpkjOSz^VHxSMGFu9i!2sLd+_pjE9}RFnG+L9$8`s)slZADwtu z;GeZ@rB}Z|xIHq(OoRPsrnv(Zh#-L%8?|$aslizNh6*2h9bh}0$A=hR##OjL+gL@~ z^5AvWCqgD0U8;Ure&ap!w9lc+fvpX_RnGuZbQ8|d*@)|E3=Q%~3s+p0Jw7@FP59loT{Al?1@y=??P?( zKi%`&4PlDLKQ3W@sCS-V|FsycE%+t&Op_55-D!fti1I zPB(NczR^L2Q)&N2P{0gEwnbk;mLo8f0vjrICG<-AZITeyP)@3v;HYow^ySN&+gNkH~g10bZ%kuCbq(pS%aFrK4o=aPWP+Qfuc17^&~=h z%Ts{n#k#h?E+!DHY*cs~Agg*aa5-IXqVS_VH7R+>Wwfaky=4PB_3xc8%6RH?nd6hD z*-zfotE)+1gWuwSUUvyuQkId?(955;$7qlfnWZNY{EN%#`j7ouY0*#AFKa*v01T&{osG(oD}aFak+@!1 zwCPdHA6)+4st;*cUNYl(V5x8#!4?sh(!RjC5QOtUmA}7;4XJy&&oK(cSjB%11=uVP z`V`%+TK8Do%Ffaf>C#@0cIU!T|30bd!bl+MGiBlNDOthC66re#GI6~P@T_tFi*BfX z{V&+G$>C|hcboPO$@UMHzd4T0{r(ob-D#c$L-oAa^gVLaBaTI`*MT=tUIv5m$?PE) zOLN{jh4_=ODJI>U`gq@0z(}+?J?9ybj6$r9!JJ$|a9-h?oTbja_pJw$nY}Ln)|B@_ zy_oZJ-h&Ih&c6*^MR`n1i_~N^+l6C@Ql?iV%S2}}qrX`c$e0l$g<#8`aRb#U6z?|l zhXaeyjs;%n-6>(RDi%^FAAS_z-YeB%9RhgR9esemVBobc(Eu_lP5%NXA2g|uvTy-+ zm!yg8dO=n~5UMHVyLq6T8a)nGIRdPYwH>DeV18}W`}?D}vGbGSnd-8NYVX|)bb!V7 zk{nRqi^vD%o&u`^w(`Ouz^LD zbC=p&V~sjSx2hCqR--&BNxxJ}0&IeURz+qO4~NC)$nEEg88wP`!c zHDkcq_$NS$LPN*EswJ|Z7S}McwRG={-$8DI;Jt&BwLCM~Ej;1r0I{o9k6Wo9P zEu6l0cHGJ)78*-a-Q7QK#%1HT7YWcUgTbQfJeB}clgx4;PHm=m29$GF5u_ibFI5Mv z(nh+yl0&S~x5r|WB%QbpWfXOrLLV+%$6}Vi{96Nql4nvaSpWM6Va%OsqT;nKHYSSg zO`qKm-KD!PYeGz&oZaDvO$y?Q`>&d3k2edl3%?v<2XbNSI(YHbl?eTdum9Gh_u4yg z!+?0h@0NWqooG@j3=XIsW=z91%&#lG`8ryCpth=Z3fZqV-YascuPEm(^2le%yipLk za;U@7(m6}XUAMfdipgS;hMj)#totKhT;104;9-}6Bu#D9AF;tL?tS-PZD)qom7_md z286hnLpvj+QLnyW?zkEUSR_XD$40(kzz+G|g50rtgE;Y;Jt4YV!@(x`)V^%OcWHQw zfT$au;2p7=RP#BP7ppB!{#Fo6ls&PCt_5W~o(dr*CuLs(lCzu3>ttV`5~u8(hh)`i zjip!tUw+lTfhC%ez2IE_D&XlwzwDXA_>E~;s&!?>NkaUR`EjlL1sSks)K>xQ8NH4_ z7O+ksr}^T+qdy(6Zic72>+BoBJlr98 zjKaDd7&XQZ_jW!PIR4^q%&12t1a;9o=1|(Fs4nk@Kiy#)#+9(Iy~JmrUp99Ir7MfG z=5$UVx^vf%6H<in)Q`_nny``w3`mY?`>>)BYLjmYV04k80)wzIB z{k|{J64u?>D5NAX%+ZuRQAR3W^N&DzHsF5JzRqSQ~5G1JBhaA7P+copSqW`~C^dFvY&yDzWFE$mR&bpf4^T=18)jleRaTzf%1s z0aAJ*BKpU-;)(>|+X3)B;n)jrD zI_rCLb46isuo1iVGvd=MsfQ5kgXav9aoCu2ko?<|jA313jXg}`s zZI|o}9U|v``|WVOr!@FW)boug7O2OqQ^J)nE2`pniL~Dr(H;Mv(*X%QA)+$z2t(~TleLUHD3axlm@7wijp1a_nT9!M_Va6<^WplSV zvOSm8(dDCn?b~sa)2jFCw)fty*E9vvmvcL2@vs@rwHc9F6K(5gR;Cc*I7Yi*b@N|M z0DE!OiLlw zHr>?@;2Y842L1OG-YX{vDXpt?=%}nC&yUb03V}a(U3(gZ_8YzY>uRay`Fi@;mkD_< zGvZu91=W20h;t6_W2VPL)vUOVkzr>VS&AK%8<7%tb@)WIo^VvTNo#?}Mc-_NfClMn zBP-eX34Jn*uPW=wBIor7^Sb{7pN$ou;Ejy2arE#pQ4hMr^uu=^0vMk`Fz?K)63i zAKo!*0#MIYZbPv{5-p%7AKR-odz}{@tZ^;yhC_VMCCTwbbUsO8QsGSO-^AySG*xBr ztHM#D&P@|e%UgH%P!blWQ*K1b^R&PDnWld7!J4NS-Zr z`1ANE$da_F(jKQZWIRGpq_E3wV`ybKk6P9=J2H605@{26$meouq~tTgl1 zfbl&XvD-_qnAVx`VVhBQv1U5OJ2ssAy(=y(Qp&;X@bp=)1Xo7hiKsovd)J%nu72+L zc${qXd9)mwnrM{!wUP21fj`2vVpsazUJ56IcUGN{Mq~mIg|e`DgXmUx`zn~Q1AP!B z+i?#3=3~ycQEr5r7CRP4+wb&HNBEnrbKo_o?|{C@Fx0JBn$`he|RS$yJdcSHd5 zu19)Yjor zE-hQ2kw?s27gkjoc%1VoHE$TnAyGLNRJpKRV+6$kX|>@vnrsIG1-8NGNxKv*f;Y7cI_@plOJ zXySo2y5efl{7J!+`XfcX_HP?DKboe>ggdY{HFYY*?AZ4*+jt=P;;B>YoR3*HNpNx`LXkme?x_G3x$ zTc#9x6(`3g%`UPSp9)0~uQR_cq_x{TQ6~Rc&0T$T9N#_9_uAbPou>_(Fd=U(`8acl zau)_)OuH2wB|Jk}2;4asSeX3Mj++6UzGE(MuWb8koEZI{DN>WYGN2BKHC(C?$$L}{ zGb@r+K+Wa5hLT^8Y{NsvO?)elMn1|Wm8$p;%um;YMVcy0SAvgjFnUin5FKt0(+ z>!_C>f(^c-Acb380Q_qy;@k>fORf&l%y4wW_Z$T-+Mc#v-8{|_2?W?$3cH46t+pFL zDVaWlbp^gBcQ{|wC#|_e(1o3HzKZZWUIx!7sx;mxf1(_9#`Vqcq4N?w5?b4 zgm=_xuS^BJ)-0kht32f#*Kwi?2jp)?en9?)Q!ew`P5-1Q<*{IFWERsYhjq4`5po1D-cH`&w-8OVdbqWs2nK!X|Xc zFj?c6{L(urvx@e^J*Op>a6nBx?nE{g>~UnXFn$y4cuC}83piC1y%Aq^4K_bmmXE&D zqkB{Yt%Sy}{#d~6jC3|{ge#eQQO68DmIasf`a)EiO)7o6_Z($jauZD)VlGl-o2;ed zvGzwstA25|86d>5dQ`H~%S47J73l1WU*M?igYMQ)^w4jQx(m?NZQIla8>RsNO~EvejRq9dhdGlTh@+do0WXNB+q;GK zR74sPGjRIg)LS#RV67A8GC2<$%;^d&4V3SALPi)yWUZVL@0R4Qg?PYgE!xxm;l+B^ z$|r}J^cR2PP(4&qoOxG+Z$phUNw>2uS*+z>LSgvE4at}@4NYmfpPcf__-zr4FlzYU z$S7r{-G6$H{-|=7$14ZL?RPmP*y$h`gY1rXt4C9_WzlLhO7B89xpMG1RHXwHE zeLaXn5@AYB&BC}PYZXI>qkn^j9pQg<^-Mo!KaFPy^Qgj6%svLd4f|un1ndE9z9LbP z#W^(Yd7pC;7(R)}B>ngNJD0n)-hP?0VwIMbu~NEev-smFGA|fYQ+20$b_nxOy;6B9 zNjy#c2yKq}njR(CqU5#p`M%`3Jhd;Ry-*Ne2JT~(x2ztsxKH0@00$-wLafA-_GTeD zhQHi*6z7b{d050mkk3fRXCMpix{ctPjl{ckaJ~vD)Q2JzlS6R9lC=~ZbW&Ev-}j%R zlX*HRlV|4(QePMDl?tQpdI7Q#_skW016XG`#U00tPhwL5{P%Y-l`wG1CD#&G0406iu|4A?HMpovC&#DqzbPkCLYGXUd^1w8Q$ zbs6Rzbs0~=IWS|Tu_Kg6F9Y~0sx(=EThl{NA3UQA$yU5X46;E&$ugYQ@aT;MwL%M4 z862Nhe(?54v;+nULC25a*+z^pZ1!v|c!RFK30j&2&fdhRaD- z$UQ-;S)1>p)4D5ZzCS6{g9scHD1X2rl`;5srRW*5I=V&+VhTG_9B-8=Di1Ir3Vhyv z^N(!YO7CbIO2=P1>Q1~*evtue9>Y#JLH7jduwTLHYE+zL6pE;wk0Y_{Ld?+WpCm+F zFJ<6hyV$CI;Y%(eueQ+VkI*=vY)bPuX%r#szPj;>%f#KIeQ&b+>~^2}lKFLlU2BC4 zv4DZb01Ykr@XaRCRpu-YI;z0ft(Ji5W;pkMmGsOqXb1beP12dKl&`sDcJY> zJt3lH2TqxDT$2pjI>j{Al;?Cil3&=+*Xfg!41+Ue9p0Y%RE@FDG3)?V4H@+Hixac( z;^|cNsrocGc2FJ6>S$mwS!0dcnyVa^l{PxWAeXh^Td6!HQ0j5&?;ifPg z21&5VN_b_Rsugq=Y%?sv;CeA6uP3rOVDOthD>3J1fN8?HLeLxm88)pt$|*JJoo+h| z)lyPljDr}gqucTu*dhkCc&gq~FM=*i0gzwBt}&o>W7Fr^7j z^~u<62Im|w`(ZY)VYz(CU2~kV1tW_AD% zMwY5hrTv5c$rs)?Ggd70T{<8Ef$V+J!KSoK?~M8T&4~QXOYmarjY*kR za8~$DXeZl`_aDBFer`$pT?sApFR_J84XbrX_7AFu8T1^6MWfV16%lE!BaYq0{-DFI zrDp(jX`k)|eKPA=@+n?@W(H?26GD4d6tr#+!Md>mx1*JVCGe$l2FF&wR;$JWUuUNwq=bQQoA5m#u$EOwdh2?XAKpoG&xl(8i8!Bnt_ zXmo{WIgC3gEF47#y+!b&3#iK|Ob9V|%;UCZ{lt)mi3qHd9wRk+0Of@Dc_lhe0r@jh)Pgoyg1R}iKa0L8Z}sF?vS*|Qvh2K902=`Di-3k?CklfZ-cM#X5HI?D_+)U)oU65U79bvz=Srao z4tBr>wEnA}D+Q1P#8>RT!ibFflKZT&->Wwt4qA?&(1P36JLek_Kh2;X4{>7R^wbr= z?(|x|d7SAVYUaNx<|1Gw2SCAAWOK2%L;LNOnTX`q%pmdHb}Qr=)*qEj&iG$RIibR2jW&JHeX zG9qI9>GG+OukAEIxE;jt#bg2?8Q{0IZAT4w%pb~gA<4CUXl=d|bS3?cjl~Jr~vel2KvrKI6GzS}}`T z*{NBJ?Dsz2`l~H~DMXrLyj=&XQgA;<&LmRmX5{q`m&@jC-r*E*<4qf%=`LMdFDRb4 zqKi~UD@|muJklHcy2`huqrrvp7;ApR?J}@cua^x9==o|k<}t?2@4tX8U5Pbv!42jY zrubngj&S>{?DR%PZ*@yi!WElr@fyCKiq=Y)4{_&eRRrmSp%Ja~*xmdh;4)w?HEgnSDnC za@BPJf;h>rg%EPUKU`|pemj2PucimP1ZWzEeNyGZTsjgWzo&$Y>Ww7U(_;_+@bq+) z{JQimOA=|spWWNG5OE(y51xgw_*r%ZzWpc?R!W{a>$#O$pb>tbU@EALr?C>oXkGCC zQ#VXqHM4SumZ-$kV0p$biK+Ms4R4u@pLp+1!s7U5_|DeDs}Xx0fqedr0%yj@=fO3X zLiTH1+B$;JfTW+u_(}Q6C$lb@_TP*-Iejk5y_LIxvdR6f>O60H_o>DKulGZn`09t= zwQiA@)h;VuFHO;tc^1$pAxpG7Y>4S7swiIBEagu<4JERPM88@v!d@bP7=K)(9qFI^@z znA01jmP^y|sQKDn&7GJz`iFUx=1^?qVoSYu5`JM*{y&dlMD<2Svz0Bec`@Sh|Jc<>{t4k% zzJ@tIk`X(*StJa$axk+vxzFX{^kyPAqMzeJmM5PLNKX+HE?2DYc3S_ZGv4x6CKbvZ z(ZL+UUa6@UP6D6oqf{P#&f(FwJD8L0B^JL7GwSM?+V0<_zt_KZik7cz8F>_lTOT`c z4Q6r80IHV<@y>!Cr2{QRiqoqLouXw6{R69>f*yynzV|ZvxfpAs#`h!EZV~G%o1|q@ zp_g4j^z*}k>d=?RBxX{7KrNYa3@EO2Hq$}p*`Zq%EBaE7dR{SVe1Qb z#Gh&>^J=kjnv}EyP}|V)>4%>?t(@T~!>b+^litDIHm+CFW_>Ovnd;EeS+e_7@&*>e zrT=TlyzTR}&-B}MvGTFSs~L`FS{zviU|7}Y*7&{)>vY}}WHNA=HLb5-KRf(>!P(Fz zLUbPox8gLb;0-V)s(Pl-d{y4GwMJ{b@|9N0!r@Gre}BLUuPmod83qZUT%XxFfHM7E z8|7r^+TwE%4s2o<7)ZTsdz1Cp!ensm$6WO!BFfl#anGl1-ZQcG#$lcg&wkVR-Z2+o zqY|#V>~!McVs1;H#`go52-E8<9M%6b1d?O;{e54uQhFyj>=Xxa#OT1v*(F(?@X40K zpp-r|-{A{tZFTD8&Q@t);qiWne@BVo*XQZJG(9a-Ihyk6_4S4D!OikB$!`wg5TD39 zUr(U3-KJBxRrw&l{N7!e;f^+x*`@i~a>ajtgpeo$_!0#8;@x7e7#}twt|^}EuUxY{ zoG0UVFi$jAHaW1l><`5qEPWj>W2E0t^qmH><>6Y`{ zbrc4Nv#`t8rZC9%K@E%kXMoY=)P5&E#xUIsrIl&X0?~-I`QbKxCD_7&^&j%5$ zLL!MLGTeVepbrNTmWntBw~P+`XQ;5mPe`2g0)3pgY=&fcUV#k(l2OJQWz+Iq3_@L_Tu%<;wg!=x&wSd z$Z|a*tje$>F_<$~ReEvJ$<$>?s?DOo`{BW&V5o^qrytSMq?BAJ$6Gh#cBIA1v^cy6 zd<#rA>@!gGC>7$29Puq2J;o`E!_w}Z#w(s6vwR;GKVL#g7`G$jB0}+|WTLOeT zaxa+%^EQa=z3lg@#YuMz*&p^H_YvW&0 zo_R811*~1K<)7BYNvAVuYoW`x`T6zjAXe52I(k(Nz2UNnVVWP~QuxFd@>kh?t317` zdWeyu6k0g!#tDWUC0060V|%-_!vM9O;rPj3NPFmK?!rscyCTPNkTG%1E`lqvqbd7z zp(V`8hEG2nZG4*?A>a@#E*EL8Q?VbeDQg1bsLd1b@&$$WzAYmSKV0SChmC8D1|GH< z_m@aM%zb^yvv?dnUIN-@AqxJ#|B2%o-=ko%QlsekNYMg=!9;>|Y=bUc402O)4R8bh z!A{7YI3*>kASI_@C3iwePEkqr%yC&+C0W_CX7McltAV%gB@g$j|9=A^R=Hx(0A`?L KiY?J{zVSc9OFX&& literal 0 HcmV?d00001 diff --git a/genai/tools/tools_test.go b/genai/tools/tools_test.go new file mode 100644 index 0000000000..0d457aeeb0 --- /dev/null +++ b/genai/tools/tools_test.go @@ -0,0 +1,71 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tools + +import ( + "bytes" + "testing" + + "github.com/GoogleCloudPlatform/golang-samples/internal/testutil" +) + +func TestTextGeneration(t *testing.T) { + tc := testutil.SystemTest(t) + + t.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "1") + t.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1") + t.Setenv("GOOGLE_CLOUD_PROJECT", tc.ProjectID) + + buf := new(bytes.Buffer) + + t.Run("generate with code execution tool", func(t *testing.T) { + buf.Reset() + err := generateWithCodeExec(buf) + if err != nil { + t.Fatalf("generateWithCodeExec failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + + t.Run("generate with code execution tool and local image input", func(t *testing.T) { + buf.Reset() + err := generateWithCodeExecAndImg(buf) + if err != nil { + t.Fatalf("generateWithCodeExecAndImg failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) + + t.Run("generate with func declaration and func response", func(t *testing.T) { + buf.Reset() + err := generateWithFuncCall(buf) + if err != nil { + t.Fatalf("generateWithFuncCall failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) +} diff --git a/go.work b/go.work index e289cefdb3..791d9a836a 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.22.7 +go 1.23.0 toolchain go1.23.4 @@ -84,6 +84,7 @@ use ( ./functions/tips ./functions/tips/infinite_retries ./functions/typed/greeting + ./genai ./getting-started ./getting-started/authenticating-users ./getting-started/background From b8e91b431504dfec3d0748ac72a0d142f1593bef Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 17 Feb 2025 15:52:17 +0100 Subject: [PATCH 2/2] feat: generate with google search --- genai/tools/google_search_with_txt.go | 67 +++++++++++++++++++++++++++ genai/tools/tools_test.go | 13 ++++++ 2 files changed, 80 insertions(+) create mode 100644 genai/tools/google_search_with_txt.go diff --git a/genai/tools/google_search_with_txt.go b/genai/tools/google_search_with_txt.go new file mode 100644 index 0000000000..9214d345ce --- /dev/null +++ b/genai/tools/google_search_with_txt.go @@ -0,0 +1,67 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tools shows examples of various tools that Gemini model can use to generate text. +package tools + +// [START googlegenaisdk_tools_google_search_with_txt] +import ( + "context" + "fmt" + "io" + + genai "google.golang.org/genai" +) + +// generateWithGoogleSearch shows how to generate text using Google Search. +func generateWithGoogleSearch(w io.Writer) error { + ctx := context.Background() + + client, err := genai.NewClient(ctx, &genai.ClientConfig{ + HTTPOptions: genai.HTTPOptions{APIVersion: "v1"}, + }) + if err != nil { + return fmt.Errorf("failed to create genai client: %w", err) + } + + modelName := "gemini-2.0-flash-001" + contents := []*genai.Content{ + {Parts: []*genai.Part{ + {Text: "When is the next total solar eclipse in the United States?"}, + }}, + } + config := &genai.GenerateContentConfig{ + Tools: []*genai.Tool{ + {GoogleSearch: &genai.GoogleSearch{}}, + }, + } + + resp, err := client.Models.GenerateContent(ctx, modelName, contents, config) + if err != nil { + return fmt.Errorf("failed to generate content: %w", err) + } + + respText, err := resp.Text() + if err != nil { + return fmt.Errorf("failed to convert model response to text: %w", err) + } + fmt.Fprintln(w, respText) + + // Example response: + // The next total solar eclipse in the United States will occur on March 30, 2033, but it will only ... + + return nil +} + +// [END googlegenaisdk_tools_google_search_with_txt] diff --git a/genai/tools/tools_test.go b/genai/tools/tools_test.go index 0d457aeeb0..92d9545132 100644 --- a/genai/tools/tools_test.go +++ b/genai/tools/tools_test.go @@ -68,4 +68,17 @@ func TestTextGeneration(t *testing.T) { t.Error("expected non-empty output, got empty") } }) + + t.Run("generate with Google Search", func(t *testing.T) { + buf.Reset() + err := generateWithGoogleSearch(buf) + if err != nil { + t.Fatalf("generateWithGoogleSearch failed: %v", err) + } + + output := buf.String() + if output == "" { + t.Error("expected non-empty output, got empty") + } + }) }