google / generative-ai-go

Go SDK for Google Generative AI
Apache License 2.0
595 stars 61 forks source link

can't set proxy #17

Closed 496672097 closed 11 months ago

496672097 commented 11 months ago

I want to set a proxy client. but it didn't work. i need U help like: client, _ = genai.NewClient(g.ctx, option.WithAPIKey("AIzaSyBwDX0zNmutFMwViEE"), option.WithHTTPClient(httpclientByProxy))

"httpclientByProxy" is a *httpclient

transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }
    client := &http.Client{
        Transport: transport,
    }
jba commented 11 months ago

I just confirmed that the proxy you pass is called from GenerateContent:

func TestProxy(t *testing.T) {
     ctx := context.Background()
    client, err := genai.NewClient(ctx,
        option.WithAPIKey("your-API-key"), option.WithHTTPClient(&http.Client{Transport: panicRT{}}))
    if err != nil {
        t.Fatal(err)
    }
    defer client.Close()
    model := client.GenerativeModel("gemini-pro")
    if _, err := model.GenerateContent(ctx, genai.Text("What is the average size of a swallow?")); err != nil {
        t.Fatal(err)
    }

}

type panicRT struct{}

func (panicRT) RoundTrip(r *http.Request) (*http.Response, error) { panic("P") }  

I get the panic when I run this code.

Maybe it isn't for a different method? Can you provide more of your code?

496672097 commented 11 months ago

bad myself. let me found the worng whith some times

496672097 commented 11 months ago

i used the example. but it not work (i can get response by POSTMAN https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=)

func test(c *http.Client) {
    ctx := context.Background()
    // Access your API key as an environment variable (see "Set up your API key" above)
    client, err := genai.NewClient(ctx, option.WithAPIKey("AIzaSyBtFMwViEE"), option.WithHTTPClient(c))
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    // For text-and-image input (multimodal), use the gemini-pro-vision model
    model := client.GenerativeModel("gemini-pro")
    prompt := genai.Text("Tell me a story about a lumberjack and his giant ox")
    iter := model.GenerateContentStream(ctx, prompt)
    for {

        resp, err := iter.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(resp.Candidates[0].Content)
    }
}

the C is :

// SetAiClientSteam 设置ai 代理
func (g *GoogleAi) SetProxy() *http.Client {
    proxyURL, err := url.Parse(g.Proxy)
    if err != nil {
        fmt.Println("出现错误:" + err.Error())
        return nil
    }
    var transport *http.Transport
    if g.Proxy != "" {
        transport = &http.Transport{
            Proxy: http.ProxyURL(proxyURL),
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify: true,
            },
        }
    } else {
        transport = &http.Transport{
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify: true,
            },
        }
    }
    client := &http.Client{Transport: transport}
    return client
}

the consle tell me "2023/12/16 18:54:46 googleapi: Error 403:"

mzmuer commented 11 months ago

I don't think it's a proxy issue, you can look at the body field of httperr in the err returned

ghj1976 commented 11 months ago

看了源码,WithAPIKey 其实就是自己实现了一套WithHTTPClient,所以这两个一起用,会冲突,导致请求时没有带apikey,解决方法就是自己封装一个。

package tools

import (
    "crypto/tls"
    "fmt"
    "net/http"
    "net/url"
)

// 整合了APIKey和代理的 Transport
type APIKeyProxyTransport struct {
    // APIKey is the API Key to set on requests.
    APIKey string

    // Transport is the underlying HTTP transport.
    // If nil, http.DefaultTransport is used.
    Transport http.RoundTripper

    // ProxyURL is the URL of the proxy server. If empty, no proxy is used.
    ProxyURL string
}

func (t *APIKeyProxyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    rt := t.Transport
    if rt == nil {
        rt = http.DefaultTransport
    }

    // 如果提供了 ProxyURL,则对 Transport 设置代理
    if t.ProxyURL != "" {
        proxyURL, err := url.Parse(t.ProxyURL)
        if err != nil {
            return nil, err
        }
        if transport, ok := rt.(*http.Transport); ok {
            // 只有当 rt 为 *http.Transport 类型时,才设置代理
            transport.Proxy = http.ProxyURL(proxyURL)
            transport.TLSClientConfig = &tls.Config{
                InsecureSkipVerify: true,
            }
        } else {
            // 如果 rt 不是 *http.Transport 类型,则创建一个新的带代理的 http.Transport
            rt = &http.Transport{
                Proxy: http.ProxyURL(proxyURL),
                TLSClientConfig: &tls.Config{
                    InsecureSkipVerify: true,
                },
            }
        }
    }

    // 克隆请求以避免修改原始请求
    newReq := *req
    args := newReq.URL.Query()
    args.Set("key", t.APIKey)
    newReq.URL.RawQuery = args.Encode()

    // 执行 HTTP 请求,并处理可能的错误
    resp, err := rt.RoundTrip(&newReq)
    if err != nil {
        // 返回网络请求中的错误
        return nil, fmt.Errorf("error during round trip: %v", err)
    }

    return resp, nil
}

使用时

    c := &http.Client{Transport: &tools.APIKeyProxyTransport{
        APIKey:    GetGeminiAPIKey(),
        Transport: nil,
        ProxyURL:  ProxyUrl,
    }}

    ctx := context.Background()
    client, err := genai.NewClient(ctx,option.WithHTTPClient(c))
chekun commented 11 months ago

Confirmed: using WithHTTPClient causes WithAPIKey to not take effect.

And @ghj1976 's solution helps.

496672097 commented 11 months ago

谢谢老大哥, 可以解决了。

nihao87224 commented 3 months ago

看了源码,WithAPIKey 其实就是自己实现了一套WithHTTPClient,所以这两个一起用,会冲突,导致请求时没有带apikey,解决方法就是自己封装一个。

package tools

import (
  "crypto/tls"
  "fmt"
  "net/http"
  "net/url"
)

// 整合了APIKey和代理的 Transport
type APIKeyProxyTransport struct {
  // APIKey is the API Key to set on requests.
  APIKey string

  // Transport is the underlying HTTP transport.
  // If nil, http.DefaultTransport is used.
  Transport http.RoundTripper

  // ProxyURL is the URL of the proxy server. If empty, no proxy is used.
  ProxyURL string
}

func (t *APIKeyProxyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  rt := t.Transport
  if rt == nil {
      rt = http.DefaultTransport
  }

  // 如果提供了 ProxyURL,则对 Transport 设置代理
  if t.ProxyURL != "" {
      proxyURL, err := url.Parse(t.ProxyURL)
      if err != nil {
          return nil, err
      }
      if transport, ok := rt.(*http.Transport); ok {
          // 只有当 rt 为 *http.Transport 类型时,才设置代理
          transport.Proxy = http.ProxyURL(proxyURL)
          transport.TLSClientConfig = &tls.Config{
              InsecureSkipVerify: true,
          }
      } else {
          // 如果 rt 不是 *http.Transport 类型,则创建一个新的带代理的 http.Transport
          rt = &http.Transport{
              Proxy: http.ProxyURL(proxyURL),
              TLSClientConfig: &tls.Config{
                  InsecureSkipVerify: true,
              },
          }
      }
  }

  // 克隆请求以避免修改原始请求
  newReq := *req
  args := newReq.URL.Query()
  args.Set("key", t.APIKey)
  newReq.URL.RawQuery = args.Encode()

  // 执行 HTTP 请求,并处理可能的错误
  resp, err := rt.RoundTrip(&newReq)
  if err != nil {
      // 返回网络请求中的错误
      return nil, fmt.Errorf("error during round trip: %v", err)
  }

  return resp, nil
}

使用时

  c := &http.Client{Transport: &tools.APIKeyProxyTransport{
      APIKey:    GetGeminiAPIKey(),
      Transport: nil,
      ProxyURL:  ProxyUrl,
  }}

  ctx := context.Background()
  client, err := genai.NewClient(ctx,option.WithHTTPClient(c))

It doesn't work because https://github.com/google/generative-ai-go/pull/152