Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance the feature of ai-proxy plugin #976

Merged
merged 3 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 257 additions & 1 deletion plugins/wasm-go/extensions/ai-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ Azure OpenAI 所对应的 `type` 为 `azure`。它特有的配置字段如下:

#### 通义千问(Qwen)

通义千问所对应的 `type` 为 `qwen`。它并无特有的配置字段。
通义千问所对应的 `type` 为 `qwen`。它特有的配置字段如下:

| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|--------------------|-----------------|------|-----|------------------------------------------------------------------|
| `qwenEnableSearch` | boolean | 非必填 | - | 是否启用通义千问内置的互联网搜索功能。 |
| `qwenFileIds` | array of string | 非必填 | - | 通过文件接口上传至Dashscope的文件 ID,其内容将被用做 AI 对话的上下文。不可与 `context` 字段同时配置。 |

#### 百川智能 (Baichuan AI)

Expand All @@ -72,6 +77,18 @@ Azure OpenAI 所对应的 `type` 为 `azure`。它特有的配置字段如下:

零一万物所对应的 `type` 为 `yi`。它并无特有的配置字段。

#### Groq

Groq 所对应的 `type` 为 `groq`。它并无特有的配置字段。

#### Anthropic Claude

Anthropic Claude 所对应的 `type` 为 `claude`。它特有的配置字段如下:

| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|-----------|--------|-----|-----|-------------------|
| `version` | string | 必填 | - | Claude 服务的 API 版本 |

## 用法示例

### 使用 OpenAI 协议代理 Azure OpenAI 服务
Expand Down Expand Up @@ -291,6 +308,63 @@ provider:
}
```

### 使用通义千问配合其原生的文件上下文

提前上传文件至通义千问,以文件内容作为上下文使用其 AI 服务。

**配置信息**

```yaml
provider:
type: qwen
apiTokens:
- "YOUR_QWEN_API_TOKEN"
modelMapping:
"*": "qwen-long" # 通义千问的文件上下文只能在 qwen-long 模型下使用
qwenFileIds:
- "file-fe-xxx"
- "file-fe-yyy"
```

**请求示例**

```json
{
"model": "gpt-4-turbo",
"messages": [
{
"role": "user",
"content": "请概述文案内容"
}
],
"temperature": 0.3
}
```

**响应示例**

```json
{
"output": {
"choices": [
{
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "您上传了两个文件,`context.txt` 和 `context_2.txt`,它们似乎都包含了关于xxxx"
}
}
]
},
"usage": {
"total_tokens": 2023,
"output_tokens": 530,
"input_tokens": 1493
},
"request_id": "187e99ba-5b64-9ffe-8f69-01dafbaf6ed7"
}
```

### 使用月之暗面配合其原生的文件上下文

提前上传文件至月之暗面,以文件内容作为上下文使用其 AI 服务。
Expand Down Expand Up @@ -347,3 +421,185 @@ provider:
}
}
```

### 使用 OpenAI 协议代理 Groq 服务

**配置信息**

```yaml
provider:
type: groq
apiTokens:
- "YOUR_GROQ_API_TOKEN"
```

**请求示例**

```json
{
"model": "llama3-8b-8192",
"messages": [
{
"role": "user",
"content": "你好,你是谁?"
}
]
}
```

**响应示例**

```json
{
"id": "chatcmpl-26733989-6c52-4056-b7a9-5da791bd7102",
"object": "chat.completion",
"created": 1715917967,
"model": "llama3-8b-8192",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "😊 Ni Hao! (That's \"hello\" in Chinese!)\n\nI am LLaMA, an AI assistant developed by Meta AI that can understand and respond to human input in a conversational manner. I'm not a human, but a computer program designed to simulate conversations and answer questions to the best of my ability. I'm happy to chat with you in Chinese or help with any questions or topics you'd like to discuss! 😊"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 16,
"prompt_time": 0.005,
"completion_tokens": 89,
"completion_time": 0.104,
"total_tokens": 105,
"total_time": 0.109
},
"system_fingerprint": "fp_dadc9d6142",
"x_groq": {
"id": "req_01hy2awmcxfpwbq56qh6svm7qz"
}
}
```

### 使用 OpenAI 协议代理 Claude 服务

**配置信息**

```yaml
provider:
type: claude
apiTokens:
- "YOUR_CLAUDE_API_TOKEN"
```

**请求示例**

```json
{
"model": "claude-3-opus-20240229",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "你好,你是谁?"
}
]
}
```

**响应示例**

```json
{
"id": "msg_01K8iLH18FGN7Xd9deurwtoD",
"type": "message",
"role": "assistant",
"model": "claude-3-opus-20240229",
"stop_sequence": null,
"usage": {
"input_tokens": 16,
"output_tokens": 141
},
"content": [
{
"type": "text",
"text": "你好!我是Claude,一个由Anthropic公司开发的人工智能助手。我的任务是尽我所能帮助人类,比如回答问题,提供建议和意见,协助完成任务等。我掌握了很多知识,也具备一定的分析和推理能力,但我不是人类,也没有实体的身体。很高兴认识你!如果有什么需要帮助的地方,欢迎随时告诉我。"
}
],
"stop_reason": "end_turn"
}
```

## 完整配置示例

以下以使用 OpenAI 协议代理 Groq 服务为例,展示完整的插件配置示例。

```yaml
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: ai-proxy-groq
namespace: higress-system
spec:
matchRules:
- config:
provider:
type: groq
apiTokens:
- "YOUR_API_TOKEN"
ingress:
- groq
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/ai-proxy:1.0.0
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/backend-protocol: HTTPS
higress.io/destination: groq.dns
higress.io/proxy-ssl-name: api.groq.com
higress.io/proxy-ssl-server-name: "on"
labels:
higress.io/resource-definer: higress
name: groq
namespace: higress-system
spec:
ingressClassName: higress
rules:
- host: <YOUR-DOMAIN>
http:
paths:
- backend:
resource:
apiGroup: networking.higress.io
kind: McpBridge
name: default
path: /
pathType: Prefix
---
apiVersion: networking.higress.io/v1
kind: McpBridge
metadata:
name: default
namespace: higress-system
spec:
registries:
- domain: api.groq.com
name: groq
port: 443
type: dns
```

访问示例:

```bash
curl "http://<YOUR-DOMAIN>/v1/chat/completions" -H "Content-Type: application/json" -d '{
"model": "llama3-8b-8192",
"messages": [
{
"role": "user",
"content": "你好,你是谁?"
}
]
}'
```
2 changes: 1 addition & 1 deletion plugins/wasm-go/extensions/ai-proxy/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..

require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.14.3
)
Expand Down
4 changes: 2 additions & 2 deletions plugins/wasm-go/extensions/ai-proxy/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ 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/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43 h1:dCw7F/9ciw4NZN7w68wQRaygZ2zGOWMTIEoRvP1tlWs=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
13 changes: 13 additions & 0 deletions plugins/wasm-go/extensions/ai-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ func onHttpRequestHeader(ctx wrapper.HttpContext, pluginConfig config.PluginConf
return types.ActionContinue
}

log.Debugf("[onHttpRequestHeader] provider=%s", activeProvider.GetProviderType())

rawPath := ctx.Path()
path, _ := url.Parse(rawPath)
apiName := getApiName(path.Path)
Expand Down Expand Up @@ -125,6 +127,14 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, pluginConfig config.PluginCo
return types.ActionContinue
}

contentType, err := proxywasm.GetHttpResponseHeader("Content-Type")
if err != nil || !strings.HasPrefix(contentType, "text/event-stream") {
if err != nil {
log.Errorf("unable to load content-type header from response: %v", err)
}
ctx.BufferResponseBody()
}

if handler, ok := activeProvider.(provider.ResponseHeadersHandler); ok {
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
action, err := handler.OnResponseHeaders(ctx, apiName, log)
Expand All @@ -143,6 +153,9 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, pluginConfig config.PluginCo
ctx.BufferResponseBody()
}

// Disable the route re-calculation since the plugin may modify some headers related to the chosen route.
ctx.DisableReroute()

return types.ActionContinue
}

Expand Down
Loading
Loading