feat(openai): support azure api type #475

This commit is contained in:
Jacky 2024-12-15 21:02:31 +08:00
parent 835349c33f
commit ad97f973ab
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
23 changed files with 4726 additions and 3837 deletions

View file

@ -4,14 +4,13 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/0xJacky/Nginx-UI/internal/chatbot" "github.com/0xJacky/Nginx-UI/internal/chatbot"
"github.com/0xJacky/Nginx-UI/internal/transport"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"github.com/uozi-tech/cosy" "github.com/uozi-tech/cosy"
"github.com/uozi-tech/cosy/logger"
"io" "io"
"net/http"
) )
const ChatGPTInitPrompt = `You are a assistant who can help users write and optimise the configurations of Nginx, const ChatGPTInitPrompt = `You are a assistant who can help users write and optimise the configurations of Nginx,
@ -49,10 +48,7 @@ func MakeChatCompletionRequest(c *gin.Context) {
c.Writer.Header().Set("Connection", "keep-alive") c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
config := openai.DefaultConfig(settings.OpenAISettings.Token) openaiClient, err := chatbot.GetClient()
if settings.OpenAISettings.Proxy != "" {
t, err := transport.NewTransport(transport.WithProxy(settings.OpenAISettings.Proxy))
if err != nil { if err != nil {
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
c.SSEvent("message", gin.H{ c.SSEvent("message", gin.H{
@ -63,16 +59,7 @@ func MakeChatCompletionRequest(c *gin.Context) {
}) })
return return
} }
config.HTTPClient = &http.Client{
Transport: t,
}
}
if settings.OpenAISettings.BaseUrl != "" {
config.BaseURL = settings.OpenAISettings.BaseUrl
}
openaiClient := openai.NewClientWithConfig(config)
ctx := context.Background() ctx := context.Background()
req := openai.ChatCompletionRequest{ req := openai.ChatCompletionRequest{
@ -82,7 +69,7 @@ func MakeChatCompletionRequest(c *gin.Context) {
} }
stream, err := openaiClient.CreateChatCompletionStream(ctx, req) stream, err := openaiClient.CreateChatCompletionStream(ctx, req)
if err != nil { if err != nil {
fmt.Printf("CompletionStream error: %v\n", err) logger.Errorf("CompletionStream error: %v\n", err)
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
c.SSEvent("message", gin.H{ c.SSEvent("message", gin.H{
"type": "error", "type": "error",
@ -99,12 +86,11 @@ func MakeChatCompletionRequest(c *gin.Context) {
for { for {
response, err := stream.Recv() response, err := stream.Recv()
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
fmt.Println()
return return
} }
if err != nil { if err != nil {
fmt.Printf("Stream error: %v\n", err) logger.Errorf("Stream error: %v\n", err)
return return
} }

View file

@ -72,6 +72,7 @@ export interface OpenaiSettings {
base_url: string base_url: string
proxy: string proxy: string
token: string token: string
api_type: string
} }
export interface TerminalSettings { export interface TerminalSettings {

View file

@ -1 +1 @@
ar en zh_CN zh_TW fr_FR es ru_RU vi_VN ko_KR tr_TR en zh_CN zh_TW fr_FR es ru_RU vi_VN ko_KR tr_TR ar

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ const errors: Record<string, Record<string, string>> = inject('errors') as Recor
:label="$gettext('Node name')" :label="$gettext('Node name')"
:validate-status="errors?.node?.name ? 'error' : ''" :validate-status="errors?.node?.name ? 'error' : ''"
:help="errors?.node?.name.includes('safety_text') :help="errors?.node?.name.includes('safety_text')
? $gettext('The node name should only contain letters, unicode, numbers, hyphens, dashes, and dots.') ? $gettext('The node name should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
: $gettext('Customize the name of local node to be displayed in the environment indicator.')" : $gettext('Customize the name of local node to be displayed in the environment indicator.')"
> >
<AInput v-model:value="data.node.name" /> <AInput v-model:value="data.node.name" />
@ -51,7 +51,7 @@ const errors: Record<string, Record<string, string>> = inject('errors') as Recor
:label="$gettext('ICP Number')" :label="$gettext('ICP Number')"
:validate-status="errors?.node?.icp_number ? 'error' : ''" :validate-status="errors?.node?.icp_number ? 'error' : ''"
:help="errors?.node?.icp_number.includes('safety_text') :help="errors?.node?.icp_number.includes('safety_text')
? $gettext('The ICP Number should only contain letters, unicode, numbers, hyphens, dashes, and dots.') ? $gettext('The ICP Number should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
: ''" : ''"
> >
<AInput <AInput
@ -63,7 +63,7 @@ const errors: Record<string, Record<string, string>> = inject('errors') as Recor
:label="$gettext('Public Security Number')" :label="$gettext('Public Security Number')"
:validate-status="errors?.node?.public_security_number ? 'error' : ''" :validate-status="errors?.node?.public_security_number ? 'error' : ''"
:help="errors?.node?.public_security_number.includes('safety_text') :help="errors?.node?.public_security_number.includes('safety_text')
? $gettext('The Public Security Number should only contain letters, unicode, numbers, hyphens, dashes, and dots.') ? $gettext('The Public Security Number should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
: ''" : ''"
> >
<AInput <AInput

View file

@ -32,7 +32,7 @@ const models = shallowRef([
:label="$gettext('Model')" :label="$gettext('Model')"
:validate-status="errors?.openai?.model ? 'error' : ''" :validate-status="errors?.openai?.model ? 'error' : ''"
:help="errors?.openai?.model === 'safety_text' :help="errors?.openai?.model === 'safety_text'
? $gettext('The model name should only contain letters, unicode, numbers, hyphens, dashes, and dots.') ? $gettext('The model name should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
: ''" : ''"
> >
<AAutoComplete <AAutoComplete
@ -45,7 +45,7 @@ const models = shallowRef([
:validate-status="errors?.openai?.base_url ? 'error' : ''" :validate-status="errors?.openai?.base_url ? 'error' : ''"
:help="errors?.openai?.base_url === 'url' :help="errors?.openai?.base_url === 'url'
? $gettext('The url is invalid.') ? $gettext('The url is invalid.')
: $gettext('To use a local large model, deploy it with vllm or imdeploy. ' : $gettext('To use a local large model, deploy it with ollama, vllm or imdeploy. '
+ 'They provide an OpenAI-compatible API endpoint, so just set the baseUrl to your local API.')" + 'They provide an OpenAI-compatible API endpoint, so just set the baseUrl to your local API.')"
> >
<AInput <AInput
@ -74,6 +74,19 @@ const models = shallowRef([
> >
<AInputPassword v-model:value="data.openai.token" /> <AInputPassword v-model:value="data.openai.token" />
</AFormItem> </AFormItem>
<AFormItem
:label="$gettext('API Type')"
:validate-status="errors?.openai?.apt_type ? 'error' : ''"
>
<ASelect v-model:value="data.openai.api_type">
<ASelectOption value="OPEN_AI">
OpenAI
</ASelectOption>
<ASelectOption value="AZURE">
Azure
</ASelectOption>
</ASelect>
</AFormItem>
</AForm> </AForm>
</template> </template>

View file

@ -77,6 +77,7 @@ const data = ref<Settings>({
base_url: '', base_url: '',
proxy: '', proxy: '',
token: '', token: '',
api_type: 'OPEN_AI',
}, },
terminal: { terminal: {
start_cmd: '', start_cmd: '',

3
go.mod
View file

@ -35,7 +35,7 @@ require (
github.com/spf13/cast v1.7.0 github.com/spf13/cast v1.7.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tufanbarisyildirim/gonginx v0.0.0-20241205102811-323481085fb4 github.com/tufanbarisyildirim/gonginx v0.0.0-20241205102811-323481085fb4
github.com/uozi-tech/cosy v1.12.3 github.com/uozi-tech/cosy v1.12.5
github.com/uozi-tech/cosy-driver-sqlite v0.2.0 github.com/uozi-tech/cosy-driver-sqlite v0.2.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.31.0
@ -106,7 +106,6 @@ require (
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dnsimple/dnsimple-go v1.7.0 // indirect github.com/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/ebitengine/purego v0.8.1 // indirect github.com/ebitengine/purego v0.8.1 // indirect
github.com/elliotchance/orderedmap/v2 v2.5.0 // indirect
github.com/exoscale/egoscale/v3 v3.1.7 // indirect github.com/exoscale/egoscale/v3 v3.1.7 // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect

6
go.sum
View file

@ -856,8 +856,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elliotchance/orderedmap/v2 v2.5.0 h1:WRPmWGChucaZ09eEd3UkU8XfVajv6ZZ6eg3+x0cLWPM=
github.com/elliotchance/orderedmap/v2 v2.5.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/elliotchance/orderedmap/v3 v3.0.0 h1:Yay/tDjX+vzza+Drcoo8VEbuBnOYGpgenCXWcpQSFDg= github.com/elliotchance/orderedmap/v3 v3.0.0 h1:Yay/tDjX+vzza+Drcoo8VEbuBnOYGpgenCXWcpQSFDg=
github.com/elliotchance/orderedmap/v3 v3.0.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/elliotchance/orderedmap/v3 v3.0.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -1771,8 +1769,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
github.com/uozi-tech/cosy v1.12.3 h1:0Nii/OYKOsXOy/x6l8f0g0RuHj7t1vkqQQv6xmitZsU= github.com/uozi-tech/cosy v1.12.5 h1:rX7mVj4KKuI+xnpNor3BuFsnX6f8nUzeEFgA//gjywo=
github.com/uozi-tech/cosy v1.12.3/go.mod h1:zRYGFp//aDvrS6mOA91qWQSGPrSfVjuomnhdEhcdP8Y= github.com/uozi-tech/cosy v1.12.5/go.mod h1:Q597nSDM8yAnW8yKfcWBcPU+fRfEpxXA0ZjsSse88Tc=
github.com/uozi-tech/cosy-driver-mysql v0.2.2 h1:22S/XNIvuaKGqxQPsYPXN8TZ8hHjCQdcJKVQ83Vzxoo= github.com/uozi-tech/cosy-driver-mysql v0.2.2 h1:22S/XNIvuaKGqxQPsYPXN8TZ8hHjCQdcJKVQ83Vzxoo=
github.com/uozi-tech/cosy-driver-mysql v0.2.2/go.mod h1:EZnRIbSj1V5U0gEeTobrXai/d1SV11lkl4zP9NFEmyE= github.com/uozi-tech/cosy-driver-mysql v0.2.2/go.mod h1:EZnRIbSj1V5U0gEeTobrXai/d1SV11lkl4zP9NFEmyE=
github.com/uozi-tech/cosy-driver-postgres v0.2.1 h1:OICakGuT+omva6QOJCxTJ5Lfr7CGXLmk/zD+aS51Z2o= github.com/uozi-tech/cosy-driver-postgres v0.2.1 h1:OICakGuT+omva6QOJCxTJ5Lfr7CGXLmk/zD+aS51Z2o=

View file

@ -0,0 +1,33 @@
package chatbot
import (
"github.com/0xJacky/Nginx-UI/internal/transport"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/sashabaranov/go-openai"
"net/http"
)
func GetClient() (*openai.Client, error) {
var config openai.ClientConfig
if openai.APIType(settings.OpenAISettings.APIType) == openai.APITypeAzure {
config = openai.DefaultAzureConfig(settings.OpenAISettings.Token, settings.OpenAISettings.BaseUrl)
} else {
config = openai.DefaultConfig(settings.OpenAISettings.Token)
}
if settings.OpenAISettings.Proxy != "" {
t, err := transport.NewTransport(transport.WithProxy(settings.OpenAISettings.Proxy))
if err != nil {
return nil, err
}
config.HTTPClient = &http.Client{
Transport: t,
}
}
if settings.OpenAISettings.BaseUrl != "" {
config.BaseURL = settings.OpenAISettings.BaseUrl
}
return openai.NewClientWithConfig(config), nil
}

View file

@ -5,7 +5,6 @@ import (
) )
func ChatCompletionWithContext(filename string, messages []openai.ChatCompletionMessage) []openai.ChatCompletionMessage { func ChatCompletionWithContext(filename string, messages []openai.ChatCompletionMessage) []openai.ChatCompletionMessage {
for i := len(messages) - 1; i >= 0; i-- { for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Role == openai.ChatMessageRoleUser { if messages[i].Role == openai.ChatMessageRoleUser {
// openai.ChatCompletionMessage: can't use both Content and MultiContent properties simultaneously // openai.ChatCompletionMessage: can't use both Content and MultiContent properties simultaneously

View file

@ -12,12 +12,7 @@ func Init() {
logger.Fatal("failed to initialize binding validator engine") logger.Fatal("failed to initialize binding validator engine")
} }
err := v.RegisterValidation("safety_text", safetyText) err := v.RegisterValidation("certificate", isCertificate)
if err != nil {
logger.Fatal(err)
}
err = v.RegisterValidation("certificate", isCertificate)
if err != nil { if err != nil {
logger.Fatal(err) logger.Fatal(err)
} }

View file

@ -1,10 +1,15 @@
package settings package settings
import "github.com/sashabaranov/go-openai"
type OpenAI struct { type OpenAI struct {
BaseUrl string `json:"base_url" binding:"omitempty,url"` BaseUrl string `json:"base_url" binding:"omitempty,url"`
Token string `json:"token" binding:"omitempty,safety_text"` Token string `json:"token" binding:"omitempty,safety_text"`
Proxy string `json:"proxy" binding:"omitempty,url"` Proxy string `json:"proxy" binding:"omitempty,url"`
Model string `json:"model" binding:"omitempty,safety_text"` Model string `json:"model" binding:"omitempty,safety_text"`
APIType string `json:"api_type" binding:"omitempty,oneof=OPEN_AI AZURE"`
} }
var OpenAISettings = &OpenAI{} var OpenAISettings = &OpenAI{
APIType: string(openai.APITypeOpenAI),
}