搭建ChatGPT3.5 API的 体验网站

最近,chatgpt很吸引我,感觉很有意思,出于技术热情自己也搭建了一个
效果图如下:搭建ChatGPT3.5 API的 体验网站_第1张图片
使用前提:有openai账号,并且创建好api_key,注册事项可以参考 https://juejin.cn/post/7173447848292253704

配置文件说明

{
  "api_key": "your api key",
  "api_url": "",
  "port": 8080,
  "listen": "",
  "bot_desc": "你是一个AI助手,我需要你模拟一名温柔贴心的女朋友来回答我的问题。",
  "proxy": "http://host.docker.internal:10809",
  "model": "gpt-3.5-turbo-0301",
  "max_tokens": 512,
  "temperature": 0.9,
  "top_p": 1,
  "frequency_penalty": 0.0,
  "presence_penalty": 0.6,
  "auth_user": "",
  "auth_password": ""
}
api_key:openai api_key
api_url: openai api接口地址 不填使用默认 https://api.openai.com/v1 
port: http服务端口
listen: http服务监听地址,不填默认监听0.0.0.0
proxy: openai请求代理,防墙。 例如 http://127.0.0.1:7890 socks5://127.0.0.1:7890
bot_desc:AI特征,非常重要,功能等同给与AI一个身份设定
max_tokens: GPT响应字符数,最大2048,默认值512。max_tokens会影响接口响应速度,字符越大响应越慢。
model: GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
temperature: GPT热度,0到1,默认0.9。数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
top_p: 使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的代币。
frequency_penalty:
presence_penalty:
auth_user": http基本认证用户名(空表示不开启验证)
auth_password": http基本认证密码

代理服务器,如果不配置国内无法访问 https://api.openai.com/v1
代理服务器是一个中间服务器,可以使客户端在请求网络资源时间接地访问互联网。代理服务器常用于访问受限制的网站、匿名浏览和安全访问等场景。

在网络编程中,如果需要通过代理服务器进行 HTTP 请求,可以通过在 Transport 中指定一个代理地址,然后使用 http.ProxyURL() 函数将地址转换为 Proxy 对象。例如,如果代理地址为 “http://127.0.0.1:7890”,可以使用以下代码将其转换为 Proxy 对象:

proxyUrl, err := url.Parse("http://127.0.0.1:7890")
if err != nil {
    log.Fatal(err)
}
proxy := http.ProxyURL(proxyUrl)

在创建 HTTP 客户端时,可以将这个代理对象设置到 Transport 中的 Proxy 字段中,如下所示:

client := &http.Client{
    Transport: &http.Transport{
        Proxy: proxy,
    },
}

这样,在客户端进行 HTTP 请求时,就会使用指定的代理服务器。需要注意的是,代理地址的格式应该符合 URL 规范,例如上面的示例中,代理地址前面需要加上 “http://” 前缀。如果需要使用 socks5 协议的代理服务器,地址应该以 “socks5://” 开头。

关键代码如下:

package controllers

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	"golang.org/x/net/proxy"

	"github.com/869413421/chatgpt-web/config"
	"github.com/869413421/chatgpt-web/pkg/logger"
	"github.com/gin-gonic/gin"
	gogpt "github.com/sashabaranov/go-gpt3"
)

// ChatController 首页控制器
type ChatController struct {
	BaseController
}

// NewChatController 创建控制器
func NewChatController() *ChatController {
	return &ChatController{}
}

// Index 首页
func (c *ChatController) Index(ctx *gin.Context) {
	ctx.HTML(http.StatusOK, "index.html", gin.H{
		"title": "Main website",
	})
}

// Completion 回复
func (c *ChatController) Completion(ctx *gin.Context) {
	var request gogpt.ChatCompletionRequest
	err := ctx.BindJSON(&request)
	if err != nil {
		c.ResponseJson(ctx, http.StatusInternalServerError, err.Error(), nil)
		return
	}
	logger.Info(request)
	if len(request.Messages) == 0 {
		c.ResponseJson(ctx, http.StatusBadRequest, "request messages required", nil)
		return
	}
	cnf := config.LoadConfig()
	gptConfig := gogpt.DefaultConfig(cnf.ApiKey)
	if cnf.Proxy != "" {
		transport := &http.Transport{}

		if strings.HasPrefix(cnf.Proxy, "socks5h://") {
			// 创建一个 DialContext 对象,并设置代理服务器
			dialContext, err := newDialContext(cnf.Proxy[10:])
			if err != nil {
				panic(err)
			}
			transport.DialContext = dialContext
		} else {
			// 创建一个 HTTP Transport 对象,并设置代理服务器
			proxyUrl, err := url.Parse(cnf.Proxy)
			if err != nil {
				panic(err)
			}
			transport.Proxy = http.ProxyURL(proxyUrl)
		}
		// 创建一个 HTTP 客户端,并将 Transport 对象设置为其 Transport 字段
		gptConfig.HTTPClient = &http.Client{
			Transport: transport,
		}

	}
	// 自定义gptConfig.BaseURL
	if cnf.ApiURL != "" {
		gptConfig.BaseURL = cnf.ApiURL
	}
	client := gogpt.NewClientWithConfig(gptConfig)
	if request.Messages[0].Role != "system" {
		newMessage := append([]gogpt.ChatCompletionMessage{
			{Role: "system", Content: cnf.BotDesc},
		}, request.Messages...)
		request.Messages = newMessage
		logger.Info(request.Messages)
	}
	if cnf.Model == gogpt.GPT3Dot5Turbo0301 || cnf.Model == gogpt.GPT3Dot5Turbo {
		request.Model = cnf.Model
		resp, err := client.CreateChatCompletion(ctx, request)
		if err != nil {
			c.ResponseJson(ctx, http.StatusInternalServerError, err.Error(), nil)
			return
		}
		c.ResponseJson(ctx, http.StatusOK, "", gin.H{
			"reply":    resp.Choices[0].Message.Content,
			"messages": append(request.Messages, resp.Choices[0].Message),
		})
	} else {
		prompt := ""
		for _, item := range request.Messages {
			prompt += item.Content + "/n"
		}
		prompt = strings.Trim(prompt, "/n")

		logger.Info("request prompt is %s", prompt)
		req := gogpt.CompletionRequest{
			Model:            cnf.Model,
			MaxTokens:        cnf.MaxTokens,
			TopP:             cnf.TopP,
			FrequencyPenalty: cnf.FrequencyPenalty,
			PresencePenalty:  cnf.PresencePenalty,
			Prompt:           prompt,
		}

		resp, err := client.CreateCompletion(ctx, req)
		if err != nil {
			c.ResponseJson(ctx, http.StatusInternalServerError, err.Error(), nil)
			return
		}

		c.ResponseJson(ctx, http.StatusOK, "", gin.H{
			"reply": resp.Choices[0].Text,
			"messages": append(request.Messages, gogpt.ChatCompletionMessage{
				Role:    "assistant",
				Content: resp.Choices[0].Text,
			}),
		})
	}

}

type dialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)

func newDialContext(socks5 string) (dialContextFunc, error) {
	baseDialer := &net.Dialer{
		Timeout:   60 * time.Second,
		KeepAlive: 60 * time.Second,
	}

	if socks5 != "" {
		// split socks5 proxy string [username:password@]host:port
		var auth *proxy.Auth = nil

		if strings.Contains(socks5, "@") {
			proxyInfo := strings.SplitN(socks5, "@", 2)
			proxyUser := strings.Split(proxyInfo[0], ":")
			if len(proxyUser) == 2 {
				auth = &proxy.Auth{
					User:     proxyUser[0],
					Password: proxyUser[1],
				}
			}
			socks5 = proxyInfo[1]
		}

		dialSocksProxy, err := proxy.SOCKS5("tcp", socks5, auth, baseDialer)
		if err != nil {
			return nil, err
		}

		contextDialer, ok := dialSocksProxy.(proxy.ContextDialer)
		if !ok {
			return nil, err
		}

		return contextDialer.DialContext, nil
	} else {
		return baseDialer.DialContext, nil
	}
}

你可能感兴趣的:(gpt-3)