Compare commits
12 Commits
dev
...
feat/claud
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e33d22c23 | ||
|
|
f74ba295ee | ||
|
|
14cf6d01ed | ||
|
|
95a7d8634f | ||
|
|
71872171c9 | ||
|
|
69cd825180 | ||
|
|
799b177c4f | ||
|
|
617485f7e2 | ||
|
|
d9ecd1ea74 | ||
|
|
de31741d06 | ||
|
|
7df0b2817c | ||
|
|
173436f2a3 |
19
.github/workflows/ci.yaml
vendored
19
.github/workflows/ci.yaml
vendored
@@ -1,25 +1,28 @@
|
||||
name: Docker Image CI
|
||||
|
||||
#触发器设置
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
#项目任务,任务之间可以并行调度
|
||||
jobs:
|
||||
|
||||
build:
|
||||
#选择云端运行的环境
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
#uses代表使用一个模块,此处使用的是checkout模块,将github项目文件导入到当前环境中
|
||||
- uses: actions/checkout@v3
|
||||
#使用with跟在后面来为前面的模块输入参数
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "today=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
run: echo "::set-output name=today::$(date +'%Y%m%d')"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -28,20 +31,24 @@ jobs:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
#这里用到了github的secrets功能,避免账户和密码随仓库泄露
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
#导入这个模块来完成自动编译和推送
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: true
|
||||
#在这里通过加入需要编译的平台和前面配好的QEMU,buildx来达到多平台编译
|
||||
platforms: linux/amd64,linux/arm64
|
||||
#指定用户/仓库名
|
||||
tags: |
|
||||
${{ github.repository }}:${{ steps.date.outputs.today }}
|
||||
${{ github.repository }}:${{ contains(github.ref,'main') && 'latest' || github.ref_name }}
|
||||
- name: Docker Hub Description
|
||||
#这里是通过md文件自动生成dockerhub描述的模块,也可以不需要
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
|
||||
23
.github/workflows/tag.yaml
vendored
23
.github/workflows/tag.yaml
vendored
@@ -1,24 +1,27 @@
|
||||
name: Docker Image CI Tag
|
||||
name: Docker Image CI
|
||||
#触发器设置
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
#项目任务,任务之间可以并行调度
|
||||
jobs:
|
||||
|
||||
build:
|
||||
#选择云端运行的环境
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
#uses代表使用一个模块,此处使用的是checkout模块,将github项目文件导入到当前环境中
|
||||
- uses: actions/checkout@v3
|
||||
#使用with跟在后面来为前面的模块输入参数
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Get version
|
||||
- name: Get version # 获取 Tag Version
|
||||
id: vars
|
||||
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
|
||||
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -27,20 +30,24 @@ jobs:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
#这里用到了github的secrets功能,避免账户和密码随仓库泄露
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
#导入这个模块来完成自动编译和推送
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: true
|
||||
#在这里通过加入需要编译的平台和前面配好的QEMU,buildx来达到多平台编译
|
||||
platforms: linux/amd64,linux/arm64
|
||||
#指定用户/仓库名
|
||||
tags: |
|
||||
${{ github.repository }}:${{ steps.vars.outputs.tag }}
|
||||
${{ github.repository }}:latest
|
||||
- name: Docker Hub Description
|
||||
#这里是通过md文件自动生成dockerhub描述的模块,也可以不需要
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,4 +2,3 @@ bin/
|
||||
test/
|
||||
*.log
|
||||
*.db
|
||||
demo/
|
||||
33
README.md
33
README.md
@@ -1,13 +1,8 @@
|
||||
# ~~opencatd-open~~ [OpenTeam](https://github.com/mirrors2/opencatd-open)
|
||||
|
||||
本项目即将更名,后续请关注 👉🏻 https://github.com/mirrors2/openteam
|
||||
|
||||
# opencatd-open
|
||||
|
||||
<a title="Docker Image CI" target="_blank" href="https://github.com/mirrors2/opencatd-open/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/mirrors2/opencatd-open/ci.yaml?label=Actions&logo=github&style=flat-square"></a>
|
||||
<a title="Docker Pulls" target="_blank" href="https://hub.docker.com/r/mirrors2/opencatd-open"><img src="https://img.shields.io/docker/pulls/mirrors2/opencatd-open.svg?logo=docker&label=docker&style=flat-square"></a>
|
||||
|
||||
[](https://t.me/OpenTeamChat) [](https://t.me/OpenTeamLLM)
|
||||
|
||||
opencatd-open is an open-source, team-shared service for ChatGPT API that can be safely shared with others for API usage.
|
||||
|
||||
---
|
||||
@@ -15,15 +10,11 @@ OpenCat for Team的开源实现
|
||||
|
||||
~~基本~~实现了opencatd的全部功能
|
||||
|
||||
(openai附属能力:whisper,tts,dall-e(text to image)...)
|
||||
|
||||
## Extra Support:
|
||||
|
||||
| 🎯 | 🚧 |Extra Provider|
|
||||
| --- | --- | --- |
|
||||
|[OpenAI](./doc/azure.md) | ✅|Azure, Github Marketplace|
|
||||
|[Claude](./doc/azure.md) | ✅|VertexAI|
|
||||
|[Gemini](./doc/gemini.md) | ✅||
|
||||
| 任务 | 完成情况 |
|
||||
| --- | --- |
|
||||
|[Azure OpenAI](./doc/azure.md) | ✅|
|
||||
| ... | ... |
|
||||
|
||||
|
||||
@@ -85,20 +76,6 @@ wget https://github.com/mirrors2/opencatd-open/raw/main/docker/docker-compose.ym
|
||||
|
||||
pandora for team
|
||||
- [pandora for team](./doc/pandora.md)
|
||||
|
||||
如何自定义HOST地址? (仅OpenAI)
|
||||
- 需修改环境变量,优先级递增
|
||||
- Cloudflare AI Gateway地址 `AIGateWay_Endpoint=https://gateway.ai.cloudflare.com/v1/123456789/xxxx/openai/chat/completions`
|
||||
- 自定义的endpoint `$CUSTOM_ENDPOINT=true && $OpenAI_Endpoint=https://your.domain/v1/chat/completions`
|
||||
|
||||
设置主页跳转地址?
|
||||
- 修改环境变量 `CUSTOM_REDIRECT=https://your.domain`
|
||||
## 获取更多信息
|
||||
[](https://t.me/OpenTeamLLM)
|
||||
|
||||
## 赞助
|
||||
[](https://www.buymeacoffee.com/littlecjun)
|
||||
|
||||
# License
|
||||
|
||||
[](https://github.com/mirrors2/opencatd-open/blob/main/License)
|
||||
[GNU General Public License v3.0](License)
|
||||
@@ -181,7 +181,7 @@ Req:
|
||||
|
||||
}
|
||||
```
|
||||
api_type:不传的话默认为“openai”;当前可选值[openai,azure,claude]
|
||||
api_type:不传的话默认为“openai”;当前可选值[openai,azure_openai]
|
||||
endpoint: 当 api_type 为 azure_openai时传入(目前暂未使用)
|
||||
|
||||
Resp:
|
||||
@@ -236,7 +236,4 @@ Resp:
|
||||
"totalUnit" : 55
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Whisper接口
|
||||
### 与openai一致
|
||||
```
|
||||
@@ -19,7 +19,3 @@
|
||||
- [AMA(问天)](http://bytemyth.com/ama) 使用方式
|
||||
- 
|
||||
- 每个 team server 用户旁边有一个复制按钮,点击后,把复制的链接粘贴到浏览器,可以一键设置
|
||||
|
||||
## Claude
|
||||
|
||||
- opencat 添加Claude api, key name以 "claude.key名称",即("Api类型.Key名称")
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
## 添加ApiKey
|
||||
gemini的"ApiType":"google"
|
||||
|
||||
或者使用 google.xxxx 的apikey名称 添加
|
||||

|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 39 KiB |
@@ -1,26 +0,0 @@
|
||||
# Email: admin@example.com
|
||||
# Password: changeme
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
npm:
|
||||
image: jc21/nginx-proxy-manager
|
||||
network_mode: host
|
||||
ports:
|
||||
- '80:80'
|
||||
- '81:81'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- $PWD/data:/data
|
||||
- $PWD/www:/var/www
|
||||
- $PWD/letsencrypt:/etc/letsencrypt
|
||||
environment:
|
||||
- "TZ=Asia/Shanghai" # set timezone, default UTC
|
||||
- "PUID=1000" # set group id, default 0 (root)
|
||||
- "PGID=1000"
|
||||
|
||||
# certbot:
|
||||
# image: certbot/certbot
|
||||
# volumes:
|
||||
# - $PWD/data/certbot/conf:/etc/letsencrypt
|
||||
# - $PWD/data/certbot/www:/var/www/certbot
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM node:20-alpine AS frontend
|
||||
FROM node:18.12.1-alpine3.16 AS frontend
|
||||
WORKDIR /frontend-build
|
||||
COPY ./web/ .
|
||||
RUN npm install && npm run build && rm -rf node_modules
|
||||
|
||||
FROM golang:1.23-alpine as builder
|
||||
FROM golang:1.19-alpine as builder
|
||||
LABEL anther="github.com/Sakurasan"
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk --no-cache add make cmake upx
|
||||
WORKDIR /build
|
||||
@@ -11,7 +11,7 @@ COPY --from=frontend /frontend-build/dist /build/dist
|
||||
COPY . /build
|
||||
ENV GO111MODULE=on
|
||||
# ENV GOPROXY=https://goproxy.cn,direct
|
||||
CMD [ "go mod tidy","go mod download" ]
|
||||
CMD [ "go mod download" ]
|
||||
RUN make build
|
||||
|
||||
FROM alpine:latest AS runner
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
services:
|
||||
opencatd:
|
||||
image: mirrors2/opencatd-open
|
||||
container_name: opencatd-open
|
||||
container_name: opencatd-open
|
||||
restart: unless-stopped
|
||||
#network_mode: host
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- $PWD/db:/app/db
|
||||
logging:
|
||||
# driver: "json-file"
|
||||
options:
|
||||
max-size: 10m
|
||||
max-file: 3
|
||||
# environment:
|
||||
# Vertex: |
|
||||
# {
|
||||
# "type": "service_account",
|
||||
# "universe_domain": "googleapis.com"
|
||||
# }
|
||||
|
||||
|
||||
- /etc/opencatd:/app/db
|
||||
105
go.mod
105
go.mod
@@ -1,100 +1,57 @@
|
||||
module opencatd-open
|
||||
|
||||
go 1.23.2
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
cloud.google.com/go/vertexai v0.13.1
|
||||
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d
|
||||
github.com/coder/websocket v1.8.12
|
||||
github.com/duke-git/lancet/v2 v2.3.3
|
||||
github.com/duke-git/lancet/v2 v2.2.3
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/glebarez/sqlite v1.8.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/generative-ai-go v0.18.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkoukk/tiktoken-go v0.1.7
|
||||
github.com/sashabaranov/go-openai v1.32.2
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
golang.org/x/sync v0.8.0
|
||||
google.golang.org/api v0.201.0
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.2.0
|
||||
gorm.io/gorm v1.25.12
|
||||
github.com/pkoukk/tiktoken-go v0.1.4
|
||||
github.com/sashabaranov/go-openai v1.13.0
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.1.1
|
||||
gorm.io/gorm v1.25.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/ai v0.8.2 // indirect
|
||||
cloud.google.com/go/aiplatform v1.68.0 // indirect
|
||||
cloud.google.com/go/auth v0.9.8 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
cloud.google.com/go/iam v1.2.1 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/bytedance/sonic v1.9.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||
github.com/gin-contrib/cors v1.7.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
|
||||
go.opentelemetry.io/otel v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
||||
golang.org/x/arch v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.61.0 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.33.1 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
||||
361
go.sum
361
go.sum
@@ -1,155 +1,61 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
||||
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
|
||||
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
|
||||
cloud.google.com/go/ai v0.8.2 h1:LEaQwqBv+k2ybrcdTtCTc9OPZXoEdcQaGrfvDYS6Bnk=
|
||||
cloud.google.com/go/ai v0.8.2/go.mod h1:Wb3EUUGWwB6yHBaUf/+oxUq/6XbCaU1yh0GrwUS8lr4=
|
||||
cloud.google.com/go/aiplatform v1.68.0 h1:EPPqgHDJpBZKRvv+OsB3cr0jYz3EL2pZ+802rBPcG8U=
|
||||
cloud.google.com/go/aiplatform v1.68.0/go.mod h1:105MFA3svHjC3Oazl7yjXAmIR89LKhRAeNdnDKJczME=
|
||||
cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8=
|
||||
cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
|
||||
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 v1.28.1 h1:XwPcZjgMCnU2tkwY10VleUjSAfpTj9RDn+kGrbYsi8o=
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU=
|
||||
cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g=
|
||||
cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc=
|
||||
cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0=
|
||||
cloud.google.com/go/vertexai v0.13.1 h1:E6I+eA6vNQxz7/rb0wdILdKg4hFmMNWZLp+dSy9DnEo=
|
||||
cloud.google.com/go/vertexai v0.13.1/go.mod h1:25DzKFzP9JByYxcNjJefu/px2dRjcRpCDSdULYL2avI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d h1:3v1QFdgk450QH+7C+lw1k+olbjK4fKGsrEfnEG/HLkY=
|
||||
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d/go.mod h1:2sp0vsMyh5sqmKl5N+ps/cSspqLkoXUlesSzsufIGRU=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/duke-git/lancet/v2 v2.3.2 h1:Cv+uNkx5yGqDSvGc5Vu9eiiZobsPIf0Ng7NGy5hEdow=
|
||||
github.com/duke-git/lancet/v2 v2.3.2/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/duke-git/lancet/v2 v2.3.3 h1:OhqzNzkbJBS9ZlWLo/C7g+WSAOAAyNj7p9CAiEHurUc=
|
||||
github.com/duke-git/lancet/v2 v2.3.3/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/duke-git/lancet/v2 v2.2.3 h1:Lj4iWgvEbgktEjAfqxE1G2BoGm1mL7l3QHBlXRYptjE=
|
||||
github.com/duke-git/lancet/v2 v2.2.3/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
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/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
|
||||
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/glebarez/sqlite v1.8.0 h1:02X12E2I/4C1n+v90yTqrjRa8yuo7c3KeHI3FRznCvc=
|
||||
github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqSaYLTLx8=
|
||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||
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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
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/generative-ai-go v0.18.0 h1:6ybg9vOCLcI/UpBBYXOTVgvKmcUKFRNj+2Cj3GnebSo=
|
||||
github.com/google/generative-ai-go v0.18.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||
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=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||
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/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
@@ -158,21 +64,16 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
@@ -181,206 +82,78 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw=
|
||||
github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
|
||||
github.com/pkoukk/tiktoken-go v0.1.4 h1:bniMzWdUvNO6YkRbASo2x5qJf2LAG/TIJojqz+Igm8E=
|
||||
github.com/pkoukk/tiktoken-go v0.1.4/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sashabaranov/go-openai v1.31.0 h1:rGe77x7zUeCjtS2IS7NCY6Tp4bQviXNMhkQM6hz/UC4=
|
||||
github.com/sashabaranov/go-openai v1.31.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.32.2 h1:8z9PfYaLPbRzmJIYpwcWu6z3XU8F+RwVMF1QRSeSF2M=
|
||||
github.com/sashabaranov/go-openai v1.32.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.13.0 h1:EAusFfnhaMaaUspUZ2+MbB/ZcVeD4epJmTOlZ+8AcAE=
|
||||
github.com/sashabaranov/go-openai v1.13.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8=
|
||||
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/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
|
||||
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 v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
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/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
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.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.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/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.200.0 h1:0ytfNWn101is6e9VBoct2wrGDjOi5vn7jw5KtaQgDrU=
|
||||
google.golang.org/api v0.200.0/go.mod h1:Tc5u9kcbjO7A8SwGlYj4IiVifJU01UqXtEgDMYmBmV8=
|
||||
google.golang.org/api v0.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0=
|
||||
google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4=
|
||||
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/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=
|
||||
google.golang.org/genproto v0.0.0-20241007155032-5fefd90f89a9 h1:nFS3IivktIU5Mk6KQa+v6RKkHUpdQpphqGNLxqNnbEk=
|
||||
google.golang.org/genproto v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:tEzYTYZxbmVNOu0OAFH9HzdJtLn6h4Aj89zzlBCdHms=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.2.0 h1:iuOqTsbfYuqIz4tAU9NWh22CmBGxlGHdgj4iqP+NUmY=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.2.0/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.1.1 h1:DIh5fMn+tlBvG7pXyUZdemVmLdERnf2xX6XOFF+0BBU=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.1.1/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
|
||||
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
|
||||
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
52
opencat.go
52
opencat.go
@@ -8,7 +8,6 @@ import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"opencatd-open/pkg/team"
|
||||
"opencatd-open/router"
|
||||
"opencatd-open/store"
|
||||
"os"
|
||||
@@ -142,32 +141,43 @@ func main() {
|
||||
}
|
||||
port := os.Getenv("PORT")
|
||||
r := gin.Default()
|
||||
r.Use(team.CORS())
|
||||
group := r.Group("/1")
|
||||
{
|
||||
group.Use(team.AuthMiddleware())
|
||||
group.Use(router.AuthMiddleware())
|
||||
|
||||
// 获取当前用户信息
|
||||
group.GET("/me", team.HandleMe)
|
||||
group.GET("/me/usages", team.HandleMeUsage)
|
||||
group.GET("/me", router.HandleMe)
|
||||
|
||||
group.GET("/keys", team.HandleKeys) // 获取所有Key
|
||||
group.POST("/keys", team.HandleAddKey) // 添加Key
|
||||
group.DELETE("/keys/:id", team.HandleDelKey) // 删除Key
|
||||
group.GET("/me/usages", router.HandleMeUsage)
|
||||
|
||||
group.GET("/users", team.HandleUsers) // 获取所有用户信息
|
||||
group.POST("/users", team.HandleAddUser) // 添加用户
|
||||
group.DELETE("/users/:id", team.HandleDelUser) // 删除用户
|
||||
// 获取所有Key
|
||||
group.GET("/keys", router.HandleKeys)
|
||||
|
||||
group.GET("/usages", team.HandleUsage)
|
||||
// 获取所有用户信息
|
||||
group.GET("/users", router.HandleUsers)
|
||||
|
||||
group.GET("/usages", router.HandleUsage)
|
||||
|
||||
// 添加Key
|
||||
group.POST("/keys", router.HandleAddKey)
|
||||
|
||||
// 删除Key
|
||||
group.DELETE("/keys/:id", router.HandleDelKey)
|
||||
|
||||
// 添加用户
|
||||
group.POST("/users", router.HandleAddUser)
|
||||
|
||||
// 删除用户
|
||||
group.DELETE("/users/:id", router.HandleDelUser)
|
||||
|
||||
// 重置用户Token
|
||||
group.POST("/users/:id/reset", team.HandleResetUserToken)
|
||||
group.POST("/users/:id/reset", router.HandleResetUserToken)
|
||||
}
|
||||
// 初始化用户
|
||||
r.POST("/1/users/init", team.Handleinit)
|
||||
|
||||
r.Any("/v1/*proxypath", router.HandleProxy)
|
||||
// 初始化用户
|
||||
r.POST("/1/users/init", router.Handleinit)
|
||||
|
||||
r.Any("/v1/*proxypath", router.HandleProy)
|
||||
|
||||
// r.POST("/v1/chat/completions", router.HandleProy)
|
||||
// r.GET("/v1/models", router.HandleProy)
|
||||
@@ -178,15 +188,7 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
redirect := os.Getenv("CUSTOM_REDIRECT")
|
||||
if redirect != "" {
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusMovedPermanently, redirect)
|
||||
})
|
||||
|
||||
} else {
|
||||
r.GET("/", gin.WrapH(http.FileServer(http.FS(idxFS))))
|
||||
}
|
||||
r.GET("/", gin.WrapH(http.FileServer(http.FS(idxFS))))
|
||||
assetsFS, err := fs.Sub(web, "dist/assets")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -16,9 +16,6 @@ https://learn.microsoft.com/zh-cn/rest/api/cognitiveservices/azureopenaistable/m
|
||||
-H "Content-Type: application/json" \
|
||||
-H "api-key: $AZURE_OPENAI_KEY" \
|
||||
|
||||
> GPT-4 Turbo
|
||||
https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/azure-openai-service-launches-gpt-4-turbo-and-gpt-3-5-turbo-1106/ba-p/3985962
|
||||
|
||||
*/
|
||||
|
||||
package azureopenai
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
// https://docs.anthropic.com/claude/reference/messages_post
|
||||
|
||||
package claude
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"opencatd-open/pkg/error"
|
||||
"opencatd-open/pkg/openai"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/pkg/vertexai"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ChatProxy(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
|
||||
ChatMessages(c, chatReq)
|
||||
}
|
||||
|
||||
func ChatTextCompletions(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
|
||||
|
||||
}
|
||||
|
||||
type ChatRequest struct {
|
||||
Model string `json:"model,omitempty"`
|
||||
Messages any `json:"messages,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
System string `json:"system,omitempty"`
|
||||
TopK int `json:"top_k,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
AnthropicVersion string `json:"anthropic_version,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ChatRequest) ByteJson() []byte {
|
||||
bytejson, _ := json.Marshal(c)
|
||||
return bytejson
|
||||
}
|
||||
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
type VisionMessages struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Content []VisionContent `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
type VisionContent struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Source *VisionSource `json:"source,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
type VisionSource struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
MediaType string `json:"media_type,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type ChatResponse struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Role string `json:"role"`
|
||||
Model string `json:"model"`
|
||||
StopSequence any `json:"stop_sequence"`
|
||||
Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
} `json:"usage"`
|
||||
Content []struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
} `json:"content"`
|
||||
StopReason string `json:"stop_reason"`
|
||||
}
|
||||
|
||||
type ClaudeStreamResponse struct {
|
||||
Type string `json:"type"`
|
||||
Index int `json:"index"`
|
||||
ContentBlock struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
} `json:"content_block"`
|
||||
Delta struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
StopReason string `json:"stop_reason"`
|
||||
StopSequence any `json:"stop_sequence"`
|
||||
} `json:"delta"`
|
||||
Message struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Role string `json:"role"`
|
||||
Content []any `json:"content"`
|
||||
Model string `json:"model"`
|
||||
StopReason string `json:"stop_reason"`
|
||||
StopSequence any `json:"stop_sequence"`
|
||||
Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
} `json:"usage"`
|
||||
} `json:"message"`
|
||||
Error struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
Usage struct {
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
func ChatMessages(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
|
||||
var (
|
||||
req *http.Request
|
||||
targetURL = ClaudeMessageEndpoint
|
||||
)
|
||||
|
||||
apiKey, err := store.SelectKeyCache("claude")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
usagelog := store.Tokens{Model: chatReq.Model}
|
||||
var claudReq ChatRequest
|
||||
claudReq.Model = chatReq.Model
|
||||
claudReq.Stream = chatReq.Stream
|
||||
// claudReq.Temperature = chatReq.Temperature
|
||||
claudReq.TopP = chatReq.TopP
|
||||
claudReq.MaxTokens = 4096
|
||||
if apiKey.ApiType == "vertex" {
|
||||
claudReq.AnthropicVersion = "vertex-2023-10-16"
|
||||
claudReq.Model = ""
|
||||
}
|
||||
|
||||
var prompt string
|
||||
|
||||
var claudecontent []VisionContent
|
||||
for _, msg := range chatReq.Messages {
|
||||
if msg.Role == "system" {
|
||||
claudReq.System = string(msg.Content)
|
||||
continue
|
||||
}
|
||||
|
||||
var oaivisioncontent []openai.VisionContent
|
||||
if err := json.Unmarshal(msg.Content, &oaivisioncontent); err != nil {
|
||||
prompt += "<" + msg.Role + ">: " + string(msg.Content) + "\n"
|
||||
|
||||
claudecontent = append(claudecontent, VisionContent{Type: "text", Text: msg.Role + ":" + string(msg.Content)})
|
||||
} else {
|
||||
if len(oaivisioncontent) > 0 {
|
||||
for _, content := range oaivisioncontent {
|
||||
if content.Type == "text" {
|
||||
prompt += "<" + msg.Role + ">: " + content.Text + "\n"
|
||||
claudecontent = append(claudecontent, VisionContent{Type: "text", Text: msg.Role + ":" + content.Text})
|
||||
} else if content.Type == "image_url" {
|
||||
if strings.HasPrefix(content.ImageURL.URL, "http") {
|
||||
fmt.Println("链接:", content.ImageURL.URL)
|
||||
} else if strings.HasPrefix(content.ImageURL.URL, "data:image") {
|
||||
fmt.Println("base64:", content.ImageURL.URL[:20])
|
||||
}
|
||||
// todo image tokens
|
||||
var mediaType string
|
||||
if strings.HasPrefix(content.ImageURL.URL, "data:image/jpeg") {
|
||||
mediaType = "image/jpeg"
|
||||
}
|
||||
if strings.HasPrefix(content.ImageURL.URL, "data:image/png") {
|
||||
mediaType = "image/png"
|
||||
}
|
||||
claudecontent = append(claudecontent, VisionContent{Type: "image", Source: &VisionSource{Type: "base64", MediaType: mediaType, Data: strings.Split(content.ImageURL.URL, ",")[1]}})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// if len(chatReq.Tools) > 0 {
|
||||
// tooljson, _ := json.Marshal(chatReq.Tools)
|
||||
// prompt += "<tools>: " + string(tooljson) + "\n"
|
||||
// }
|
||||
}
|
||||
claudReq.Messages = []VisionMessages{{Role: "user", Content: claudecontent}}
|
||||
|
||||
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
|
||||
|
||||
if apiKey.ApiType == "vertex" {
|
||||
var vertexSecret vertexai.VertexSecretKey
|
||||
if err := json.Unmarshal([]byte(apiKey.ApiSecret), &vertexSecret); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, error.ErrorData(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
vcmodel, ok := vertexai.VertexClaudeModelMap[chatReq.Model]
|
||||
if !ok {
|
||||
c.JSON(http.StatusInternalServerError, error.ErrorData("Model not found"))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取gcloud token,临时放置在apiKey.Key中
|
||||
gcloudToken, err := vertexai.GcloudAuth(vertexSecret.ClientEmail, vertexSecret.PrivateKey)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, error.ErrorData(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// 拼接vertex的请求地址
|
||||
targetURL = fmt.Sprintf("https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/anthropic/models/%s:streamRawPredict", vcmodel.Region, vertexSecret.ProjectID, vcmodel.Region, vcmodel.VertexName)
|
||||
|
||||
req, _ = http.NewRequest("POST", targetURL, bytes.NewReader(claudReq.ByteJson()))
|
||||
req.Header.Set("Authorization", "Bearer "+gcloudToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
req.Header.Set("Accept-Encoding", "identity")
|
||||
} else {
|
||||
req, _ = http.NewRequest("POST", targetURL, bytes.NewReader(claudReq.ByteJson()))
|
||||
req.Header.Set("x-api-key", apiKey.Key)
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
client := http.DefaultClient
|
||||
rsp, err := client.Do(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
io.Copy(c.Writer, rsp.Body)
|
||||
return
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
teeReader := io.TeeReader(rsp.Body, &buffer)
|
||||
|
||||
dataChan := make(chan string)
|
||||
// stopChan := make(chan bool)
|
||||
|
||||
var result string
|
||||
|
||||
scanner := bufio.NewScanner(teeReader)
|
||||
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
if len(line) > 0 && bytes.HasPrefix(line, []byte("data: ")) {
|
||||
if bytes.HasPrefix(line, []byte("data: [DONE]")) {
|
||||
dataChan <- string(line) + "\n"
|
||||
break
|
||||
}
|
||||
var claudeResp ClaudeStreamResponse
|
||||
line = bytes.Replace(line, []byte("data: "), []byte(""), -1)
|
||||
line = bytes.TrimSpace(line)
|
||||
if err := json.Unmarshal(line, &claudeResp); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if claudeResp.Type == "message_start" {
|
||||
if claudeResp.Message.Role != "" {
|
||||
result += "<" + claudeResp.Message.Role + ">"
|
||||
}
|
||||
} else if claudeResp.Type == "message_stop" {
|
||||
break
|
||||
}
|
||||
|
||||
if claudeResp.Delta.Text != "" {
|
||||
result += claudeResp.Delta.Text
|
||||
}
|
||||
var choice openai.Choice
|
||||
choice.Delta.Role = claudeResp.Message.Role
|
||||
choice.Delta.Content = claudeResp.Delta.Text
|
||||
choice.FinishReason = claudeResp.Delta.StopReason
|
||||
|
||||
chatResp := openai.ChatCompletionStreamResponse{
|
||||
Model: chatReq.Model,
|
||||
Choices: []openai.Choice{choice},
|
||||
}
|
||||
dataChan <- "data: " + string(chatResp.ByteJson()) + "\n"
|
||||
if claudeResp.Delta.StopReason != "" {
|
||||
dataChan <- "\ndata: [DONE]\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
defer close(dataChan)
|
||||
}()
|
||||
|
||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
c.Writer.Header().Set("Transfer-Encoding", "chunked")
|
||||
c.Writer.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
if data, ok := <-dataChan; ok {
|
||||
if strings.HasPrefix(data, "data: ") {
|
||||
c.Writer.WriteString(data)
|
||||
// c.Writer.WriteString("\n\n")
|
||||
} else {
|
||||
c.Writer.WriteHeader(http.StatusBadGateway)
|
||||
c.Writer.WriteString(data)
|
||||
}
|
||||
c.Writer.Flush()
|
||||
return true
|
||||
}
|
||||
go func() {
|
||||
usagelog.CompletionCount = tokenizer.NumTokensFromStr(result, chatReq.Model)
|
||||
usagelog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(usagelog.Model, usagelog.PromptCount, usagelog.CompletionCount))
|
||||
if err := store.Record(&usagelog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(usagelog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
return false
|
||||
})
|
||||
}
|
||||
@@ -22,26 +22,11 @@ data: {"completion":"","stop_reason":"stop_sequence","model":"claude-2.0","stop"
|
||||
|
||||
# Model Pricing
|
||||
|
||||
Claude Instant |100,000 tokens |Prompt $1.63/million tokens |Completion $5.51/million tokens
|
||||
Claude Instant |100,000 tokens |Prompt $1.63/million tokens |Completion $5.51/million tokens
|
||||
|
||||
Claude 2 |100,000 tokens |Prompt $11.02/million tokens |Completion $32.68/million tokens
|
||||
Claude 2 |100,000 tokens |Prompt $11.02/million tokens |Completion $32.68/million tokens
|
||||
*Claude 1 is still accessible and offered at the same price as Claude 2.
|
||||
|
||||
# AWS
|
||||
https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-service.html
|
||||
https://aws.amazon.com/cn/bedrock/pricing/
|
||||
Anthropic models Price for 1000 input tokens Price for 1000 output tokens
|
||||
Claude Instant $0.00163 $0.00551
|
||||
|
||||
Claude $0.01102 $0.03268
|
||||
|
||||
https://docs.aws.amazon.com/bedrock/latest/userguide/endpointsTable.html
|
||||
地区名称 地区 端点 协议
|
||||
美国东部(弗吉尼亚北部) 美国东部1 bedrock-runtime.us-east-1.amazonaws.com HTTPS
|
||||
bedrock-runtime-fips.us-east-1.amazonaws.com HTTPS
|
||||
美国西部(俄勒冈州) 美国西2号 bedrock-runtime.us-west-2.amazonaws.com HTTPS
|
||||
bedrock-runtime-fips.us-west-2.amazonaws.com HTTPS
|
||||
亚太地区(新加坡) ap-东南-1 bedrock-runtime.ap-southeast-1.amazonaws.com HTTPS
|
||||
*/
|
||||
|
||||
// package anthropic
|
||||
@@ -57,7 +42,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -68,8 +52,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ClaudeUrl = "https://api.anthropic.com/v1/complete"
|
||||
ClaudeMessageEndpoint = "https://api.anthropic.com/v1/messages"
|
||||
ClaudeUrl = "https://api.anthropic.com/v1/complete"
|
||||
)
|
||||
|
||||
type MessageModule struct {
|
||||
@@ -152,7 +135,7 @@ func ClaudeProxy(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
key, err := store.SelectKeyCache("claude") //anthropic
|
||||
key, err := store.SelectKeyCache("anthropic")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
@@ -177,7 +160,7 @@ func ClaudeProxy(c *gin.Context) {
|
||||
}
|
||||
chatlog.UserID = int(lu.ID)
|
||||
|
||||
chatlog.PromptCount = tokenizer.NumTokensFromStr(complete.Prompt, complete.Model)
|
||||
// todo calc prompt token
|
||||
|
||||
if key.EndPoint == "" {
|
||||
key.EndPoint = "https://api.anthropic.com"
|
||||
@@ -209,7 +192,6 @@ func ClaudeProxy(c *gin.Context) {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
chatlog.CompletionCount = tokenizer.NumTokensFromStr(complete_resp.Completion, chatlog.Model)
|
||||
} else {
|
||||
var completion string
|
||||
for {
|
||||
@@ -231,12 +213,10 @@ func ClaudeProxy(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
log.Println("completion:", completion)
|
||||
chatlog.CompletionCount = tokenizer.NumTokensFromStr(completion, chatlog.Model)
|
||||
}
|
||||
|
||||
// calc cost
|
||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
||||
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
||||
|
||||
// todo calc cost
|
||||
|
||||
if err := store.Record(&chatlog); err != nil {
|
||||
log.Println(err)
|
||||
@@ -258,7 +238,7 @@ func TransReq(chatreq *openai.ChatCompletionRequest) (*bytes.Buffer, error) {
|
||||
MaxTokensToSample: chatreq.MaxTokens,
|
||||
}
|
||||
if transReq.MaxTokensToSample == 0 {
|
||||
transReq.MaxTokensToSample = 100000
|
||||
transReq.MaxTokensToSample = 1000000
|
||||
}
|
||||
var prompt string
|
||||
for _, msg := range chatreq.Messages {
|
||||
@@ -279,7 +259,7 @@ func TransReq(chatreq *openai.ChatCompletionRequest) (*bytes.Buffer, error) {
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func TransRsp(c *gin.Context, isStream bool, chatlog store.Tokens, reader *bufio.Reader) {
|
||||
func TransRsp(c *gin.Context, isStream bool, reader *bufio.Reader) {
|
||||
if !isStream {
|
||||
var completersp CompleteResponse
|
||||
var chatrsp openai.ChatCompletionResponse
|
||||
@@ -306,24 +286,13 @@ func TransRsp(c *gin.Context, isStream bool, chatlog store.Tokens, reader *bufio
|
||||
})
|
||||
return
|
||||
}
|
||||
chatlog.CompletionCount = tokenizer.NumTokensFromStr(completersp.Completion, chatlog.Model)
|
||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
||||
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
||||
if err := store.Record(&chatlog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(chatlog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, payload)
|
||||
return
|
||||
} else {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
dataChan = make(chan string)
|
||||
stopChan = make(chan bool)
|
||||
complete_resp string
|
||||
wg sync.WaitGroup
|
||||
dataChan = make(chan string)
|
||||
stopChan = make(chan bool)
|
||||
)
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
@@ -336,7 +305,6 @@ func TransRsp(c *gin.Context, isStream bool, chatlog store.Tokens, reader *bufio
|
||||
json.NewDecoder(strings.NewReader(line[6:])).Decode(&result)
|
||||
if result.StopReason == "" {
|
||||
if result.Completion != "" {
|
||||
complete_resp += result.Completion
|
||||
chatrsp := openai.ChatCompletionStreamResponse{
|
||||
ID: result.LogID,
|
||||
Model: result.Model,
|
||||
@@ -404,15 +372,6 @@ func TransRsp(c *gin.Context, isStream bool, chatlog store.Tokens, reader *bufio
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
chatlog.CompletionCount = tokenizer.NumTokensFromStr(complete_resp, chatlog.Model)
|
||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
||||
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
||||
if err := store.Record(&chatlog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(chatlog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package error
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func ErrorData(message string) gin.H {
|
||||
return gin.H{
|
||||
"error": gin.H{
|
||||
"message": message,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
// https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/grounding-search-entry-points?authuser=2&hl=zh-cn
|
||||
//
|
||||
// https://cloud.google.com/vertex-ai/docs/generative-ai/quotas-genai
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"opencatd-open/pkg/openai"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
type GeminiChatRequest struct {
|
||||
Contents []GeminiContent `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
func (g GeminiChatRequest) ByteJson() []byte {
|
||||
bytejson, _ := json.Marshal(g)
|
||||
return bytejson
|
||||
}
|
||||
|
||||
type GeminiContent struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Parts []GeminiPart `json:"parts,omitempty"`
|
||||
}
|
||||
type GeminiPart struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
// InlineData GeminiPartInlineData `json:"inlineData,omitempty"`
|
||||
}
|
||||
type GeminiPartInlineData struct {
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
Data string `json:"data,omitempty"` // base64
|
||||
}
|
||||
|
||||
type GeminiResponse struct {
|
||||
Candidates []struct {
|
||||
Content struct {
|
||||
Parts []struct {
|
||||
Text string `json:"text"`
|
||||
} `json:"parts"`
|
||||
Role string `json:"role"`
|
||||
} `json:"content"`
|
||||
FinishReason string `json:"finishReason"`
|
||||
Index int `json:"index"`
|
||||
SafetyRatings []struct {
|
||||
Category string `json:"category"`
|
||||
Probability string `json:"probability"`
|
||||
} `json:"safetyRatings"`
|
||||
} `json:"candidates"`
|
||||
PromptFeedback struct {
|
||||
SafetyRatings []struct {
|
||||
Category string `json:"category"`
|
||||
Probability string `json:"probability"`
|
||||
} `json:"safetyRatings"`
|
||||
} `json:"promptFeedback"`
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
Details []struct {
|
||||
Type string `json:"@type"`
|
||||
FieldViolations []struct {
|
||||
Field string `json:"field"`
|
||||
Description string `json:"description"`
|
||||
} `json:"fieldViolations"`
|
||||
} `json:"details"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func ChatProxy(c *gin.Context, chatReq *openai.ChatCompletionRequest) {
|
||||
usagelog := store.Tokens{Model: chatReq.Model}
|
||||
|
||||
token, _ := c.Get("localuser")
|
||||
|
||||
lu, err := store.GetUserByToken(token.(string))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
usagelog.UserID = int(lu.ID)
|
||||
var prompts []genai.Part
|
||||
var prompt string
|
||||
for _, msg := range chatReq.Messages {
|
||||
var visioncontent []openai.VisionContent
|
||||
if err := json.Unmarshal(msg.Content, &visioncontent); err != nil {
|
||||
prompt += "<" + msg.Role + ">: " + string(msg.Content) + "\n"
|
||||
prompts = append(prompts, genai.Text("<"+msg.Role+">: "+string(msg.Content)))
|
||||
} else {
|
||||
if len(visioncontent) > 0 {
|
||||
for _, content := range visioncontent {
|
||||
if content.Type == "text" {
|
||||
prompt += "<" + msg.Role + ">: " + content.Text + "\n"
|
||||
prompts = append(prompts, genai.Text("<"+msg.Role+">: "+content.Text))
|
||||
} else if content.Type == "image_url" {
|
||||
if strings.HasPrefix(content.ImageURL.URL, "http") {
|
||||
fmt.Println("链接:", content.ImageURL.URL)
|
||||
} else if strings.HasPrefix(content.ImageURL.URL, "data:image") {
|
||||
fmt.Println("base64:", content.ImageURL.URL[:20])
|
||||
if chatReq.Model != "gemini-pro-vision" {
|
||||
chatReq.Model = "gemini-pro-vision"
|
||||
}
|
||||
|
||||
var mime string
|
||||
// openai 会以 data:image 开头,则去掉 data:image/png;base64, 和 data:image/jpeg;base64,
|
||||
if strings.HasPrefix(content.ImageURL.URL, "data:image/png") {
|
||||
mime = "image/png"
|
||||
} else if strings.HasPrefix(content.ImageURL.URL, "data:image/jpeg") {
|
||||
mime = "image/jpeg"
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unsupported image format"})
|
||||
return
|
||||
}
|
||||
imageString := strings.Split(content.ImageURL.URL, ",")[1]
|
||||
imageBytes, err := base64.StdEncoding.DecodeString(imageString)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
prompts = append(prompts, genai.Blob{MIMEType: mime, Data: imageBytes})
|
||||
}
|
||||
|
||||
// todo image tokens
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if len(chatReq.Tools) > 0 {
|
||||
tooljson, _ := json.Marshal(chatReq.Tools)
|
||||
prompt += "<tools>: " + string(tooljson) + "\n"
|
||||
|
||||
// for _, tool := range chatReq.Tools {
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
|
||||
|
||||
onekey, err := store.SelectKeyCache("google")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := genai.NewClient(ctx, option.WithAPIKey(onekey.Key))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
model := client.GenerativeModel(chatReq.Model)
|
||||
|
||||
iter := model.GenerateContentStream(ctx, prompts...)
|
||||
datachan := make(chan string)
|
||||
// closechan := make(chan error)
|
||||
var result string
|
||||
go func() {
|
||||
for {
|
||||
resp, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
|
||||
var chatResp openai.ChatCompletionStreamResponse
|
||||
chatResp.Model = chatReq.Model
|
||||
choice := openai.Choice{}
|
||||
choice.FinishReason = "stop"
|
||||
chatResp.Choices = append(chatResp.Choices, choice)
|
||||
datachan <- "data: " + string(chatResp.ByteJson())
|
||||
close(datachan)
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
var errResp openai.ErrResponse
|
||||
errResp.Error.Code = "500"
|
||||
errResp.Error.Message = err.Error()
|
||||
datachan <- string(errResp.ByteJson())
|
||||
close(datachan)
|
||||
break
|
||||
}
|
||||
var content string
|
||||
if resp.Candidates != nil && len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
|
||||
if s, ok := resp.Candidates[0].Content.Parts[0].(genai.Text); ok {
|
||||
content = string(s)
|
||||
result += content
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
var chatResp openai.ChatCompletionStreamResponse
|
||||
chatResp.Model = chatReq.Model
|
||||
choice := openai.Choice{}
|
||||
choice.Delta.Role = resp.Candidates[0].Content.Role
|
||||
choice.Delta.Content = content
|
||||
chatResp.Choices = append(chatResp.Choices, choice)
|
||||
|
||||
chunk := "data: " + string(chatResp.ByteJson()) + "\n\n"
|
||||
datachan <- chunk
|
||||
}
|
||||
}()
|
||||
|
||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
c.Writer.Header().Set("Transfer-Encoding", "chunked")
|
||||
c.Writer.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
if data, ok := <-datachan; ok {
|
||||
if strings.HasPrefix(data, "data: ") {
|
||||
c.Writer.WriteString(data)
|
||||
// c.Writer.WriteString("\n\n")
|
||||
} else {
|
||||
c.Writer.WriteHeader(http.StatusBadGateway)
|
||||
c.Writer.WriteString(data)
|
||||
}
|
||||
c.Writer.Flush()
|
||||
return true
|
||||
}
|
||||
go func() {
|
||||
|
||||
}()
|
||||
return false
|
||||
})
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation#latest-preview-api-releases
|
||||
AzureApiVersion = "2024-06-01"
|
||||
BaseHost = "api.openai.com"
|
||||
OpenAI_Endpoint = "https://api.openai.com/v1/chat/completions"
|
||||
Github_Marketplace = "https://models.inference.ai.azure.com/chat/completions"
|
||||
)
|
||||
|
||||
var (
|
||||
BaseURL string // "https://api.openai.com"
|
||||
AIGateWay_Endpoint = "https://gateway.ai.cloudflare.com/v1/431ba10f11200d544922fbca177aaa7f/openai/openai/chat/completions"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if os.Getenv("OpenAI_Endpoint") != "" {
|
||||
BaseURL = os.Getenv("OpenAI_Endpoint")
|
||||
}
|
||||
if os.Getenv("AIGateWay_Endpoint") != "" {
|
||||
AIGateWay_Endpoint = os.Getenv("AIGateWay_Endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
// Vision Content
|
||||
type VisionContent struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
ImageURL *VisionImageURL `json:"image_url,omitempty"`
|
||||
}
|
||||
type VisionImageURL struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
type ChatCompletionMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content json.RawMessage `json:"content"`
|
||||
Name string `json:"name,omitempty"`
|
||||
// MultiContent []VisionContent
|
||||
}
|
||||
|
||||
type FunctionDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Parameters any `json:"parameters"`
|
||||
}
|
||||
|
||||
type Tool struct {
|
||||
Type string `json:"type"`
|
||||
Function *FunctionDefinition `json:"function,omitempty"`
|
||||
}
|
||||
|
||||
type StreamOption struct {
|
||||
IncludeUsage bool `json:"include_usage,omitempty"`
|
||||
}
|
||||
|
||||
type ChatCompletionRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []ChatCompletionMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
N int `json:"n,omitempty"`
|
||||
Stream bool `json:"stream"`
|
||||
Stop []string `json:"stop,omitempty"`
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
|
||||
LogitBias map[string]int `json:"logit_bias,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
// Functions []FunctionDefinition `json:"functions,omitempty"`
|
||||
// FunctionCall any `json:"function_call,omitempty"`
|
||||
Tools []Tool `json:"tools,omitempty"`
|
||||
ParallelToolCalls bool `json:"parallel_tool_calls,omitempty"`
|
||||
// ToolChoice any `json:"tool_choice,omitempty"`
|
||||
StreamOptions *StreamOption `json:"stream_options,omitempty"`
|
||||
}
|
||||
|
||||
func (c ChatCompletionRequest) ToByteJson() []byte {
|
||||
bytejson, _ := json.Marshal(c)
|
||||
return bytejson
|
||||
}
|
||||
|
||||
type ToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Function struct {
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments"`
|
||||
} `json:"function"`
|
||||
}
|
||||
|
||||
type ChatCompletionResponse struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Object string `json:"object,omitempty"`
|
||||
Created int `json:"created,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Choices []struct {
|
||||
Index int `json:"index,omitempty"`
|
||||
Message struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
} `json:"message,omitempty"`
|
||||
Logprobs string `json:"logprobs,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
} `json:"choices,omitempty"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens,omitempty"`
|
||||
CompletionTokens int `json:"completion_tokens,omitempty"`
|
||||
TotalTokens int `json:"total_tokens,omitempty"`
|
||||
PromptTokensDetails struct {
|
||||
CachedTokens int `json:"cached_tokens,omitempty"`
|
||||
AudioTokens int `json:"audio_tokens,omitempty"`
|
||||
} `json:"prompt_tokens_details,omitempty"`
|
||||
CompletionTokensDetails struct {
|
||||
ReasoningTokens int `json:"reasoning_tokens,omitempty"`
|
||||
AudioTokens int `json:"audio_tokens,omitempty"`
|
||||
AcceptedPredictionTokens int `json:"accepted_prediction_tokens,omitempty"`
|
||||
RejectedPredictionTokens int `json:"rejected_prediction_tokens,omitempty"`
|
||||
} `json:"completion_tokens_details,omitempty"`
|
||||
} `json:"usage,omitempty"`
|
||||
SystemFingerprint string `json:"system_fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
type Choice struct {
|
||||
Index int `json:"index"`
|
||||
Delta struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ToolCalls []ToolCall `json:"tool_calls"`
|
||||
} `json:"delta"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
type ChatCompletionStreamResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
}
|
||||
|
||||
func (c *ChatCompletionStreamResponse) ByteJson() []byte {
|
||||
bytejson, _ := json.Marshal(c)
|
||||
return bytejson
|
||||
}
|
||||
|
||||
func ChatProxy(c *gin.Context, chatReq *ChatCompletionRequest) {
|
||||
usagelog := store.Tokens{Model: chatReq.Model}
|
||||
|
||||
token, _ := c.Get("localuser")
|
||||
|
||||
lu, err := store.GetUserByToken(token.(string))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
usagelog.UserID = int(lu.ID)
|
||||
|
||||
var prompt string
|
||||
for _, msg := range chatReq.Messages {
|
||||
// prompt += "<" + msg.Role + ">: " + msg.Content + "\n"
|
||||
var visioncontent []VisionContent
|
||||
if err := json.Unmarshal(msg.Content, &visioncontent); err != nil {
|
||||
prompt += "<" + msg.Role + ">: " + string(msg.Content) + "\n"
|
||||
} else {
|
||||
if len(visioncontent) > 0 {
|
||||
for _, content := range visioncontent {
|
||||
if content.Type == "text" {
|
||||
prompt += "<" + msg.Role + ">: " + content.Text + "\n"
|
||||
} else if content.Type == "image_url" {
|
||||
if strings.HasPrefix(content.ImageURL.URL, "http") {
|
||||
fmt.Println("链接:", content.ImageURL.URL)
|
||||
} else if strings.HasPrefix(content.ImageURL.URL, "data:image") {
|
||||
fmt.Println("base64:", content.ImageURL.URL[:20])
|
||||
}
|
||||
// todo image tokens
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if len(chatReq.Tools) > 0 {
|
||||
tooljson, _ := json.Marshal(chatReq.Tools)
|
||||
prompt += "<tools>: " + string(tooljson) + "\n"
|
||||
}
|
||||
}
|
||||
switch chatReq.Model {
|
||||
case "gpt-4o", "gpt-4o-mini", "chatgpt-4o-latest":
|
||||
chatReq.MaxTokens = 16384
|
||||
}
|
||||
if chatReq.Stream {
|
||||
chatReq.StreamOptions = &StreamOption{IncludeUsage: true}
|
||||
}
|
||||
|
||||
usagelog.PromptCount = tokenizer.NumTokensFromStr(prompt, chatReq.Model)
|
||||
|
||||
// onekey, err := store.SelectKeyCache("openai")
|
||||
onekey, err := store.SelectKeyCacheByModel(chatReq.Model)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
|
||||
switch onekey.ApiType {
|
||||
case "github":
|
||||
req, err = http.NewRequest(c.Request.Method, Github_Marketplace, bytes.NewReader(chatReq.ToByteJson()))
|
||||
req.Header = c.Request.Header
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
|
||||
case "azure":
|
||||
var buildurl string
|
||||
if onekey.EndPoint != "" {
|
||||
buildurl = fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", onekey.EndPoint, modelmap(chatReq.Model), AzureApiVersion)
|
||||
} else {
|
||||
buildurl = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s", onekey.ResourceNmae, modelmap(chatReq.Model), AzureApiVersion)
|
||||
}
|
||||
req, err = http.NewRequest(c.Request.Method, buildurl, bytes.NewReader(chatReq.ToByteJson()))
|
||||
req.Header = c.Request.Header
|
||||
req.Header.Set("api-key", onekey.Key)
|
||||
default:
|
||||
req, err = http.NewRequest(c.Request.Method, OpenAI_Endpoint, bytes.NewReader(chatReq.ToByteJson()))
|
||||
if onekey.EndPoint != "" { // 优先key的endpoint
|
||||
req, err = http.NewRequest(c.Request.Method, onekey.EndPoint+c.Request.RequestURI, bytes.NewReader(chatReq.ToByteJson()))
|
||||
}
|
||||
if AIGateWay_Endpoint != "" { // cloudflare gateway的endpoint
|
||||
req, err = http.NewRequest(c.Request.Method, AIGateWay_Endpoint, bytes.NewReader(chatReq.ToByteJson()))
|
||||
}
|
||||
customEndpoint := os.Getenv("CUSTOM_ENDPOINT") // 最后是用户自定义的endpoint CUSTOM_ENDPOINT=true OpenAI_Endpoint
|
||||
if customEndpoint == "true" && OpenAI_Endpoint != "" {
|
||||
req, err = http.NewRequest(c.Request.Method, BaseURL, bytes.NewReader(chatReq.ToByteJson()))
|
||||
}
|
||||
|
||||
req.Header = c.Request.Header
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", onekey.Key))
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result string
|
||||
if chatReq.Stream {
|
||||
for key, value := range resp.Header {
|
||||
for _, v := range value {
|
||||
c.Writer.Header().Add(key, v)
|
||||
}
|
||||
}
|
||||
c.Writer.WriteHeader(resp.StatusCode)
|
||||
teeReader := io.TeeReader(resp.Body, c.Writer)
|
||||
// 流式响应
|
||||
scanner := bufio.NewScanner(teeReader)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
if len(line) > 0 && bytes.HasPrefix(line, []byte("data: ")) {
|
||||
if bytes.HasPrefix(line, []byte("data: [DONE]")) {
|
||||
break
|
||||
}
|
||||
var opiResp ChatCompletionStreamResponse
|
||||
line = bytes.Replace(line, []byte("data: "), []byte(""), -1)
|
||||
line = bytes.TrimSpace(line)
|
||||
if err := json.Unmarshal(line, &opiResp); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if opiResp.Choices != nil && len(opiResp.Choices) > 0 {
|
||||
if opiResp.Choices[0].Delta.Role != "" {
|
||||
result += "<" + opiResp.Choices[0].Delta.Role + "> "
|
||||
}
|
||||
result += opiResp.Choices[0].Delta.Content // 计算Content Token
|
||||
|
||||
if len(opiResp.Choices[0].Delta.ToolCalls) > 0 { // 计算ToolCalls token
|
||||
if opiResp.Choices[0].Delta.ToolCalls[0].Function.Name != "" {
|
||||
result += "name:" + opiResp.Choices[0].Delta.ToolCalls[0].Function.Name + " arguments:"
|
||||
}
|
||||
result += opiResp.Choices[0].Delta.ToolCalls[0].Function.Arguments
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
// 处理非流式响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response body:", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var opiResp ChatCompletionResponse
|
||||
if err := json.Unmarshal(body, &opiResp); err != nil {
|
||||
log.Println("Error parsing JSON:", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if opiResp.Choices != nil && len(opiResp.Choices) > 0 {
|
||||
if opiResp.Choices[0].Message.Role != "" {
|
||||
result += "<" + opiResp.Choices[0].Message.Role + "> "
|
||||
}
|
||||
result += opiResp.Choices[0].Message.Content
|
||||
|
||||
if len(opiResp.Choices[0].Message.ToolCalls) > 0 {
|
||||
if opiResp.Choices[0].Message.ToolCalls[0].Function.Name != "" {
|
||||
result += "name:" + opiResp.Choices[0].Message.ToolCalls[0].Function.Name + " arguments:"
|
||||
}
|
||||
result += opiResp.Choices[0].Message.ToolCalls[0].Function.Arguments
|
||||
}
|
||||
|
||||
}
|
||||
for k, v := range resp.Header {
|
||||
c.Writer.Header().Set(k, v[0])
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, opiResp)
|
||||
}
|
||||
usagelog.CompletionCount = tokenizer.NumTokensFromStr(result, chatReq.Model)
|
||||
usagelog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(usagelog.Model, usagelog.PromptCount, usagelog.CompletionCount))
|
||||
if err := store.Record(&usagelog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(usagelog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func modelmap(in string) string {
|
||||
// gpt-3.5-turbo -> gpt-35-turbo
|
||||
if strings.Contains(in, ".") {
|
||||
return strings.ReplaceAll(in, ".", "")
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
type ErrResponse struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func (e *ErrResponse) ByteJson() []byte {
|
||||
bytejson, _ := json.Marshal(e)
|
||||
return bytejson
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
"strconv"
|
||||
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
DalleEndpoint = "https://api.openai.com/v1/images/generations"
|
||||
DalleEditEndpoint = "https://api.openai.com/v1/images/edits"
|
||||
DalleVariationEndpoint = "https://api.openai.com/v1/images/variations"
|
||||
)
|
||||
|
||||
type DallERequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
N int `form:"n" json:"n,omitempty"`
|
||||
Size string `form:"size" json:"size,omitempty"`
|
||||
Quality string `json:"quality,omitempty"` // standard,hd
|
||||
Style string `json:"style,omitempty"` // vivid,natural
|
||||
ResponseFormat string `json:"response_format,omitempty"` // url or b64_json
|
||||
}
|
||||
|
||||
func DallEProxy(c *gin.Context) {
|
||||
|
||||
var dalleRequest DallERequest
|
||||
if err := c.ShouldBind(&dalleRequest); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if dalleRequest.N == 0 {
|
||||
dalleRequest.N = 1
|
||||
}
|
||||
|
||||
if dalleRequest.Size == "" {
|
||||
dalleRequest.Size = "512x512"
|
||||
}
|
||||
|
||||
model := dalleRequest.Model
|
||||
|
||||
var chatlog store.Tokens
|
||||
chatlog.CompletionCount = dalleRequest.N
|
||||
|
||||
if model == "dall-e" {
|
||||
model = "dall-e-2"
|
||||
}
|
||||
model = model + "." + dalleRequest.Size
|
||||
|
||||
if dalleRequest.Model == "dall-e-2" || dalleRequest.Model == "dall-e" {
|
||||
if !slice.Contain([]string{"256x256", "512x512", "1024x1024"}, dalleRequest.Size) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": gin.H{
|
||||
"message": fmt.Sprintf("Invalid size: %s for %s", dalleRequest.Size, dalleRequest.Model),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if dalleRequest.Model == "dall-e-3" {
|
||||
if !slice.Contain([]string{"256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"}, dalleRequest.Size) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": gin.H{
|
||||
"message": fmt.Sprintf("Invalid size: %s for %s", dalleRequest.Size, dalleRequest.Model),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if dalleRequest.Quality == "hd" {
|
||||
model = model + ".hd"
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": gin.H{
|
||||
"message": fmt.Sprintf("Invalid model: %s", dalleRequest.Model),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
chatlog.Model = model
|
||||
|
||||
token, _ := c.Get("localuser")
|
||||
|
||||
lu, err := store.GetUserByToken(token.(string))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
chatlog.UserID = int(lu.ID)
|
||||
|
||||
key, err := store.SelectKeyCache("openai")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
targetURL, _ := url.Parse(DalleEndpoint)
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
||||
proxy.Director = func(req *http.Request) {
|
||||
req.Header.Set("Authorization", "Bearer "+key.Key)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
req.Host = targetURL.Host
|
||||
req.URL.Scheme = targetURL.Scheme
|
||||
req.URL.Host = targetURL.Host
|
||||
req.URL.Path = targetURL.Path
|
||||
req.URL.RawPath = targetURL.RawPath
|
||||
req.URL.RawQuery = targetURL.RawQuery
|
||||
|
||||
bytebody, _ := json.Marshal(dalleRequest)
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(bytebody))
|
||||
req.ContentLength = int64(len(bytebody))
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(bytebody)))
|
||||
}
|
||||
|
||||
proxy.ModifyResponse = func(resp *http.Response) error {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
||||
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
||||
if err := store.Record(&chatlog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(chatlog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
https://platform.openai.com/docs/guides/realtime
|
||||
https://learn.microsoft.com/zh-cn/azure/ai-services/openai/how-to/audio-real-time
|
||||
|
||||
wss://my-eastus2-openai-resource.openai.azure.com/openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview-1001
|
||||
*/
|
||||
package openai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01"
|
||||
const realtimeURL = "wss://api.openai.com/v1/realtime"
|
||||
const azureRealtimeURL = "wss://%s.openai.azure.com/openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview"
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"`
|
||||
Response Response `json:"response"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Modalities []string `json:"modalities"`
|
||||
Instructions string `json:"instructions"`
|
||||
}
|
||||
|
||||
type RealTimeResponse struct {
|
||||
Type string `json:"type"`
|
||||
EventID string `json:"event_id"`
|
||||
Response struct {
|
||||
Object string `json:"object"`
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
StatusDetails any `json:"status_details"`
|
||||
Output []struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Role string `json:"role"`
|
||||
Content []struct {
|
||||
Type string `json:"type"`
|
||||
Transcript string `json:"transcript"`
|
||||
} `json:"content"`
|
||||
} `json:"output"`
|
||||
Usage Usage `json:"usage"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
InputTokenDetails struct {
|
||||
CachedTokens int `json:"cached_tokens"`
|
||||
TextTokens int `json:"text_tokens"`
|
||||
AudioTokens int `json:"audio_tokens"`
|
||||
} `json:"input_token_details"`
|
||||
OutputTokenDetails struct {
|
||||
TextTokens int `json:"text_tokens"`
|
||||
AudioTokens int `json:"audio_tokens"`
|
||||
} `json:"output_token_details"`
|
||||
}
|
||||
|
||||
func RealTimeProxy(c *gin.Context) {
|
||||
log.Println(c.Request.URL.String())
|
||||
var model string = c.Query("model")
|
||||
value := url.Values{}
|
||||
value.Add("model", model)
|
||||
realtimeURL := realtimeURL + "?" + value.Encode()
|
||||
|
||||
// 升级 HTTP 连接为 WebSocket
|
||||
clientConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Println("Upgrade error:", err)
|
||||
return
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
apikey, err := store.SelectKeyCacheByModel(model)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
// 连接到 OpenAI WebSocket
|
||||
headers := http.Header{"OpenAI-Beta": []string{"realtime=v1"}}
|
||||
|
||||
if apikey.ApiType == "azure" {
|
||||
headers.Set("api-key", apikey.Key)
|
||||
if apikey.EndPoint != "" {
|
||||
realtimeURL = fmt.Sprintf("%s/openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview", apikey.EndPoint)
|
||||
} else {
|
||||
realtimeURL = fmt.Sprintf(azureRealtimeURL, apikey.ResourceNmae)
|
||||
}
|
||||
} else {
|
||||
headers.Set("Authorization", "Bearer "+apikey.Key)
|
||||
}
|
||||
|
||||
conn := websocket.DefaultDialer
|
||||
if os.Getenv("LOCAL_PROXY") != "" {
|
||||
proxyUrl, _ := url.Parse(os.Getenv("LOCAL_PROXY"))
|
||||
conn.Proxy = http.ProxyURL(proxyUrl)
|
||||
}
|
||||
|
||||
openAIConn, _, err := conn.Dial(realtimeURL, headers)
|
||||
if err != nil {
|
||||
log.Println("OpenAI dial error:", err)
|
||||
return
|
||||
}
|
||||
defer openAIConn.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(c.Request.Context())
|
||||
defer cancel()
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
g.Go(func() error {
|
||||
return forwardMessages(ctx, c, clientConn, openAIConn)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return forwardMessages(ctx, c, openAIConn, clientConn)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
log.Println("Error in message forwarding:", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func forwardMessages(ctx context.Context, c *gin.Context, src, dst *websocket.Conn) error {
|
||||
usagelog := store.Tokens{Model: "gpt-4o-realtime-preview"}
|
||||
|
||||
token, _ := c.Get("localuser")
|
||||
|
||||
lu, err := store.GetUserByToken(token.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usagelog.UserID = int(lu.ID)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
messageType, message, err := src.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
|
||||
return nil // 正常关闭,不报错
|
||||
}
|
||||
return err
|
||||
}
|
||||
if messageType == websocket.TextMessage {
|
||||
var usage Usage
|
||||
err := json.Unmarshal(message, &usage)
|
||||
if err == nil {
|
||||
usagelog.PromptCount += usage.InputTokens
|
||||
usagelog.CompletionCount += usage.OutputTokens
|
||||
}
|
||||
|
||||
}
|
||||
err = dst.WriteMessage(messageType, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
usagelog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(usagelog.Model, usagelog.PromptCount, usagelog.CompletionCount))
|
||||
if err := store.Record(&usagelog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(usagelog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
SpeechEndpoint = "https://api.openai.com/v1/audio/speech"
|
||||
)
|
||||
|
||||
type SpeechRequest struct {
|
||||
Model string `json:"model"`
|
||||
Input string `json:"input"`
|
||||
Voice string `json:"voice"`
|
||||
}
|
||||
|
||||
func SpeechProxy(c *gin.Context) {
|
||||
var chatreq SpeechRequest
|
||||
if err := c.ShouldBindJSON(&chatreq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var chatlog store.Tokens
|
||||
chatlog.Model = chatreq.Model
|
||||
chatlog.CompletionCount = len(chatreq.Input)
|
||||
|
||||
token, _ := c.Get("localuser")
|
||||
|
||||
lu, err := store.GetUserByToken(token.(string))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
chatlog.UserID = int(lu.ID)
|
||||
|
||||
key, err := store.SelectKeyCache("openai")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
targetURL, _ := url.Parse(SpeechEndpoint)
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
||||
|
||||
proxy.Director = func(req *http.Request) {
|
||||
req.Header = c.Request.Header
|
||||
req.Header["Authorization"] = []string{"Bearer " + key.Key}
|
||||
req.Host = targetURL.Host
|
||||
req.URL.Scheme = targetURL.Scheme
|
||||
req.URL.Host = targetURL.Host
|
||||
req.URL.Path = targetURL.Path
|
||||
req.URL.RawPath = targetURL.RawPath
|
||||
|
||||
reqBytes, _ := json.Marshal(chatreq)
|
||||
req.Body = io.NopCloser(bytes.NewReader(reqBytes))
|
||||
req.ContentLength = int64(len(reqBytes))
|
||||
|
||||
}
|
||||
proxy.ModifyResponse = func(resp *http.Response) error {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
||||
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
||||
if err := store.Record(&chatlog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(chatlog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"opencatd-open/pkg/tokenizer"
|
||||
"opencatd-open/store"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/mp3"
|
||||
"github.com/faiface/beep/wav"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/vansante/go-ffprobe.v2"
|
||||
)
|
||||
|
||||
func WhisperProxy(c *gin.Context) {
|
||||
var chatlog store.Tokens
|
||||
|
||||
byteBody, _ := io.ReadAll(c.Request.Body)
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(byteBody))
|
||||
|
||||
model, _ := c.GetPostForm("model")
|
||||
|
||||
key, err := store.SelectKeyCache("openai")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
chatlog.Model = model
|
||||
|
||||
token, _ := c.Get("localuser")
|
||||
|
||||
lu, err := store.GetUserByToken(token.(string))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
chatlog.UserID = int(lu.ID)
|
||||
|
||||
if err := ParseWhisperRequestTokens(c, &chatlog, byteBody); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": gin.H{
|
||||
"message": err.Error(),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if key.EndPoint == "" {
|
||||
key.EndPoint = "https://api.openai.com"
|
||||
}
|
||||
targetUrl, _ := url.ParseRequestURI(key.EndPoint + c.Request.URL.String())
|
||||
log.Println(targetUrl)
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetUrl)
|
||||
proxy.Director = func(req *http.Request) {
|
||||
req.Host = targetUrl.Host
|
||||
req.URL.Scheme = targetUrl.Scheme
|
||||
req.URL.Host = targetUrl.Host
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+key.Key)
|
||||
}
|
||||
|
||||
proxy.ModifyResponse = func(resp *http.Response) error {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
chatlog.TotalTokens = chatlog.PromptCount + chatlog.CompletionCount
|
||||
chatlog.Cost = fmt.Sprintf("%.6f", tokenizer.Cost(chatlog.Model, chatlog.PromptCount, chatlog.CompletionCount))
|
||||
if err := store.Record(&chatlog); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := store.SumDaily(chatlog.UserID); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
func probe(fileReader io.Reader) (time.Duration, error) {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelFn()
|
||||
|
||||
data, err := ffprobe.ProbeReader(ctx, fileReader)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
duration := data.Format.DurationSeconds
|
||||
pduration, err := time.ParseDuration(fmt.Sprintf("%fs", duration))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Error parsing duration: %s", err)
|
||||
}
|
||||
return pduration, nil
|
||||
}
|
||||
|
||||
func getAudioDuration(file *multipart.FileHeader) (time.Duration, error) {
|
||||
var (
|
||||
streamer beep.StreamSeekCloser
|
||||
format beep.Format
|
||||
err error
|
||||
)
|
||||
|
||||
f, err := file.Open()
|
||||
defer f.Close()
|
||||
|
||||
// Get the file extension to determine the audio file type
|
||||
fileType := filepath.Ext(file.Filename)
|
||||
|
||||
switch fileType {
|
||||
case ".mp3":
|
||||
streamer, format, err = mp3.Decode(f)
|
||||
case ".wav":
|
||||
streamer, format, err = wav.Decode(f)
|
||||
case ".m4a":
|
||||
duration, err := probe(f)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return duration, nil
|
||||
default:
|
||||
return 0, errors.New("unsupported audio file format")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer streamer.Close()
|
||||
|
||||
// Calculate the audio file's duration.
|
||||
numSamples := streamer.Len()
|
||||
sampleRate := format.SampleRate
|
||||
duration := time.Duration(numSamples) * time.Second / time.Duration(sampleRate)
|
||||
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
func ParseWhisperRequestTokens(c *gin.Context, usage *store.Tokens, byteBody []byte) error {
|
||||
file, _ := c.FormFile("file")
|
||||
model, _ := c.GetPostForm("model")
|
||||
usage.Model = model
|
||||
|
||||
if file != nil {
|
||||
duration, err := getAudioDuration(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting audio duration:%s", err)
|
||||
}
|
||||
|
||||
if duration > 5*time.Minute {
|
||||
return fmt.Errorf("Audio duration exceeds 5 minutes")
|
||||
}
|
||||
// 计算时长,四舍五入到最接近的秒数
|
||||
usage.PromptCount = int(duration.Round(time.Second).Seconds())
|
||||
}
|
||||
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(byteBody))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
文档 https://www.microsoft.com/en-us/bing/apis/bing-web-search-api
|
||||
价格 https://www.microsoft.com/en-us/bing/apis/pricing
|
||||
|
||||
curl -H "Ocp-Apim-Subscription-Key: <yourkeygoeshere>" https://api.bing.microsoft.com/v7.0/search?q=今天上海天气怎么样
|
||||
curl -H "Ocp-Apim-Subscription-Key: 6fc7c97ebed54f75a5e383ee2272c917" https://api.bing.microsoft.com/v7.0/search?q=今天上海天气怎么样
|
||||
*/
|
||||
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
bingEndpoint = "https://api.bing.microsoft.com/v7.0/search"
|
||||
)
|
||||
|
||||
var subscriptionKey string
|
||||
|
||||
func init() {
|
||||
if os.Getenv("bing") != "" {
|
||||
subscriptionKey = os.Getenv("bing")
|
||||
} else {
|
||||
log.Println("bing key not found")
|
||||
}
|
||||
}
|
||||
|
||||
func BingSearch(searchParams SearchParams) (any, error) {
|
||||
params := url.Values{}
|
||||
params.Set("q", searchParams.Query)
|
||||
params.Set("count", "5")
|
||||
if searchParams.Num > 0 {
|
||||
params.Set("count", fmt.Sprintf("%d", searchParams.Num))
|
||||
}
|
||||
|
||||
reqURL, _ := url.Parse(bingEndpoint)
|
||||
reqURL.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", reqURL.String(), nil)
|
||||
req.Header.Set("Ocp-Apim-Subscription-Key", subscriptionKey)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
fmt.Println("Error sending request:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response:", err)
|
||||
return nil, err
|
||||
}
|
||||
result := gjson.ParseBytes(body).Get("webPages.value")
|
||||
|
||||
return result.Raw, nil
|
||||
|
||||
}
|
||||
|
||||
type SearchParams struct {
|
||||
Query string `form:"q"`
|
||||
Num int `form:"num,default=5"`
|
||||
}
|
||||
182
pkg/team/key.go
182
pkg/team/key.go
@@ -1,182 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"opencatd-open/pkg/azureopenai"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ApiType string `json:"api_type,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
}
|
||||
|
||||
func HandleKeys(c *gin.Context) {
|
||||
keys, err := store.GetAllKeys()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, keys)
|
||||
}
|
||||
|
||||
func HandleAddKey(c *gin.Context) {
|
||||
var body Key
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
body.Name = strings.ToLower(strings.TrimSpace(body.Name))
|
||||
body.Key = strings.TrimSpace(body.Key)
|
||||
if strings.HasPrefix(body.Name, "azure.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
k := &store.Key{
|
||||
ApiType: "azure",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(body.Name, "claude.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
if body.Endpoint == "" {
|
||||
body.Endpoint = "https://api.anthropic.com"
|
||||
}
|
||||
k := &store.Key{
|
||||
// ApiType: "anthropic",
|
||||
ApiType: "claude",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(body.Name, "google.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
|
||||
k := &store.Key{
|
||||
// ApiType: "anthropic",
|
||||
ApiType: "google",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(body.Name, "github.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
|
||||
k := &store.Key{
|
||||
ApiType: "github",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if body.ApiType == "" {
|
||||
if err := store.AddKey("openai", body.Key, body.Name); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
k := &store.Key{
|
||||
ApiType: body.ApiType,
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: azureopenai.GetResourceName(body.Endpoint),
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
k, err := store.GetKeyrByName(body.Name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, k)
|
||||
}
|
||||
|
||||
func HandleDelKey(c *gin.Context) {
|
||||
id := to.Int(c.Param("id"))
|
||||
if id < 1 {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid key id"})
|
||||
return
|
||||
}
|
||||
if err := store.DeleteKey(uint(id)); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid key id"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
}
|
||||
104
pkg/team/me.go
104
pkg/team/me.go
@@ -1,104 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
"time"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Handleinit(c *gin.Context) {
|
||||
user, err := store.GetUserByID(1)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
u := store.User{Name: "root", Token: uuid.NewString()}
|
||||
u.ID = 1
|
||||
if err := store.CreateUser(&u); err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
rootToken = u.Token
|
||||
resJSON := User{
|
||||
false,
|
||||
int(u.ID),
|
||||
u.UpdatedAt.Format(time.RFC3339),
|
||||
u.Name,
|
||||
u.Token,
|
||||
u.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
c.JSON(http.StatusOK, resJSON)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.ID == uint(1) {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "super user already exists, use cli to reset password",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func HandleMe(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
u, err := store.GetUserByToken(token[7:])
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
resJSON := User{
|
||||
false,
|
||||
int(u.ID),
|
||||
u.UpdatedAt.Format(time.RFC3339),
|
||||
u.Name,
|
||||
u.Token,
|
||||
u.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
c.JSON(http.StatusOK, resJSON)
|
||||
}
|
||||
|
||||
func HandleMeUsage(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
fromStr := c.Query("from")
|
||||
toStr := c.Query("to")
|
||||
getMonthStartAndEnd := func() (start, end string) {
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
now := time.Now().In(loc)
|
||||
|
||||
year, month, _ := now.Date()
|
||||
|
||||
startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0)
|
||||
|
||||
start = startOfMonth.Format("2006-01-02")
|
||||
end = endOfMonth.Format("2006-01-02")
|
||||
return
|
||||
}
|
||||
if fromStr == "" || toStr == "" {
|
||||
fromStr, toStr = getMonthStartAndEnd()
|
||||
}
|
||||
user, err := store.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusForbidden, err)
|
||||
return
|
||||
}
|
||||
usage, err := store.QueryUserUsage(to.String(user.ID), fromStr, toStr)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusForbidden, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, usage)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
rootToken string
|
||||
)
|
||||
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if rootToken == "" {
|
||||
u, err := store.GetUserByID(uint(1))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
rootToken = u.Token
|
||||
}
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" || token[:7] != "Bearer " {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if store.IsExistAuthCache(token[7:]) {
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/1/me") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
if token[7:] != rootToken {
|
||||
u, err := store.GetUserByID(uint(1))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if token[:7] != u.Token {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
rootToken = u.Token
|
||||
store.LoadAuthCache()
|
||||
}
|
||||
// 可以在这里对 token 进行验证并检查权限
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CORS() gin.HandlerFunc {
|
||||
config := cors.DefaultConfig()
|
||||
config.AllowAllOrigins = true
|
||||
config.AllowCredentials = true
|
||||
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
|
||||
config.AllowHeaders = []string{"*"}
|
||||
return cors.New(config)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func HandleUsage(c *gin.Context) {
|
||||
fromStr := c.Query("from")
|
||||
toStr := c.Query("to")
|
||||
getMonthStartAndEnd := func() (start, end string) {
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
now := time.Now().In(loc)
|
||||
|
||||
year, month, _ := now.Date()
|
||||
|
||||
startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0)
|
||||
|
||||
start = startOfMonth.Format("2006-01-02")
|
||||
end = endOfMonth.Format("2006-01-02")
|
||||
return
|
||||
}
|
||||
if fromStr == "" || toStr == "" {
|
||||
fromStr, toStr = getMonthStartAndEnd()
|
||||
}
|
||||
|
||||
usage, err := store.QueryUsage(fromStr, toStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, usage)
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
IsDelete bool `json:"IsDelete,omitempty"`
|
||||
ID int `json:"id,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
}
|
||||
|
||||
func HandleUsers(c *gin.Context) {
|
||||
users, err := store.GetAllUsers()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
func HandleAddUser(c *gin.Context) {
|
||||
var body User
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if len(body.Name) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid user name"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.AddUser(body.Name, uuid.NewString()); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
u, err := store.GetUserByName(body.Name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, u)
|
||||
}
|
||||
|
||||
func HandleDelUser(c *gin.Context) {
|
||||
id := to.Int(c.Param("id"))
|
||||
if id <= 1 {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
if err := store.DeleteUser(uint(id)); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
}
|
||||
|
||||
func HandleResetUserToken(c *gin.Context) {
|
||||
id := to.Int(c.Param("id"))
|
||||
newtoken := c.Query("token")
|
||||
if newtoken == "" {
|
||||
newtoken = uuid.NewString()
|
||||
}
|
||||
|
||||
if err := store.UpdateUser(uint(id), newtoken); err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
u, err := store.GetUserByID(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if u.ID == uint(1) {
|
||||
rootToken = u.Token
|
||||
}
|
||||
c.JSON(http.StatusOK, u)
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package tokenizer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/pkoukk/tiktoken-go"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
func NumTokensFromMessages(messages []openai.ChatCompletionMessage, model string) (numTokens int) {
|
||||
tkm, err := tiktoken.EncodingForModel(model)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("EncodingForModel: %v", err)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var tokensPerMessage, tokensPerName int
|
||||
|
||||
switch model {
|
||||
case "gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-32k-0613":
|
||||
tokensPerMessage = 3
|
||||
tokensPerName = 1
|
||||
case "gpt-3.5-turbo-0301":
|
||||
tokensPerMessage = 4 // every message follows <|start|>{role/name}\n{content}<|end|>\n
|
||||
tokensPerName = -1 // if there's a name, the role is omitted
|
||||
default:
|
||||
if strings.Contains(model, "gpt-3.5-turbo") {
|
||||
log.Println("warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.")
|
||||
return NumTokensFromMessages(messages, "gpt-3.5-turbo-0613")
|
||||
} else if strings.Contains(model, "gpt-4") {
|
||||
log.Println("warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
|
||||
return NumTokensFromMessages(messages, "gpt-4-0613")
|
||||
} else {
|
||||
err = fmt.Errorf("warning: unknown model [%s]. Use default calculation method converted tokens.", model)
|
||||
log.Println(err)
|
||||
return NumTokensFromMessages(messages, "gpt-3.5-turbo-0613")
|
||||
}
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
numTokens += tokensPerMessage
|
||||
numTokens += len(tkm.Encode(message.Content, nil, nil))
|
||||
numTokens += len(tkm.Encode(message.Role, nil, nil))
|
||||
numTokens += len(tkm.Encode(message.Name, nil, nil))
|
||||
if message.Name != "" {
|
||||
numTokens += tokensPerName
|
||||
}
|
||||
}
|
||||
numTokens += 3
|
||||
return numTokens
|
||||
}
|
||||
|
||||
func NumTokensFromStr(messages string, model string) (num_tokens int) {
|
||||
tkm, err := tiktoken.EncodingForModel(model)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Unsupport Model,use cl100k_base Encode")
|
||||
tkm, _ = tiktoken.GetEncoding("cl100k_base")
|
||||
}
|
||||
|
||||
num_tokens += len(tkm.Encode(messages, nil, nil))
|
||||
return num_tokens
|
||||
}
|
||||
|
||||
// https://openai.com/pricing
|
||||
func Cost(model string, promptCount, completionCount int) float64 {
|
||||
var cost, prompt, completion float64
|
||||
prompt = float64(promptCount)
|
||||
completion = float64(completionCount)
|
||||
|
||||
switch model {
|
||||
case "gpt-3.5-turbo-0301":
|
||||
cost = 0.002 * float64((prompt+completion)/1000)
|
||||
case "gpt-3.5-turbo", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-0125":
|
||||
cost = 0.0015*float64((prompt)/1000) + 0.002*float64(completion/1000)
|
||||
case "gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613":
|
||||
cost = 0.003*float64((prompt)/1000) + 0.004*float64(completion/1000)
|
||||
case "gpt-4", "gpt-4-0613", "gpt-4-0314":
|
||||
cost = 0.03*float64(prompt/1000) + 0.06*float64(completion/1000)
|
||||
case "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613":
|
||||
cost = 0.06*float64(prompt/1000) + 0.12*float64(completion/1000)
|
||||
case "gpt-4-1106-preview", "gpt-4-vision-preview", "gpt-4-0125-preview", "gpt-4-turbo-preview":
|
||||
cost = 0.01*float64(prompt/1000) + 0.03*float64(completion/1000)
|
||||
case "gpt-4-turbo", "gpt-4-turbo-2024-04-09":
|
||||
cost = 0.01*float64(prompt/1000) + 0.03*float64(completion/1000)
|
||||
// omni
|
||||
case "gpt-4o", "gpt-4o-2024-08-06":
|
||||
cost = 0.0025*float64(prompt/1000) + 0.01*float64(completion/1000)
|
||||
case "gpt-4o-2024-05-13":
|
||||
cost = 0.005*float64(prompt/1000) + 0.015*float64(completion/1000)
|
||||
case "gpt-4o-mini", "gpt-4o-mini-2024-07-18":
|
||||
cost = 0.00015*float64(prompt/1000) + 0.0006*float64(completion/1000)
|
||||
case "chatgpt-4o-latest":
|
||||
cost = 0.005*float64(prompt/1000) + 0.015*float64(completion/1000)
|
||||
// o1
|
||||
case "o1-preview", "o1-preview-2024-09-12":
|
||||
cost = 0.015*float64(prompt/1000) + 0.06*float64(completion/1000)
|
||||
case "o1-mini", "o1-mini-2024-09-12":
|
||||
cost = 0.003*float64(prompt/1000) + 0.012*float64(completion/1000)
|
||||
// Realtime API
|
||||
// Audio*
|
||||
// $0.1 / 1K input tokens
|
||||
// $0.2 / 1K output tokens
|
||||
case "gpt-4o-realtime-preview", "gpt-4o-realtime-preview-2024-10-01":
|
||||
cost = 0.005*float64(prompt/1000) + 0.020*float64(completion/1000)
|
||||
case "gpt-4o-realtime-preview.audio", "gpt-4o-realtime-preview-2024-10-01.audio":
|
||||
cost = 0.1*float64(prompt/1000) + 0.2*float64(completion/1000)
|
||||
case "whisper-1":
|
||||
// 0.006$/min
|
||||
cost = 0.006 * float64(prompt+completion) / 60
|
||||
case "tts-1":
|
||||
cost = 0.015 * float64(prompt+completion)
|
||||
case "tts-1-hd":
|
||||
cost = 0.03 * float64(prompt+completion)
|
||||
case "dall-e-2.256x256":
|
||||
cost = float64(0.016 * completion)
|
||||
case "dall-e-2.512x512":
|
||||
cost = float64(0.018 * completion)
|
||||
case "dall-e-2.1024x1024":
|
||||
cost = float64(0.02 * completion)
|
||||
case "dall-e-3.256x256":
|
||||
cost = float64(0.04 * completion)
|
||||
case "dall-e-3.512x512":
|
||||
cost = float64(0.04 * completion)
|
||||
case "dall-e-3.1024x1024":
|
||||
cost = float64(0.04 * completion)
|
||||
case "dall-e-3.1024x1792", "dall-e-3.1792x1024":
|
||||
cost = float64(0.08 * completion)
|
||||
case "dall-e-3.256x256.hd":
|
||||
cost = float64(0.08 * completion)
|
||||
case "dall-e-3.512x512.hd":
|
||||
cost = float64(0.08 * completion)
|
||||
case "dall-e-3.1024x1024.hd":
|
||||
cost = float64(0.08 * completion)
|
||||
case "dall-e-3.1024x1792.hd", "dall-e-3.1792x1024.hd":
|
||||
cost = float64(0.12 * completion)
|
||||
|
||||
// claude /million tokens
|
||||
// https://aws.amazon.com/cn/bedrock/pricing/
|
||||
case "claude-v1", "claude-v1-100k":
|
||||
cost = 11.02/1000000*float64(prompt) + (32.68/1000000)*float64(completion)
|
||||
case "claude-instant-v1", "claude-instant-v1-100k":
|
||||
cost = (1.63/1000000)*float64(prompt) + (5.51/1000000)*float64(completion)
|
||||
case "claude-2", "claude-2.1":
|
||||
cost = (8.0/1000000)*float64(prompt) + (24.0/1000000)*float64(completion)
|
||||
case "claude-3-haiku":
|
||||
cost = (0.00025/1000)*float64(prompt) + (0.00125/1000)*float64(completion)
|
||||
case "claude-3-sonnet":
|
||||
cost = (0.003/1000)*float64(prompt) + (0.015/1000)*float64(completion)
|
||||
case "claude-3-opus":
|
||||
cost = (0.015/1000)*float64(prompt) + (0.075/1000)*float64(completion)
|
||||
case "claude-3-haiku-20240307":
|
||||
cost = (0.00025/1000)*float64(prompt) + (0.00125/1000)*float64(completion)
|
||||
case "claude-3-5-haiku-latest", "claude-3-5-haiku-20241022":
|
||||
cost = (0.001/1000)*float64(prompt) + (0.005/1000)*float64(completion)
|
||||
case "claude-3-sonnet-20240229":
|
||||
cost = (0.003/1000)*float64(prompt) + (0.015/1000)*float64(completion)
|
||||
case "claude-3-opus-20240229":
|
||||
cost = (0.015/1000)*float64(prompt) + (0.075/1000)*float64(completion)
|
||||
case "claude-3-5-sonnet", "claude-3-5-sonnet-latest", "claude-3-5-sonnet-20240620", "claude-3-5-sonnet-20241022":
|
||||
cost = (0.003/1000)*float64(prompt) + (0.015/1000)*float64(completion)
|
||||
// google
|
||||
// https://ai.google.dev/pricing?hl=zh-cn
|
||||
case "gemini-pro":
|
||||
cost = (0.0005/1000)*float64(prompt) + (0.0015/1000)*float64(completion)
|
||||
case "gemini-pro-vision":
|
||||
cost = (0.0005/1000)*float64(prompt) + (0.0015/1000)*float64(completion)
|
||||
case "gemini-1.5-pro-latest":
|
||||
cost = (0.0035/1000)*float64(prompt) + (0.0105/1000)*float64(completion)
|
||||
case "gemini-1.5-flash-latest":
|
||||
cost = (0.00035/1000)*float64(prompt) + (0.00053/1000)*float64(completion)
|
||||
case "gemini-2.0-flash-exp":
|
||||
cost = (0.00035/1000)*float64(prompt) + (0.00053/1000)*float64(completion)
|
||||
case "learnlm-1.5-pro-experimental", " gemini-exp-1114", "gemini-exp-1121", "gemini-exp-1206":
|
||||
cost = (0.00035/1000)*float64(prompt) + (0.00053/1000)*float64(completion)
|
||||
|
||||
// Mistral AI
|
||||
// https://docs.mistral.ai/platform/pricing/
|
||||
case "mistral-small-latest":
|
||||
cost = (0.002/1000)*float64(prompt) + (0.006/1000)*float64(completion)
|
||||
case "mistral-medium-latest":
|
||||
cost = (0.0027/1000)*float64(prompt) + (0.0081/1000)*float64(completion)
|
||||
case "mistral-large-latest":
|
||||
cost = (0.008/1000)*float64(prompt) + (0.024/1000)*float64(completion)
|
||||
|
||||
default:
|
||||
if strings.Contains(model, "gpt-3.5-turbo") {
|
||||
cost = 0.003 * float64((prompt+completion)/1000)
|
||||
} else if strings.Contains(model, "gpt-4") {
|
||||
cost = 0.06 * float64((prompt+completion)/1000)
|
||||
} else {
|
||||
cost = 0.002 * float64((prompt+completion)/1000)
|
||||
}
|
||||
}
|
||||
return cost
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
https://docs.anthropic.com/zh-CN/api/claude-on-vertex-ai
|
||||
|
||||
MODEL_ID=claude-3-5-sonnet@20240620
|
||||
REGION=us-east5
|
||||
PROJECT_ID=MY_PROJECT_ID
|
||||
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
|
||||
-H "Content-Type: application/json" \
|
||||
https://$LOCATION-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/publishers/anthropic/models/${MODEL_ID}:streamRawPredict \
|
||||
-d '{
|
||||
"anthropic_version": "vertex-2023-10-16",
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": "介绍一下你自己"
|
||||
}],
|
||||
"stream": true,
|
||||
"max_tokens": 4096
|
||||
}'
|
||||
|
||||
quota:
|
||||
https://console.cloud.google.com/iam-admin/quotas?hl=zh-cn
|
||||
*/
|
||||
|
||||
package vertexai
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
// json文件存储在ApiKey.ApiSecret中
|
||||
type VertexSecretKey struct {
|
||||
Type string `json:"type"`
|
||||
ProjectID string `json:"project_id"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientID string `json:"client_id"`
|
||||
AuthURI string `json:"auth_uri"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"`
|
||||
ClientX509CertURL string `json:"client_x509_cert_url"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
type VertexClaudeModel struct {
|
||||
VertexName string
|
||||
Region string
|
||||
}
|
||||
|
||||
var VertexClaudeModelMap = map[string]VertexClaudeModel{
|
||||
"claude-3-opus": {
|
||||
VertexName: "claude-3-opus@20240229",
|
||||
Region: "us-east5",
|
||||
},
|
||||
"claude-3-sonnet": {
|
||||
VertexName: "claude-3-sonnet@20240229",
|
||||
Region: "us-central1",
|
||||
// Region: "asia-southeast1",
|
||||
},
|
||||
"claude-3-haiku": {
|
||||
VertexName: "claude-3-haiku@20240307",
|
||||
Region: "us-central1",
|
||||
// Region: "europe-west4",
|
||||
},
|
||||
"claude-3-opus-20240229": {
|
||||
VertexName: "claude-3-opus@20240229",
|
||||
Region: "us-east5",
|
||||
},
|
||||
"claude-3-sonnet-20240229": {
|
||||
VertexName: "claude-3-sonnet@20240229",
|
||||
Region: "us-central1",
|
||||
// Region: "asia-southeast1",
|
||||
},
|
||||
"claude-3-haiku-20240307": {
|
||||
VertexName: "claude-3-haiku@20240307",
|
||||
Region: "us-central1",
|
||||
// Region: "europe-west4",
|
||||
},
|
||||
"claude-3-5-sonnet": {
|
||||
VertexName: "claude-3-5-sonnet@20240620",
|
||||
Region: "us-east5",
|
||||
// Region: "europe-west1",
|
||||
},
|
||||
"claude-3-5-sonnet-20240620": {
|
||||
VertexName: "claude-3-5-sonnet@20240620",
|
||||
Region: "us-east5",
|
||||
// Region: "europe-west1",
|
||||
},
|
||||
"claude-3-5-sonnet-20241022": {
|
||||
VertexName: "claude-3-5-sonnet-v2@20241022",
|
||||
Region: "us-east5",
|
||||
},
|
||||
"claude-3-5-sonnet-latest": { //可能没有容量,指向老模型
|
||||
VertexName: "claude-3-5-sonnet@20240620",
|
||||
Region: "us-east5",
|
||||
},
|
||||
}
|
||||
|
||||
func createSignedJWT(email, privateKeyPEM string) (string, error) {
|
||||
block, _ := pem.Decode([]byte(privateKeyPEM))
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("failed to parse PEM block containing the private key")
|
||||
}
|
||||
|
||||
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rsaKey, ok := privateKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("not an RSA private key")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
claims := jwt.MapClaims{
|
||||
"iss": email,
|
||||
"aud": "https://www.googleapis.com/oauth2/v4/token",
|
||||
"iat": now.Unix(),
|
||||
"exp": now.Add(10 * time.Minute).Unix(),
|
||||
"scope": "https://www.googleapis.com/auth/cloud-platform",
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
return token.SignedString(rsaKey)
|
||||
}
|
||||
|
||||
func exchangeJwtForAccessToken(signedJWT string) (string, error) {
|
||||
authURL := "https://www.googleapis.com/oauth2/v4/token"
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
|
||||
data.Set("assertion", signedJWT)
|
||||
|
||||
client := http.DefaultClient
|
||||
if os.Getenv("LOCAL_PROXY") != "" {
|
||||
if proxyUrl, err := url.Parse(os.Getenv("LOCAL_PROXY")); err == nil {
|
||||
client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.PostForm(authURL, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
accessToken, ok := result["access_token"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("access token not found in response")
|
||||
}
|
||||
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
// 获取gcloud auth token
|
||||
func GcloudAuth(ClientEmail, PrivateKey string) (string, error) {
|
||||
signedJWT, err := createSignedJWT(ClientEmail, PrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token, err := exchangeJwtForAccessToken(signedJWT)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid jwt token: %v\n", err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"opencatd-open/pkg/claude"
|
||||
"opencatd-open/pkg/google"
|
||||
"opencatd-open/pkg/openai"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ChatHandler(c *gin.Context) {
|
||||
var chatreq openai.ChatCompletionRequest
|
||||
if err := c.ShouldBindJSON(&chatreq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatreq.Model, "gpt") || strings.HasPrefix(chatreq.Model, "o1-") {
|
||||
openai.ChatProxy(c, &chatreq)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatreq.Model, "claude") {
|
||||
claude.ChatProxy(c, &chatreq)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(chatreq.Model, "gemini") || strings.HasPrefix(chatreq.Model, "learnlm") {
|
||||
google.ChatProxy(c, &chatreq)
|
||||
return
|
||||
}
|
||||
}
|
||||
1024
router/router.go
1024
router/router.go
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
@@ -50,58 +49,6 @@ func SelectKeyCache(apitype string) (Key, error) {
|
||||
if item.Object.(Key).ApiType == apitype {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
if apitype == "openai" {
|
||||
if item.Object.(Key).ApiType == "azure" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
if item.Object.(Key).ApiType == "github" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
}
|
||||
if apitype == "claude" {
|
||||
if item.Object.(Key).ApiType == "vertex" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return Key{}, errors.New("No key found")
|
||||
} else if len(keys) == 1 {
|
||||
return keys[0], nil
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
idx := rand.Intn(len(keys))
|
||||
return keys[idx], nil
|
||||
}
|
||||
|
||||
func SelectKeyCacheByModel(model string) (Key, error) {
|
||||
var keys []Key
|
||||
items := KeysCache.Items()
|
||||
for _, item := range items {
|
||||
if strings.Contains(model, "realtime") {
|
||||
if item.Object.(Key).ApiType == "openai" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
if item.Object.(Key).ApiType == "azure" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(model, "gpt-") {
|
||||
if item.Object.(Key).ApiType == "openai" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
if item.Object.(Key).ApiType == "azure" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
if item.Object.(Key).ApiType == "github" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(model, "o1-") || model == "chatgpt-4o-latest" {
|
||||
if item.Object.(Key).ApiType == "openai" {
|
||||
keys = append(keys, item.Object.(Key))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return Key{}, errors.New("No key found")
|
||||
|
||||
@@ -2,36 +2,9 @@ package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"opencatd-open/pkg/vertexai"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// check vertex
|
||||
if os.Getenv("Vertex") != "" {
|
||||
vertex_auth := os.Getenv("Vertex")
|
||||
var Vertex vertexai.VertexSecretKey
|
||||
if err := json.Unmarshal([]byte(vertex_auth), &Vertex); err != nil {
|
||||
log.Fatalln(fmt.Errorf("import vertex_auth json error: %w", err))
|
||||
return
|
||||
}
|
||||
key := Key{
|
||||
ApiType: "vertex",
|
||||
Name: Vertex.ProjectID,
|
||||
Key: vertex_auth,
|
||||
ApiSecret: vertex_auth,
|
||||
}
|
||||
if err := db.Where("name = ?", Vertex.ProjectID).FirstOrCreate(&key).Error; err != nil {
|
||||
log.Fatalln(fmt.Errorf("import vertex_auth json error: %w", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
LoadKeysCache()
|
||||
}
|
||||
|
||||
type Key struct {
|
||||
ID uint `gorm:"primarykey" json:"id,omitempty"`
|
||||
Key string `gorm:"unique;not null" json:"key,omitempty"`
|
||||
@@ -41,7 +14,6 @@ type Key struct {
|
||||
EndPoint string `gorm:"column:endpoint"`
|
||||
ResourceNmae string `gorm:"column:resource_name"`
|
||||
DeploymentName string `gorm:"column:deployment_name"`
|
||||
ApiSecret string `gorm:"column:api_secret"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user