Compare commits

..

21 Commits

Author SHA1 Message Date
johnlanni
272d693df3 fix higress-console version in helm chart 2025-06-18 09:15:46 +08:00
澄潭
69bc800198 fix: The mcp to rest capability of the mcp server supports returning status without returning a body from the backend, and instead responds via sse (#2445) 2025-06-17 21:26:38 +08:00
澄潭
1daaa4b880 release 2.1.5-rc.1 (#2446) 2025-06-17 21:23:42 +08:00
澄潭
6e31a7b67c update envoy and istio (#2440) 2025-06-17 17:22:46 +08:00
澄潭
91f070906a feat: add mcp-router plugin (#2409) 2025-06-17 15:40:13 +08:00
澄潭
e3aeddcc24 add release-notes of 2.1.4 (#2433) 2025-06-17 14:41:14 +08:00
woody
926913f0e7 feat(ai-proxy): add support for OpenAI Fine-Tuning API (#2424) 2025-06-17 13:44:00 +08:00
mirror
c471bb2003 feat: add default route support for wanx image&video synthesis (#2431) 2025-06-17 13:43:26 +08:00
澄潭
0b9256617e fix: When configuring an MCP server for SSE forwarding, the controller may crash (#2423) 2025-06-16 16:08:39 +08:00
hourmoneys
2670ecbf8e feat: Add AI-based bidding information tool MCP service (#2343) 2025-06-16 10:14:46 +08:00
mirror
7040e4bd34 feat: support for wanxiang image/video generation in ai-proxy & ai-statistics (#2378) 2025-06-16 09:39:37 +08:00
xuruidong
de8a4d0b03 docs: fix broken link in mcp-servers README_zh.md (#2418) 2025-06-15 22:14:10 +08:00
Xijun Dai
b33a3a4d2e fix(ai-proxy): fix gemini provider missing finishReason (#2408)
Signed-off-by: Xijun Dai <daixijun1990@gmail.com>
Co-authored-by: Se7en <chengzw258@163.com>
2025-06-13 21:51:44 +08:00
澄潭
087cb48fc5 opt: unify the end-of-line markers in the MCP session filter. (#2403) 2025-06-12 18:58:56 +08:00
hourmoneys
95f32002d2 add mcp-server doc (#2327) 2025-06-12 17:14:39 +08:00
Xijun Dai
fb8dd819e9 feat(ai-proxy): Adjust the streaming response structure to keep it consistent with the openai (#2391)
Signed-off-by: Xijun Dai <daixijun1990@gmail.com>
2025-06-12 16:25:35 +08:00
EricaLiu
86934b3203 fix: fix const McpStreamableProtocol spell mistake (#2405) 2025-06-12 15:35:39 +08:00
HaoJie Liu
38068ee43d fix(ai-proxy): fix bedrock Sigv4 mismatch (#2402) 2025-06-12 10:46:02 +08:00
EricaLiu
d81573e0d2 fix: change auto generate se namespace to mcp (#2398) 2025-06-11 20:30:48 +08:00
tangchang
312b80f91d feat: Plugin server supports k8s deployment and configures the default download URL of the plugin(#2232, #2280,#2312) (#2389)
Co-authored-by: xujingfeng <jingfeng.xjf@alibaba-inc.com>
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-06-11 12:20:09 +08:00
zty98751
e42e6eeee6 split translae-readme from helm-docs action 2025-06-11 09:52:41 +08:00
80 changed files with 2665 additions and 874 deletions

View File

@@ -39,126 +39,3 @@ jobs:
fi
git diff --exit-code
rm -f ./helm-docs
translate-readme:
needs: helm
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Compare README.md
id: compare_readme
run: |
cd ./helm/higress
BASE_BRANCH=${GITHUB_BASE_REF:-main}
git fetch origin $BASE_BRANCH
if git diff --quiet origin/$BASE_BRANCH -- README.md; then
echo "README.md has no local changes compared to $BASE_BRANCH. Skipping translation."
echo "skip_translation=true" >> $GITHUB_ENV
else
echo "README.md has local changes compared to $BASE_BRANCH. Proceeding with translation."
echo "skip_translation=false" >> $GITHUB_ENV
echo "--------- diff ---------"
git diff origin/$BASE_BRANCH -- README.md
echo "------------------------"
fi
- name: Translate README.md to Chinese
if: env.skip_translation == 'false'
env:
API_URL: ${{ secrets.HIGRESS_OPENAI_API_URL }}
API_KEY: ${{ secrets.HIGRESS_OPENAI_API_KEY }}
API_MODEL: ${{ secrets.HIGRESS_OPENAI_API_MODEL }}
run: |
cat << 'EOF' > translate_readme.py
import os
import json
import requests
API_URL = os.environ["API_URL"]
API_KEY = os.environ["API_KEY"]
API_MODEL = os.environ["API_MODEL"]
README_PATH = "./helm/higress/README.md"
OUTPUT_PATH = "./helm/higress/README.zh.md"
def stream_translation(api_url, api_key, payload):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
response = requests.post(api_url, headers=headers, json=payload, stream=True)
response.raise_for_status()
with open(OUTPUT_PATH, "w", encoding="utf-8") as out_file:
for line in response.iter_lines(decode_unicode=True):
if line.strip() == "" or not line.startswith("data: "):
continue
data = line[6:]
if data.strip() == "[DONE]":
break
try:
chunk = json.loads(data)
content = chunk["choices"][0]["delta"].get("content", "")
if content:
out_file.write(content)
except Exception as e:
print("Error parsing chunk:", e)
def main():
if not os.path.exists(README_PATH):
print("README.md not found!")
return
with open(README_PATH, "r", encoding="utf-8") as f:
content = f.read()
payload = {
"model": API_MODEL,
"messages": [
{
"role": "system",
"content": "You are a translation assistant that translates English Markdown text to Chinese. Preserve original Markdown formatting and line breaks."
},
{
"role": "user",
"content": content
}
],
"temperature": 0.3,
"stream": True
}
print("Streaming translation started...")
stream_translation(API_URL, API_KEY, payload)
print(f"Translation completed and saved to {OUTPUT_PATH}.")
if __name__ == "__main__":
main()
EOF
python3 translate_readme.py
rm -rf translate_readme.py
- name: Create Pull Request
if: env.skip_translation == 'false'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update helm translated README.zh.md"
branch: update-helm-readme-zh
title: "Update helm translated README.zh.md"
body: |
This PR updates the translated README.zh.md file.
- Automatically generated by GitHub Actions
labels: translation, automated
base: main

131
.github/workflows/translate-readme.yaml vendored Normal file
View File

@@ -0,0 +1,131 @@
name: "Helm Docs"
on:
workflow_dispatch: ~
push:
branches: [ main ]
paths:
- 'helm/higress/README.md'
jobs:
translate-readme:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Compare README.md
id: compare_readme
run: |
cd ./helm/higress
BASE_BRANCH=${GITHUB_BASE_REF:-main}
git fetch origin $BASE_BRANCH
if git diff --quiet origin/$BASE_BRANCH -- README.md; then
echo "README.md has no local changes compared to $BASE_BRANCH. Skipping translation."
echo "skip_translation=true" >> $GITHUB_ENV
else
echo "README.md has local changes compared to $BASE_BRANCH. Proceeding with translation."
echo "skip_translation=false" >> $GITHUB_ENV
echo "--------- diff ---------"
git diff origin/$BASE_BRANCH -- README.md
echo "------------------------"
fi
- name: Translate README.md to Chinese
if: env.skip_translation == 'false'
env:
API_URL: ${{ secrets.HIGRESS_OPENAI_API_URL }}
API_KEY: ${{ secrets.HIGRESS_OPENAI_API_KEY }}
API_MODEL: ${{ secrets.HIGRESS_OPENAI_API_MODEL }}
run: |
cat << 'EOF' > translate_readme.py
import os
import json
import requests
API_URL = os.environ["API_URL"]
API_KEY = os.environ["API_KEY"]
API_MODEL = os.environ["API_MODEL"]
README_PATH = "./helm/higress/README.md"
OUTPUT_PATH = "./helm/higress/README.zh.md"
def stream_translation(api_url, api_key, payload):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
response = requests.post(api_url, headers=headers, json=payload, stream=True)
response.raise_for_status()
with open(OUTPUT_PATH, "w", encoding="utf-8") as out_file:
for line in response.iter_lines(decode_unicode=True):
if line.strip() == "" or not line.startswith("data: "):
continue
data = line[6:]
if data.strip() == "[DONE]":
break
try:
chunk = json.loads(data)
content = chunk["choices"][0]["delta"].get("content", "")
if content:
out_file.write(content)
except Exception as e:
print("Error parsing chunk:", e)
def main():
if not os.path.exists(README_PATH):
print("README.md not found!")
return
with open(README_PATH, "r", encoding="utf-8") as f:
content = f.read()
payload = {
"model": API_MODEL,
"messages": [
{
"role": "system",
"content": "You are a translation assistant that translates English Markdown text to Chinese. Preserve original Markdown formatting and line breaks."
},
{
"role": "user",
"content": content
}
],
"temperature": 0.3,
"stream": True
}
print("Streaming translation started...")
stream_translation(API_URL, API_KEY, payload)
print(f"Translation completed and saved to {OUTPUT_PATH}.")
if __name__ == "__main__":
main()
EOF
python3 translate_readme.py
rm -rf translate_readme.py
- name: Create Pull Request
if: env.skip_translation == 'false'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update helm translated README.zh.md"
branch: update-helm-readme-zh
title: "Update helm translated README.zh.md"
body: |
This PR updates the translated README.zh.md file.
- Automatically generated by GitHub Actions
labels: translation, automated
base: main

View File

@@ -33,6 +33,7 @@ header:
- 'hgctl/cmd/hgctl/config/testdata/config'
- 'hgctl/pkg/manifests'
- 'pkg/ingress/kube/gateway/istio/testdata'
- 'release-notes/**'
comment: on-failure
dependency:

View File

@@ -144,7 +144,7 @@ docker-buildx-push: clean-env docker.higress-buildx
export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.6/envoy-symbol-ARCH.tar.gz
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.7/envoy-symbol-ARCH.tar.gz
build-envoy: prebuild
./tools/hack/build-envoy.sh

View File

@@ -1 +1 @@
v2.1.4
v2.1.5-rc.1

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.4
appVersion: 2.1.5-rc.1
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -15,4 +15,4 @@ dependencies:
repository: "file://../redis"
version: 0.0.1
type: application
version: 2.1.4
version: 2.1.5-rc.1

View File

@@ -113,3 +113,36 @@ kind: VMPodScrape
{{- fail "unexpected gateway.metrics.provider" -}}
{{- end -}}
{{- end -}}
{{- define "pluginServer.name" -}}
{{- .Values.pluginServer.name | default "higress-plugin-server" -}}
{{- end }}
{{- define "pluginServer.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "pluginServer.labels" -}}
helm.sh/chart: {{ include "pluginServer.chart" . }}
{{ include "pluginServer.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "pluginServer.name" . }}
{{- end }}
{{- define "pluginServer.selectorLabels" -}}
{{- if hasKey .Values.pluginServer.labels "app" }}
{{- with .Values.pluginServer.labels.app }}app: {{.|quote}}
{{- end}}
{{- else }}app: {{ include "pluginServer.name" . }}
{{- end }}
{{- if hasKey .Values.pluginServer.labels "higress" }}
{{- with .Values.pluginServer.labels.higress }}
higress: {{.|quote}}
{{- end}}
{{- else }}
higress: {{ include "pluginServer.name" . }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,39 @@
{{- if .Values.global.enablePluginServer }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "pluginServer.name" . }}
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.pluginServer.replicas }}
selector:
matchLabels:
{{- include "pluginServer.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- with .Values.pluginServer.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- include "pluginServer.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.pluginServer.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.pluginServer.hub | default .Values.global.hub }}/{{ .Values.pluginServer.image | default "plugin-server" }}:{{ .Values.pluginServer.tag | default "1.0.0" }}
{{- if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
ports:
- containerPort: 8080
resources:
requests:
cpu: {{ .Values.pluginServer.resources.requests.cpu }}
memory: {{ .Values.pluginServer.resources.requests.memory }}
limits:
cpu: {{ .Values.pluginServer.resources.limits.cpu }}
memory: {{ .Values.pluginServer.resources.limits.memory }}
{{- end }}

View File

@@ -0,0 +1,16 @@
{{- if .Values.global.enablePluginServer }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "pluginServer.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "pluginServer.labels" . | nindent 4 }}
spec:
ports:
- protocol: TCP
port: {{ .Values.pluginServer.service.port }}
targetPort: 8080
selector:
{{- include "pluginServer.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@@ -11,6 +11,7 @@ global:
enableSRDS: true
# -- Whether to enable Redis(redis-stack-server) for Higress, default is false.
enableRedis: false
enablePluginServer: false
onDemandRDS: false
hostRDSMergeSubset: false
onlyPushRouteCluster: true
@@ -767,4 +768,31 @@ redis:
accessModes:
- ReadWriteOnce
# -- Persistent Volume size
size: 1Gi
size: 1Gi
pluginServer:
name: "higress-plugin-server"
# -- Number of Higress Plugin Server pods, 2 recommended for high availability
replicas: 2
image: plugin-server
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: ""
imagePullSecrets: []
labels: {}
# -- Labels to apply to the pod
podLabels: {}
# Plugin-server Service configuration
service:
port: 80 # Container target port (usually fixed)
resources:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 2.1.4
version: 2.1.5-rc.1
- name: higress-console
repository: https://higress.io/helm-charts/
version: 2.1.4
digest: sha256:482d9c5263ed959848601ee249b4852aed842a7805f0b36e456639fd54649c45
generated: "2025-06-10T20:57:14.150704+08:00"
digest: sha256:6dbbfb24eabe0927a167c11896799ea20c7f8590aa2889b853dc9a210d075d3a
generated: "2025-06-18T09:15:09.621898+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.1.4
appVersion: 2.1.5-rc.1
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 2.1.4
version: 2.1.5-rc.1
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 2.1.4
type: application
version: 2.1.4
version: 2.1.5-rc.1

View File

@@ -165,6 +165,7 @@ The command removes all the Kubernetes components associated with the chart and
| global.enableIPv6 | bool | `false` | |
| global.enableIstioAPI | bool | `true` | If true, Higress Controller will monitor istio resources as well |
| global.enableLDSCache | bool | `false` | |
| global.enablePluginServer | bool | `false` | |
| global.enableProxyProtocol | bool | `false` | |
| global.enablePushAllMCPClusters | bool | `true` | |
| global.enableRedis | bool | `false` | Whether to enable Redis(redis-stack-server) for Higress, default is false. |
@@ -273,6 +274,19 @@ The command removes all the Kubernetes components associated with the chart and
| pilot.serviceAnnotations | object | `{}` | |
| pilot.tag | string | `""` | |
| pilot.traceSampling | float | `1` | |
| pluginServer.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | |
| pluginServer.image | string | `"plugin-server"` | |
| pluginServer.imagePullSecrets | list | `[]` | |
| pluginServer.labels | object | `{}` | |
| pluginServer.name | string | `"higress-plugin-server"` | |
| pluginServer.podLabels | object | `{}` | Labels to apply to the pod |
| pluginServer.replicas | int | `2` | Number of Higress Plugin Server pods, 2 recommended for high availability |
| pluginServer.resources.limits.cpu | string | `"500m"` | |
| pluginServer.resources.limits.memory | string | `"256Mi"` | |
| pluginServer.resources.requests.cpu | string | `"200m"` | |
| pluginServer.resources.requests.memory | string | `"128Mi"` | |
| pluginServer.service.port | int | `80` | |
| pluginServer.tag | string | `""` | |
| redis.redis.affinity | object | `{}` | Affinity for Redis |
| redis.redis.image | string | `"redis-stack-server"` | Specify the image |
| redis.redis.name | string | `"redis-stack-server"` | |

View File

@@ -817,19 +817,23 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
destinationRuleWrapper.DestinationRule.TrafficPolicy.Tls != nil {
dr.DestinationRule.TrafficPolicy.Tls = destinationRuleWrapper.DestinationRule.TrafficPolicy.Tls
}
portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0]
portUpdated := false
for _, policy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings {
if policy.Port.Number == portTrafficPolicy.Port.Number {
policy.Tls = portTrafficPolicy.Tls
portUpdated = true
break
// Directly inherit or override the port policy (if it exists)
if len(destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings) > 0 {
portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0]
portUpdated := false
for _, policy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings {
if policy.Port.Number == portTrafficPolicy.Port.Number {
policy.Tls = portTrafficPolicy.Tls
policy.LoadBalancer = portTrafficPolicy.LoadBalancer
portUpdated = true
break
}
}
if portUpdated {
continue
}
dr.DestinationRule.TrafficPolicy.PortLevelSettings = append(dr.DestinationRule.TrafficPolicy.PortLevelSettings, portTrafficPolicy)
}
if portUpdated {
continue
}
dr.DestinationRule.TrafficPolicy.PortLevelSettings = append(dr.DestinationRule.TrafficPolicy.PortLevelSettings, portTrafficPolicy)
}
}
}

View File

@@ -136,7 +136,7 @@ func (s *SSEServer) HandleSSE(cb api.FilterCallbackHandler, stopChan chan struct
}
// Send the initial endpoint event
initialEvent := fmt.Sprintf("event: endpoint\ndata: %s\r\n\r\n", messageEndpoint)
initialEvent := fmt.Sprintf("event: endpoint\ndata: %s\n\n", messageEndpoint)
err = s.redisClient.Publish(channel, initialEvent)
if err != nil {
api.LogErrorf("Failed to send initial event: %v", err)
@@ -210,7 +210,7 @@ func (s *SSEServer) HandleMessage(w http.ResponseWriter, r *http.Request, body j
var status int
// Only send response if there is one (not for notifications)
if response != nil {
if sessionID != ""{
if sessionID != "" {
w.WriteHeader(http.StatusAccepted)
status = http.StatusAccepted
} else {

View File

@@ -7,7 +7,7 @@
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
| `modelKey` | string | 选填 | model | 请求body中model参数的位置 |
| `modelMapping` | map of string | 选填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。<br/>1. 支持前缀匹配。例如用 "gpt-3-*" 匹配所有名称以“gpt-3-”开头的模型;<br/>2. 支持使用 "*" 为键来配置通用兜底映射关系;<br/>3. 如果映射的目标名称为空字符串 "",则表示保留原模型名称。 |
| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 |
| `enableOnPathSuffix` | array of string | 选填 | ["/completions","/embeddings","/images/generations","/audio/speech","/fine_tuning/jobs","/moderations","/image-synthesis","/video-synthesis"] | 只对这些特定路径后缀的请求生效|
## 效果说明

View File

@@ -7,7 +7,7 @@ The `model-mapper` plugin implements the functionality of routing based on the m
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
| `modelKey` | string | Optional | model | The location of the model parameter in the request body. |
| `modelMapping` | map of string | Optional | - | AI model mapping table, used to map the model names in the request to the model names supported by the service provider.<br/>1. Supports prefix matching. For example, use "gpt-3-*" to match all models whose names start with “gpt-3-”;<br/>2. Supports using "*" as the key to configure a generic fallback mapping relationship;<br/>3. If the target name in the mapping is an empty string "", it means to keep the original model name. |
| `enableOnPathSuffix` | array of string | Optional | ["/v1/chat/completions"] | Only applies to requests with these specific path suffixes. |
| `enableOnPathSuffix` | array of string | Optional | ["/completions","/embeddings","/images/generations","/audio/speech","/fine_tuning/jobs","/moderations","/image-synthesis","/video-synthesis"] | Only applies to requests with these specific path suffixes. |
## Runtime Properties

View File

@@ -43,7 +43,8 @@ struct ModelMapperConfigRule {
std::string default_model_mapping_;
std::vector<std::string> enable_on_path_suffix_ = {
"/completions", "/embeddings", "/images/generations",
"/audio/speech", "/fine_tuning/jobs", "/moderations"};
"/audio/speech", "/fine_tuning/jobs", "/moderations",
"/image-synthesis", "/video-synthesis"};
};
// PluginRootContext is the root context for all streams processed by the

View File

@@ -8,7 +8,7 @@
| `modelKey` | string | 选填 | model | 请求body中model参数的位置 |
| `addProviderHeader` | string | 选填 | - | 从model参数中解析出的provider名字放到哪个请求header中 |
| `modelToHeader` | string | 选填 | - | 直接将model参数放到哪个请求header中 |
| `enableOnPathSuffix` | array of string | 选填 | ["/completions","/embeddings","/images/generations","/audio/speech","/fine_tuning/jobs","/moderations"] | 只对这些特定路径后缀的请求生效,可以配置为 "*" 以匹配所有路径 |
| `enableOnPathSuffix` | array of string | 选填 | ["/completions","/embeddings","/images/generations","/audio/speech","/fine_tuning/jobs","/moderations","/image-synthesis","/video-synthesis"] | 只对这些特定路径后缀的请求生效,可以配置为 "*" 以匹配所有路径 |
## 运行属性

View File

@@ -8,7 +8,7 @@ The `model-router` plugin implements routing functionality based on the model pa
| `modelKey` | string | Optional | model | Location of the model parameter in the request body |
| `addProviderHeader` | string | Optional | - | Which request header to add the provider name parsed from the model parameter |
| `modelToHeader` | string | Optional | - | Which request header to directly add the model parameter to |
| `enableOnPathSuffix` | array of string | Optional | ["/v1/chat/completions"] | Only effective for requests with these specific path suffixes, can be configured as "*" to match all paths |
| `enableOnPathSuffix` | array of string | Optional | ["/completions","/embeddings","/images/generations","/audio/speech","/fine_tuning/jobs","/moderations","/image-synthesis","/video-synthesis"] | Only effective for requests with these specific path suffixes, can be configured as "*" to match all paths |
## Runtime Properties

View File

@@ -49,7 +49,8 @@ struct ModelRouterConfigRule {
std::string model_to_header_;
std::vector<std::string> enable_on_path_suffix_ = {
"/completions", "/embeddings", "/images/generations",
"/audio/speech", "/fine_tuning/jobs", "/moderations"};
"/audio/speech", "/fine_tuning/jobs", "/moderations",
"/image-synthesis", "/video-synthesis"};
};
class PluginContext;

View File

@@ -275,6 +275,16 @@ Google Vertex AI 所对应的 type 为 vertex。它特有的配置字段如下
| `vertexGeminiSafetySetting` | map of string | 非必填 | - | Gemini 模型的内容安全过滤设置。 |
| `vertexTokenRefreshAhead` | number | 非必填 | - | Vertex access token刷新提前时间(单位秒) |
#### AWS Bedrock
AWS Bedrock 所对应的 type 为 bedrock。它特有的配置字段如下
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|----------------|--------|------|-----|------------------------------|
| `awsAccessKey` | string | 必填 | - | AWS Access Key用于身份认证 |
| `awsSecretKey` | string | 必填 | - | AWS Secret Access Key用于身份认证 |
| `awsRegion` | string | 必填 | - | AWS 区域例如us-east-1 |
## 用法示例
### 使用 OpenAI 协议代理 Azure OpenAI 服务
@@ -1704,6 +1714,59 @@ provider:
}
```
### 使用 OpenAI 协议代理 AWS Bedrock 服务
**配置信息**
```yaml
provider:
type: bedrock
awsAccessKey: "YOUR_AWS_ACCESS_KEY_ID"
awsSecretKey: "YOUR_AWS_SECRET_ACCESS_KEY"
awsRegion: "YOUR_AWS_REGION"
```
**请求示例**
```json
{
"model": "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0",
"messages": [
{
"role": "user",
"content": "你好,你是谁?"
}
],
"stream": false
}
```
**响应示例**
```json
{
"id": "dc5812e2-6a62-49d6-829e-5c327b15e4e2",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!我是Claude,一个由Anthropic开发的AI助手。很高兴认识你!我的目标是以诚实、有益且有意义的方式与人类交流。我会尽力提供准确和有帮助的信息,同时保持诚实和正直。请问我今天能为你做些什么呢?"
},
"finish_reason": "stop"
}
],
"created": 1749657608,
"model": "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0",
"object": "chat.completion",
"usage": {
"prompt_tokens": 16,
"completion_tokens": 101,
"total_tokens": 117
}
}
```
## 完整配置示例

View File

@@ -220,6 +220,16 @@ For Vertex, the corresponding `type` is `vertex`. Its unique configuration field
| `vertexGeminiSafetySetting` | map of string | Optional | - | Gemini model content safety filtering settings. |
| `vertexTokenRefreshAhead` | number | Optional | - | Vertex access token refresh ahead time in seconds |
#### AWS Bedrock
For AWS Bedrock, the corresponding `type` is `bedrock`. Its unique configuration field is:
| Name | Data Type | Requirement | Default | Description |
|----------------|-----------|-------------|---------|-----------------------------------------------|
| `awsAccessKey` | string | Required | - | AWS Access Key used for authentication |
| `awsSecretKey` | string | Required | - | AWS Secret Access Key used for authentication |
| `awsRegion` | string | Required | - | AWS region, e.g., us-east-1 |
## Usage Examples
### Using OpenAI Protocol Proxy for Azure OpenAI Service
@@ -1481,6 +1491,55 @@ provider:
}
```
### Utilizing OpenAI Protocol Proxy for AWS Bedrock Services
**Configuration Information**
```yaml
provider:
type: bedrock
awsAccessKey: "YOUR_AWS_ACCESS_KEY_ID"
awsSecretKey: "YOUR_AWS_SECRET_ACCESS_KEY"
awsRegion: "YOUR_AWS_REGION"
```
**Request Example**
```json
{
"model": "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0",
"messages": [
{
"role": "user",
"content": "who are you"
}
],
"stream": false
}
```
**Response Example**
```json
{
"id": "d52da49d-daf3-49d9-a105-0b527481fe14",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "I'm Claude, an AI created by Anthropic. I aim to be helpful, honest, and harmless. I won't pretend to be human, and I'll always try to be direct and truthful about what I am and what I can do."
},
"finish_reason": "stop"
}
],
"created": 1749659050,
"model": "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0",
"object": "chat.completion",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 57,
"total_tokens": 67
}
}
```
## Full Configuration Example
### Kubernetes Example

View File

@@ -379,6 +379,33 @@ func getApiName(path string) provider.ApiName {
if strings.HasSuffix(path, "/v1/models") {
return provider.ApiNameModels
}
if strings.HasSuffix(path, "/v1/fine_tuning/jobs") {
return provider.ApiNameFineTuningJobs
}
if util.RegRetrieveFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningRetrieveJob
}
if util.RegRetrieveFineTuningJobEventsPath.MatchString(path) {
return provider.PathOpenAIFineTuningJobEvents
}
if util.RegRetrieveFineTuningJobCheckpointsPath.MatchString(path) {
return provider.PathOpenAIFineTuningJobCheckpoints
}
if util.RegCancelFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningCancelJob
}
if util.RegResumeFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningResumeJob
}
if util.RegPauseFineTuningJobPath.MatchString(path) {
return provider.ApiNameFineTuningPauseJob
}
if util.RegFineTuningCheckpointPermissionPath.MatchString(path) {
return provider.ApiNameFineTuningCheckpointPermissions
}
if util.RegDeleteFineTuningCheckpointPermissionPath.MatchString(path) {
return provider.PathOpenAIFineDeleteTuningCheckpointPermission
}
// cohere style
if strings.HasSuffix(path, "/v1/rerank") {
return provider.ApiNameCohereV1Rerank

View File

@@ -42,8 +42,7 @@ const (
requestIdHeader = "X-Amzn-Requestid"
)
type bedrockProviderInitializer struct {
}
type bedrockProviderInitializer struct{}
func (b *bedrockProviderInitializer) ValidateConfig(config *ProviderConfig) error {
if len(config.awsAccessKey) == 0 || len(config.awsSecretKey) == 0 {
@@ -104,7 +103,7 @@ func (b *bedrockProvider) convertEventFromBedrockToOpenAI(ctx wrapper.HttpContex
chatChoice.Delta = &chatMessage{Content: bedrockEvent.Delta.Text}
}
if bedrockEvent.StopReason != nil {
chatChoice.FinishReason = stopReasonBedrock2OpenAI(*bedrockEvent.StopReason)
chatChoice.FinishReason = util.Ptr(stopReasonBedrock2OpenAI(*bedrockEvent.StopReason))
}
choices = append(choices, chatChoice)
requestId := ctx.GetStringContext(requestIdHeader, "")
@@ -118,7 +117,7 @@ func (b *bedrockProvider) convertEventFromBedrockToOpenAI(ctx wrapper.HttpContex
}
if bedrockEvent.Usage != nil {
openAIFormattedChunk.Choices = choices[:0]
openAIFormattedChunk.Usage = usage{
openAIFormattedChunk.Usage = &usage{
CompletionTokens: bedrockEvent.Usage.OutputTokens,
PromptTokens: bedrockEvent.Usage.InputTokens,
TotalTokens: bedrockEvent.Usage.TotalTokens,
@@ -756,18 +755,19 @@ func (b *bedrockProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, b
Role: bedrockResponse.Output.Message.Role,
Content: outputContent,
},
FinishReason: stopReasonBedrock2OpenAI(bedrockResponse.StopReason),
FinishReason: util.Ptr(stopReasonBedrock2OpenAI(bedrockResponse.StopReason)),
},
}
requestId := ctx.GetStringContext(requestIdHeader, "")
modelId, _ := url.QueryUnescape(ctx.GetStringContext(ctxKeyFinalRequestModel, ""))
return &chatCompletionResponse{
Id: requestId,
Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Model: modelId,
SystemFingerprint: "",
Object: objectChatCompletion,
Choices: choices,
Usage: usage{
Usage: &usage{
PromptTokens: bedrockResponse.Usage.InputTokens,
CompletionTokens: bedrockResponse.Usage.OutputTokens,
TotalTokens: bedrockResponse.Usage.TotalTokens,
@@ -908,6 +908,7 @@ func (b *bedrockProvider) setAuthHeaders(body []byte, headers http.Header) {
}
func (b *bedrockProvider) generateSignature(path, amzDate, dateStamp string, body []byte) string {
path = encodeSigV4Path(path)
hashedPayload := sha256Hex(body)
endpoint := fmt.Sprintf(bedrockDefaultDomain, b.config.awsRegion)
@@ -925,6 +926,17 @@ func (b *bedrockProvider) generateSignature(path, amzDate, dateStamp string, bod
return signature
}
func encodeSigV4Path(path string) string {
segments := strings.Split(path, "/")
for i, seg := range segments {
if seg == "" {
continue
}
segments[i] = url.PathEscape(seg)
}
return strings.Join(segments, "/")
}
func getSignatureKey(key, dateStamp, region, service string) []byte {
kDate := hmacSha256([]byte("AWS4"+key), dateStamp)
kRegion := hmacSha256(kDate, region)

View File

@@ -341,7 +341,7 @@ func (c *claudeProvider) responseClaude2OpenAI(ctx wrapper.HttpContext, origResp
choice := chatCompletionChoice{
Index: 0,
Message: &chatMessage{Role: roleAssistant, Content: origResponse.Content[0].Text},
FinishReason: stopReasonClaude2OpenAI(origResponse.StopReason),
FinishReason: util.Ptr(stopReasonClaude2OpenAI(origResponse.StopReason)),
}
return &chatCompletionResponse{
@@ -351,7 +351,7 @@ func (c *claudeProvider) responseClaude2OpenAI(ctx wrapper.HttpContext, origResp
SystemFingerprint: "",
Object: objectChatCompletion,
Choices: []chatCompletionChoice{choice},
Usage: usage{
Usage: &usage{
PromptTokens: origResponse.Usage.InputTokens,
CompletionTokens: origResponse.Usage.OutputTokens,
TotalTokens: origResponse.Usage.InputTokens + origResponse.Usage.OutputTokens,
@@ -404,7 +404,7 @@ func (c *claudeProvider) streamResponseClaude2OpenAI(ctx wrapper.HttpContext, or
choice := chatCompletionChoice{
Index: origResponse.Index,
Delta: &chatMessage{},
FinishReason: stopReasonClaude2OpenAI(origResponse.Delta.StopReason),
FinishReason: util.Ptr(stopReasonClaude2OpenAI(origResponse.Delta.StopReason)),
}
return c.createChatCompletionResponse(ctx, origResponse, choice)
case "message_stop":
@@ -415,7 +415,7 @@ func (c *claudeProvider) streamResponseClaude2OpenAI(ctx wrapper.HttpContext, or
Object: objectChatCompletionChunk,
Choices: []chatCompletionChoice{},
ServiceTier: c.serviceTier,
Usage: usage{
Usage: &usage{
PromptTokens: c.usage.PromptTokens,
CompletionTokens: c.usage.CompletionTokens,
TotalTokens: c.usage.TotalTokens,

View File

@@ -116,23 +116,23 @@ func (d *difyProvider) responseDify2OpenAI(ctx wrapper.HttpContext, response *Di
choice = chatCompletionChoice{
Index: 0,
Message: &chatMessage{Role: roleAssistant, Content: response.Answer},
FinishReason: finishReasonStop,
FinishReason: util.Ptr(finishReasonStop),
}
//response header中增加conversationId字段
// response header中增加conversationId字段
_ = proxywasm.ReplaceHttpResponseHeader("ConversationId", response.ConversationId)
id = response.ConversationId
case BotTypeCompletion:
choice = chatCompletionChoice{
Index: 0,
Message: &chatMessage{Role: roleAssistant, Content: response.Answer},
FinishReason: finishReasonStop,
FinishReason: util.Ptr(finishReasonStop),
}
id = response.MessageId
case BotTypeWorkflow:
choice = chatCompletionChoice{
Index: 0,
Message: &chatMessage{Role: roleAssistant, Content: response.Data.Outputs[d.config.outputVariable]},
FinishReason: finishReasonStop,
FinishReason: util.Ptr(finishReasonStop),
}
id = response.Data.WorkflowId
}
@@ -143,7 +143,7 @@ func (d *difyProvider) responseDify2OpenAI(ctx wrapper.HttpContext, response *Di
SystemFingerprint: "",
Object: objectChatCompletion,
Choices: []chatCompletionChoice{choice},
Usage: response.MetaData.Usage,
Usage: &response.MetaData.Usage,
}
}
@@ -188,7 +188,7 @@ func (d *difyProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name Api
func (d *difyProvider) streamResponseDify2OpenAI(ctx wrapper.HttpContext, response *DifyChunkChatResponse) *chatCompletionResponse {
var choice chatCompletionChoice
var id string
var responseUsage usage
var responseUsage *usage
switch d.config.botType {
case BotTypeChat, BotTypeAgent:
choice = chatCompletionChoice{
@@ -211,9 +211,9 @@ func (d *difyProvider) streamResponseDify2OpenAI(ctx wrapper.HttpContext, respon
id = response.Data.WorkflowId
}
if response.Event == "message_end" || response.Event == "workflow_finished" {
choice.FinishReason = finishReasonStop
choice.FinishReason = util.Ptr(finishReasonStop)
if response.Event == "message_end" {
responseUsage = usage{
responseUsage = &usage{
PromptTokens: response.MetaData.Usage.PromptTokens,
CompletionTokens: response.MetaData.Usage.CompletionTokens,
TotalTokens: response.MetaData.Usage.TotalTokens,

View File

@@ -500,7 +500,7 @@ func (g *geminiProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, re
Object: objectChatCompletion,
Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Usage: usage{
Usage: &usage{
PromptTokens: response.UsageMetadata.PromptTokenCount,
CompletionTokens: response.UsageMetadata.CandidatesTokenCount,
TotalTokens: response.UsageMetadata.TotalTokenCount,
@@ -514,7 +514,7 @@ func (g *geminiProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, re
Message: &chatMessage{
Role: roleAssistant,
},
FinishReason: finishReasonStop,
FinishReason: util.Ptr(finishReasonStop),
}
if part.FunctionCall != nil {
choice.Message.ToolCalls = g.buildToolCalls(&candidate)
@@ -524,7 +524,7 @@ func (g *geminiProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, re
choice.Message.Content = part.Text
}
choice.FinishReason = candidate.FinishReason
choice.FinishReason = util.Ptr(strings.ToLower(candidate.FinishReason))
fullTextResponse.Choices = append(fullTextResponse.Choices, choice)
choiceIndex += 1
}
@@ -560,6 +560,9 @@ func (g *geminiProvider) buildChatCompletionStreamResponse(ctx wrapper.HttpConte
var choice chatCompletionChoice
if len(geminiResp.Candidates) > 0 && len(geminiResp.Candidates[0].Content.Parts) > 0 {
choice.Delta = &chatMessage{Content: geminiResp.Candidates[0].Content.Parts[0].Text}
if geminiResp.Candidates[0].FinishReason != "" {
choice.FinishReason = util.Ptr(strings.ToLower(geminiResp.Candidates[0].FinishReason))
}
}
streamResponse := chatCompletionResponse{
Id: fmt.Sprintf("chatcmpl-%s", uuid.New().String()),
@@ -567,7 +570,7 @@ func (g *geminiProvider) buildChatCompletionStreamResponse(ctx wrapper.HttpConte
Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Choices: []chatCompletionChoice{choice},
Usage: usage{
Usage: &usage{
PromptTokens: geminiResp.UsageMetadata.PromptTokenCount,
CompletionTokens: geminiResp.UsageMetadata.CandidatesTokenCount,
TotalTokens: geminiResp.UsageMetadata.TotalTokenCount,

View File

@@ -387,7 +387,7 @@ func (m *hunyuanProvider) convertChunkFromHunyuanToOpenAI(ctx wrapper.HttpContex
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "",
Object: objectChatCompletionChunk,
Usage: usage{
Usage: &usage{
PromptTokens: hunyuanFormattedChunk.Usage.PromptTokens,
CompletionTokens: hunyuanFormattedChunk.Usage.CompletionTokens,
TotalTokens: hunyuanFormattedChunk.Usage.TotalTokens,
@@ -400,7 +400,7 @@ func (m *hunyuanProvider) convertChunkFromHunyuanToOpenAI(ctx wrapper.HttpContex
if hunyuanFormattedChunk.Choices[0].FinishReason == hunyuanStreamEndMark {
// log.Debugf("@@@ --- 最后chunk: ")
openAIFormattedChunk.Choices = append(openAIFormattedChunk.Choices, chatCompletionChoice{
FinishReason: hunyuanFormattedChunk.Choices[0].FinishReason,
FinishReason: util.Ptr(hunyuanFormattedChunk.Choices[0].FinishReason),
})
} else {
deltaMsg := chatMessage{
@@ -495,7 +495,7 @@ func (m *hunyuanProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, h
Content: choice.Message.Content,
ToolCalls: nil,
},
FinishReason: choice.FinishReason,
FinishReason: util.Ptr(choice.FinishReason),
})
}
return &chatCompletionResponse{
@@ -505,7 +505,7 @@ func (m *hunyuanProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, h
SystemFingerprint: "",
Object: objectChatCompletion,
Choices: choices,
Usage: usage{
Usage: &usage{
PromptTokens: hunyuanResponse.Response.Usage.PromptTokens,
CompletionTokens: hunyuanResponse.Response.Usage.CompletionTokens,
TotalTokens: hunyuanResponse.Response.Usage.TotalTokens,

View File

@@ -36,8 +36,7 @@ const (
defaultSenderName string = "小明"
)
type minimaxProviderInitializer struct {
}
type minimaxProviderInitializer struct{}
func (m *minimaxProviderInitializer) ValidateConfig(config *ProviderConfig) error {
// If using the chat completion Pro API, a group ID must be set.
@@ -368,7 +367,7 @@ func (m *minimaxProvider) responseProToOpenAI(response *minimaxChatCompletionPro
Content: message.Text,
}
choices = append(choices, chatCompletionChoice{
FinishReason: choice.FinishReason,
FinishReason: util.Ptr(choice.FinishReason),
Index: messageIndex,
Message: message,
})
@@ -381,7 +380,7 @@ func (m *minimaxProvider) responseProToOpenAI(response *minimaxChatCompletionPro
Created: response.Created,
Model: response.Model,
Choices: choices,
Usage: usage{
Usage: &usage{
TotalTokens: int(response.Usage.TotalTokens),
PromptTokens: int(response.Usage.PromptTokens),
CompletionTokens: int(response.Usage.CompletionTokens),

View File

@@ -138,15 +138,15 @@ type chatCompletionResponse struct {
ServiceTier string `json:"service_tier,omitempty"`
SystemFingerprint string `json:"system_fingerprint,omitempty"`
Object string `json:"object,omitempty"`
Usage usage `json:"usage,omitempty"`
Usage *usage `json:"usage"`
}
type chatCompletionChoice struct {
Index int `json:"index"`
Message *chatMessage `json:"message,omitempty"`
Delta *chatMessage `json:"delta,omitempty"`
FinishReason string `json:"finish_reason,omitempty"`
Logprobs map[string]interface{} `json:"logprobs,omitempty"`
FinishReason *string `json:"finish_reason"`
Logprobs map[string]interface{} `json:"logprobs"`
}
type usage struct {

View File

@@ -26,24 +26,34 @@ func (m *openaiProviderInitializer) ValidateConfig(config *ProviderConfig) error
func (m *openaiProviderInitializer) DefaultCapabilities() map[string]string {
return map[string]string{
string(ApiNameCompletion): PathOpenAICompletions,
string(ApiNameChatCompletion): PathOpenAIChatCompletions,
string(ApiNameEmbeddings): PathOpenAIEmbeddings,
string(ApiNameImageGeneration): PathOpenAIImageGeneration,
string(ApiNameImageEdit): PathOpenAIImageEdit,
string(ApiNameImageVariation): PathOpenAIImageVariation,
string(ApiNameAudioSpeech): PathOpenAIAudioSpeech,
string(ApiNameModels): PathOpenAIModels,
string(ApiNameFiles): PathOpenAIFiles,
string(ApiNameRetrieveFile): PathOpenAIRetrieveFile,
string(ApiNameRetrieveFileContent): PathOpenAIRetrieveFileContent,
string(ApiNameBatches): PathOpenAIBatches,
string(ApiNameRetrieveBatch): PathOpenAIRetrieveBatch,
string(ApiNameCancelBatch): PathOpenAICancelBatch,
string(ApiNameResponses): PathOpenAIResponses,
string(ApiNameCompletion): PathOpenAICompletions,
string(ApiNameChatCompletion): PathOpenAIChatCompletions,
string(ApiNameEmbeddings): PathOpenAIEmbeddings,
string(ApiNameImageGeneration): PathOpenAIImageGeneration,
string(ApiNameImageEdit): PathOpenAIImageEdit,
string(ApiNameImageVariation): PathOpenAIImageVariation,
string(ApiNameAudioSpeech): PathOpenAIAudioSpeech,
string(ApiNameModels): PathOpenAIModels,
string(ApiNameFiles): PathOpenAIFiles,
string(ApiNameRetrieveFile): PathOpenAIRetrieveFile,
string(ApiNameRetrieveFileContent): PathOpenAIRetrieveFileContent,
string(ApiNameBatches): PathOpenAIBatches,
string(ApiNameRetrieveBatch): PathOpenAIRetrieveBatch,
string(ApiNameCancelBatch): PathOpenAICancelBatch,
string(ApiNameResponses): PathOpenAIResponses,
string(ApiNameFineTuningJobs): PathOpenAIFineTuningJobs,
string(ApiNameFineTuningRetrieveJob): PathOpenAIFineTuningRetrieveJob,
string(ApiNameFineTuningJobEvents): PathOpenAIFineTuningJobEvents,
string(ApiNameFineTuningJobCheckpoints): PathOpenAIFineTuningJobCheckpoints,
string(ApiNameFineTuningCancelJob): PathOpenAIFineTuningCancelJob,
string(ApiNameFineTuningResumeJob): PathOpenAIFineTuningResumeJob,
string(ApiNameFineTuningPauseJob): PathOpenAIFineTuningPauseJob,
string(ApiNameFineTuningCheckpointPermissions): PathOpenAIFineTuningCheckpointPermissions,
string(ApiNameDeleteFineTuningCheckpointPermission): PathOpenAIFineDeleteTuningCheckpointPermission,
}
}
// isDirectPath checks if the path is a known standard OpenAI interface path.
func isDirectPath(path string) bool {
return strings.HasSuffix(path, "/completions") ||
strings.HasSuffix(path, "/embeddings") ||
@@ -52,7 +62,9 @@ func isDirectPath(path string) bool {
strings.HasSuffix(path, "/images/variations") ||
strings.HasSuffix(path, "/images/edits") ||
strings.HasSuffix(path, "/models") ||
strings.HasSuffix(path, "/responses")
strings.HasSuffix(path, "/responses") ||
strings.HasSuffix(path, "/fine_tuning/jobs") ||
strings.HasSuffix(path, "/fine_tuning/checkpoints")
}
func (m *openaiProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {

View File

@@ -27,40 +27,60 @@ const (
// ApiName 格式 {vendor}/{version}/{apitype}
// 表示遵循 厂商/版本/接口类型 的格式
// 目前openai是事实意义上的标准但是也有其他厂商存在其他任务的一些可能的标准比如cohere的rerank
ApiNameCompletion ApiName = "openai/v1/completions"
ApiNameChatCompletion ApiName = "openai/v1/chatcompletions"
ApiNameEmbeddings ApiName = "openai/v1/embeddings"
ApiNameImageGeneration ApiName = "openai/v1/imagegeneration"
ApiNameImageEdit ApiName = "openai/v1/imageedit"
ApiNameImageVariation ApiName = "openai/v1/imagevariation"
ApiNameAudioSpeech ApiName = "openai/v1/audiospeech"
ApiNameFiles ApiName = "openai/v1/files"
ApiNameRetrieveFile ApiName = "openai/v1/retrievefile"
ApiNameRetrieveFileContent ApiName = "openai/v1/retrievefilecontent"
ApiNameBatches ApiName = "openai/v1/batches"
ApiNameRetrieveBatch ApiName = "openai/v1/retrievebatch"
ApiNameCancelBatch ApiName = "openai/v1/cancelbatch"
ApiNameModels ApiName = "openai/v1/models"
ApiNameResponses ApiName = "openai/v1/responses"
ApiNameCompletion ApiName = "openai/v1/completions"
ApiNameChatCompletion ApiName = "openai/v1/chatcompletions"
ApiNameEmbeddings ApiName = "openai/v1/embeddings"
ApiNameImageGeneration ApiName = "openai/v1/imagegeneration"
ApiNameImageEdit ApiName = "openai/v1/imageedit"
ApiNameImageVariation ApiName = "openai/v1/imagevariation"
ApiNameAudioSpeech ApiName = "openai/v1/audiospeech"
ApiNameFiles ApiName = "openai/v1/files"
ApiNameRetrieveFile ApiName = "openai/v1/retrievefile"
ApiNameRetrieveFileContent ApiName = "openai/v1/retrievefilecontent"
ApiNameBatches ApiName = "openai/v1/batches"
ApiNameRetrieveBatch ApiName = "openai/v1/retrievebatch"
ApiNameCancelBatch ApiName = "openai/v1/cancelbatch"
ApiNameModels ApiName = "openai/v1/models"
ApiNameResponses ApiName = "openai/v1/responses"
ApiNameFineTuningJobs ApiName = "openai/v1/fine-tuningjobs"
ApiNameFineTuningRetrieveJob ApiName = "openai/v1/retrievefine-tuningjob"
ApiNameFineTuningJobEvents ApiName = "openai/v1/fine-tuningjobsevents"
ApiNameFineTuningJobCheckpoints ApiName = "openai/v1/fine-tuningjobcheckpoints"
ApiNameFineTuningCancelJob ApiName = "openai/v1/cancelfine-tuningjob"
ApiNameFineTuningResumeJob ApiName = "openai/v1/resumefine-tuningjob"
ApiNameFineTuningPauseJob ApiName = "openai/v1/pausefine-tuningjob"
ApiNameFineTuningCheckpointPermissions ApiName = "openai/v1/fine-tuningjobcheckpointpermissions"
ApiNameDeleteFineTuningCheckpointPermission ApiName = "openai/v1/deletefine-tuningjobcheckpointpermission"
PathOpenAICompletions = "/v1/completions"
PathOpenAIChatCompletions = "/v1/chat/completions"
PathOpenAIEmbeddings = "/v1/embeddings"
PathOpenAIFiles = "/v1/files"
PathOpenAIRetrieveFile = "/v1/files/{file_id}"
PathOpenAIRetrieveFileContent = "/v1/files/{file_id}/content"
PathOpenAIBatches = "/v1/batches"
PathOpenAIRetrieveBatch = "/v1/batches/{batch_id}"
PathOpenAICancelBatch = "/v1/batches/{batch_id}/cancel"
PathOpenAIModels = "/v1/models"
PathOpenAIImageGeneration = "/v1/images/generations"
PathOpenAIImageEdit = "/v1/images/edits"
PathOpenAIImageVariation = "/v1/images/variations"
PathOpenAIAudioSpeech = "/v1/audio/speech"
PathOpenAIResponses = "/v1/responses"
PathOpenAICompletions = "/v1/completions"
PathOpenAIChatCompletions = "/v1/chat/completions"
PathOpenAIEmbeddings = "/v1/embeddings"
PathOpenAIFiles = "/v1/files"
PathOpenAIRetrieveFile = "/v1/files/{file_id}"
PathOpenAIRetrieveFileContent = "/v1/files/{file_id}/content"
PathOpenAIBatches = "/v1/batches"
PathOpenAIRetrieveBatch = "/v1/batches/{batch_id}"
PathOpenAICancelBatch = "/v1/batches/{batch_id}/cancel"
PathOpenAIModels = "/v1/models"
PathOpenAIImageGeneration = "/v1/images/generations"
PathOpenAIImageEdit = "/v1/images/edits"
PathOpenAIImageVariation = "/v1/images/variations"
PathOpenAIAudioSpeech = "/v1/audio/speech"
PathOpenAIResponses = "/v1/responses"
PathOpenAIFineTuningJobs = "/v1/fine_tuning/jobs"
PathOpenAIFineTuningRetrieveJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}"
PathOpenAIFineTuningJobEvents = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/events"
PathOpenAIFineTuningJobCheckpoints = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/checkpoints"
PathOpenAIFineTuningCancelJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/cancel"
PathOpenAIFineTuningResumeJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/resume"
PathOpenAIFineTuningPauseJob = "/v1/fine_tuning/jobs/{fine_tuning_job_id}/pause"
PathOpenAIFineTuningCheckpointPermissions = "/v1/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions"
PathOpenAIFineDeleteTuningCheckpointPermission = "/v1/fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}"
// TODO: 以下是一些非标准的API名称需要进一步确认是否支持
ApiNameCohereV1Rerank ApiName = "cohere/v1/rerank"
ApiNameQwenAsyncAIGC ApiName = "api/v1/services/aigc"
ApiNameQwenAsyncTask ApiName = "api/v1/tasks/"
providerTypeMoonshot = "moonshot"
providerTypeAzure = "azure"
@@ -840,11 +860,14 @@ func (c *ProviderConfig) DefaultTransformResponseHeaders(ctx wrapper.HttpContext
func (c *ProviderConfig) needToProcessRequestBody(apiName ApiName) bool {
switch apiName {
case ApiNameChatCompletion,
ApiNameCompletion,
ApiNameEmbeddings,
ApiNameImageGeneration,
ApiNameImageEdit,
ApiNameImageVariation,
ApiNameAudioSpeech:
ApiNameAudioSpeech,
ApiNameFineTuningJobs,
ApiNameResponses:
return true
}
return false

View File

@@ -37,6 +37,9 @@ const (
qwenBailianPath = "/api/v1/apps"
qwenMultimodalGenerationPath = "/api/v1/services/aigc/multimodal-generation/generation"
qwenAsyncAIGCPath = "/api/v1/services/aigc/"
qwenAsyncTaskPath = "/api/v1/tasks/"
qwenTopPMin = 0.000001
qwenTopPMax = 0.999999
@@ -74,6 +77,8 @@ func (m *qwenProviderInitializer) DefaultCapabilities(qwenEnableCompatible bool)
return map[string]string{
string(ApiNameChatCompletion): qwenChatCompletionPath,
string(ApiNameEmbeddings): qwenTextEmbeddingPath,
string(ApiNameQwenAsyncAIGC): qwenAsyncAIGCPath,
string(ApiNameQwenAsyncTask): qwenAsyncTaskPath,
}
}
}
@@ -302,7 +307,7 @@ func (m *qwenProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, qwen
message := qwenMessageToChatMessage(qwenChoice.Message, m.config.reasoningContentMode)
choices = append(choices, chatCompletionChoice{
Message: &message,
FinishReason: qwenChoice.FinishReason,
FinishReason: util.Ptr(qwenChoice.FinishReason),
})
}
return &chatCompletionResponse{
@@ -312,7 +317,7 @@ func (m *qwenProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, qwen
SystemFingerprint: "",
Object: objectChatCompletion,
Choices: choices,
Usage: usage{
Usage: &usage{
PromptTokens: qwenResponse.Usage.InputTokens,
CompletionTokens: qwenResponse.Usage.OutputTokens,
TotalTokens: qwenResponse.Usage.TotalTokens,
@@ -413,11 +418,11 @@ func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpCont
if finished {
finishResponse := *&baseMessage
finishResponse.Choices = append(finishResponse.Choices, chatCompletionChoice{Delta: &chatMessage{}, FinishReason: qwenChoice.FinishReason})
finishResponse.Choices = append(finishResponse.Choices, chatCompletionChoice{Delta: &chatMessage{}, FinishReason: util.Ptr(qwenChoice.FinishReason)})
usageResponse := *&baseMessage
usageResponse.Choices = []chatCompletionChoice{{Delta: &chatMessage{}}}
usageResponse.Usage = usage{
usageResponse.Usage = &usage{
PromptTokens: qwenResponse.Usage.InputTokens,
CompletionTokens: qwenResponse.Usage.OutputTokens,
TotalTokens: qwenResponse.Usage.TotalTokens,
@@ -689,6 +694,10 @@ func (m *qwenProvider) GetApiName(path string) ApiName {
case strings.Contains(path, qwenTextEmbeddingPath),
strings.Contains(path, qwenCompatibleTextEmbeddingPath):
return ApiNameEmbeddings
case strings.Contains(path, qwenAsyncAIGCPath):
return ApiNameQwenAsyncAIGC
case strings.Contains(path, qwenAsyncTaskPath):
return ApiNameQwenAsyncTask
default:
return ""
}

View File

@@ -150,7 +150,7 @@ func (p *sparkProvider) responseSpark2OpenAI(ctx wrapper.HttpContext, response *
Object: objectChatCompletion,
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Choices: choices,
Usage: response.Usage,
Usage: &response.Usage,
}
}
@@ -168,7 +168,7 @@ func (p *sparkProvider) streamResponseSpark2OpenAI(ctx wrapper.HttpContext, resp
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Object: objectChatCompletion,
Choices: choices,
Usage: response.Usage,
Usage: &response.Usage,
}
}

View File

@@ -32,8 +32,7 @@ const (
vertexEmbeddingAction = "predict"
)
type vertexProviderInitializer struct {
}
type vertexProviderInitializer struct{}
func (v *vertexProviderInitializer) ValidateConfig(config *ProviderConfig) error {
if config.vertexAuthKey == "" {
@@ -245,7 +244,7 @@ func (v *vertexProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, re
Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Choices: make([]chatCompletionChoice, 0, len(response.Candidates)),
Usage: usage{
Usage: &usage{
PromptTokens: response.UsageMetadata.PromptTokenCount,
CompletionTokens: response.UsageMetadata.CandidatesTokenCount,
TotalTokens: response.UsageMetadata.TotalTokenCount,
@@ -257,7 +256,7 @@ func (v *vertexProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, re
Message: &chatMessage{
Role: roleAssistant,
},
FinishReason: candidate.FinishReason,
FinishReason: util.Ptr(candidate.FinishReason),
}
if len(candidate.Content.Parts) > 0 {
choice.Message.Content = candidate.Content.Parts[0].Text
@@ -310,7 +309,7 @@ func (v *vertexProvider) buildChatCompletionStreamResponse(ctx wrapper.HttpConte
Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Choices: []chatCompletionChoice{choice},
Usage: usage{
Usage: &usage{
PromptTokens: vertexResp.UsageMetadata.PromptTokenCount,
CompletionTokens: vertexResp.UsageMetadata.CandidatesTokenCount,
TotalTokens: vertexResp.UsageMetadata.TotalTokenCount,

View File

@@ -17,10 +17,18 @@ const (
)
var (
RegRetrieveBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)$`)
RegCancelBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)/cancel$`)
RegRetrieveFilePath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)$`)
RegRetrieveFileContentPath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)/content$`)
RegRetrieveBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)$`)
RegCancelBatchPath = regexp.MustCompile(`^.*/v1/batches/(?P<batch_id>[^/]+)/cancel$`)
RegRetrieveFilePath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)$`)
RegRetrieveFileContentPath = regexp.MustCompile(`^.*/v1/files/(?P<file_id>[^/]+)/content$`)
RegRetrieveFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)$`)
RegRetrieveFineTuningJobEventsPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/events$`)
RegRetrieveFineTuningJobCheckpointsPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/checkpoints$`)
RegCancelFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/cancel$`)
RegResumeFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/resume$`)
RegPauseFineTuningJobPath = regexp.MustCompile(`^.*/v1/fine_tuning/jobs/(?P<fine_tuning_job_id>[^/]+)/pause$`)
RegFineTuningCheckpointPermissionPath = regexp.MustCompile(`^.*/v1/fine_tuning/checkpoints/(?P<fine_tuned_model_checkpoint>[^/]+)/permissions$`)
RegDeleteFineTuningCheckpointPermissionPath = regexp.MustCompile(`^.*/v1/fine_tuning/checkpoints/(?P<fine_tuned_model_checkpoint>[^/]+)/permissions/(?P<permission_id>[^/]+)$`)
)
type ErrorHandlerFunc func(statusCodeDetails string, err error) error

View File

@@ -0,0 +1,5 @@
package util
func Ptr[T any](v T) *T {
return &v
}

View File

@@ -23,6 +23,7 @@ description: AI可观测配置参考
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|----------------|-------|------|-----|------------------------|
| `attributes` | []Attribute | 非必填 | - | 用户希望记录在log/span中的信息 |
| `disable_openai_usage` | bool | 非必填 | false | 非openai兼容协议时model、token的支持非标配置为true时可以避免报错 |
Attribute 配置说明:

View File

@@ -22,7 +22,8 @@ Users can also expand observable values through configuration:
| Name | Type | Required | Default | Description |
|----------------|-------|------|-----|------------------------|
| `attributes` | []Attribute | required | - | Information that the user wants to record in log/span |
| `attributes` | []Attribute | optional | - | Information that the user wants to record in log/span |
| `disable_openai_usage` | bool | optional | false | When using a non-OpenAI-compatible protocol, the support for model and token is non-standard. Setting the configuration to true can prevent errors. |
Attribute Configuration instructions:

View File

@@ -92,6 +92,8 @@ type AIStatisticsConfig struct {
attributes []Attribute
// If there exist attributes extracted from streaming body, chunks should be buffered
shouldBufferStreamingBody bool
// If disableOpenaiUsage is true, model/input_token/output_token logs will be skipped
disableOpenaiUsage bool
}
func generateMetricName(route, cluster, model, consumer, metricName string) string {
@@ -160,6 +162,10 @@ func parseConfig(configJson gjson.Result, config *AIStatisticsConfig, log wrappe
}
// Metric settings
config.counterMetrics = make(map[string]proxywasm.MetricCounter)
// Parse openai usage config setting.
config.disableOpenaiUsage = configJson.Get("disable_openai_usage").Bool()
return nil
}
@@ -264,15 +270,17 @@ func onHttpStreamingBody(ctx wrapper.HttpContext, config AIStatisticsConfig, dat
}
// Set information about this request
if model, inputToken, outputToken, ok := getUsage(data); ok {
ctx.SetUserAttribute(Model, model)
ctx.SetUserAttribute(InputToken, inputToken)
ctx.SetUserAttribute(OutputToken, outputToken)
// Set span attributes for ARMS.
setSpanAttribute(ArmsModelName, model, log)
setSpanAttribute(ArmsInputToken, inputToken, log)
setSpanAttribute(ArmsOutputToken, outputToken, log)
setSpanAttribute(ArmsTotalToken, inputToken+outputToken, log)
if !config.disableOpenaiUsage {
if model, inputToken, outputToken, ok := getUsage(data); ok {
ctx.SetUserAttribute(Model, model)
ctx.SetUserAttribute(InputToken, inputToken)
ctx.SetUserAttribute(OutputToken, outputToken)
// Set span attributes for ARMS.
setSpanAttribute(ArmsModelName, model, log)
setSpanAttribute(ArmsInputToken, inputToken, log)
setSpanAttribute(ArmsOutputToken, outputToken, log)
setSpanAttribute(ArmsTotalToken, inputToken+outputToken, log)
}
}
// If the end of the stream is reached, record metrics/logs/spans.
if endOfStream {
@@ -311,15 +319,17 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AIStatisticsConfig, body
}
// Set information about this request
if model, inputToken, outputToken, ok := getUsage(body); ok {
ctx.SetUserAttribute(Model, model)
ctx.SetUserAttribute(InputToken, inputToken)
ctx.SetUserAttribute(OutputToken, outputToken)
// Set span attributes for ARMS.
setSpanAttribute(ArmsModelName, model, log)
setSpanAttribute(ArmsInputToken, inputToken, log)
setSpanAttribute(ArmsOutputToken, outputToken, log)
setSpanAttribute(ArmsTotalToken, inputToken+outputToken, log)
if !config.disableOpenaiUsage {
if model, inputToken, outputToken, ok := getUsage(body); ok {
ctx.SetUserAttribute(Model, model)
ctx.SetUserAttribute(InputToken, inputToken)
ctx.SetUserAttribute(OutputToken, outputToken)
// Set span attributes for ARMS.
setSpanAttribute(ArmsModelName, model, log)
setSpanAttribute(ArmsInputToken, inputToken, log)
setSpanAttribute(ArmsOutputToken, outputToken, log)
setSpanAttribute(ArmsTotalToken, inputToken+outputToken, log)
}
}
// Set user defined log & span attributes.
@@ -471,6 +481,11 @@ func writeMetric(ctx wrapper.HttpContext, config AIStatisticsConfig, log wrapper
log.Warnf("ClusterName typd assert failed, skip metric record")
return
}
if config.disableOpenaiUsage {
return
}
if ctx.GetUserAttribute(Model) == nil || ctx.GetUserAttribute(InputToken) == nil || ctx.GetUserAttribute(OutputToken) == nil {
log.Warnf("get usage information failed, skip metric record")
return

View File

@@ -0,0 +1,14 @@
# Use a minimal base image as we only need to store the wasm file.
FROM scratch
# Add build argument for the filter name. This will be passed by the Makefile.
ARG FILTER_NAME
# Copy the compiled WASM binary into the image's root directory.
# The wasm file will be named after the filter.
COPY ${FILTER_NAME}/main.wasm /plugin.wasm
# Metadata
LABEL org.opencontainers.image.title="${FILTER_NAME}"
LABEL org.opencontainers.image.description="Higress MCP filter - ${FILTER_NAME}"
LABEL org.opencontainers.image.source="https://github.com/alibaba/higress"

View File

@@ -0,0 +1,54 @@
# MCP Filter Makefile
# Variables
FILTER_NAME ?= mcp-router
REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
BUILD_TIME := $(shell date "+%Y%m%d-%H%M%S")
COMMIT_ID := $(shell git rev-parse --short HEAD 2>/dev/null)
IMAGE_TAG = $(if $(strip $(FILTER_VERSION)),${FILTER_VERSION},${BUILD_TIME}-${COMMIT_ID})
IMG ?= ${REGISTRY}${FILTER_NAME}:${IMAGE_TAG}
# Default target
.DEFAULT: build
build:
@echo "Building WASM binary for filter: ${FILTER_NAME}..."
@if [ ! -d "${FILTER_NAME}" ]; then \
echo "Error: Filter directory '${FILTER_NAME}' not found."; \
exit 1; \
fi
cd ${FILTER_NAME} && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm main.go
@echo ""
@echo "Output WASM file: ${FILTER_NAME}/main.wasm"
# Build Docker image (depends on build target to ensure WASM binary exists)
build-image: build
@echo "Building Docker image for ${FILTER_NAME}..."
docker build -t ${IMG} \
--build-arg FILTER_NAME=${FILTER_NAME} \
-f Dockerfile .
@echo ""
@echo "Image: ${IMG}"
# Build and push Docker image
build-push: build-image
docker push ${IMG}
# Clean build artifacts
clean:
@echo "Cleaning build artifacts for filter: ${FILTER_NAME}..."
rm -f ${FILTER_NAME}/main.wasm
# Help
help:
@echo "Available targets:"
@echo " build - Build WASM binary for a specific filter"
@echo " build-image - Build Docker image"
@echo " build-push - Build and push Docker image"
@echo " clean - Remove build artifacts for a specific filter"
@echo ""
@echo "Variables:"
@echo " FILTER_NAME - Name of the MCP filter to build (default: ${FILTER_NAME})"
@echo " REGISTRY - Docker registry (default: ${REGISTRY})"
@echo " FILTER_VERSION - Version tag for the image (default: timestamp-commit)"
@echo " IMG - Full image name (default: ${IMG})"

View File

@@ -0,0 +1,89 @@
# MCP Router Plugin
## Feature Description
The `mcp-router` plugin provides a routing capability for MCP (Model Context Protocol) `tools/call` requests. It inspects the tool name in the request payload, and if the name is prefixed with a server identifier (e.g., `server-name/tool-name`), it dynamically reroutes the request to the appropriate backend MCP server.
This enables the creation of a unified MCP endpoint that can aggregate tools from multiple, distinct MCP servers. A client can make a `tools/call` request to a single endpoint, and the `mcp-router` will ensure it reaches the correct underlying server where the tool is actually hosted.
## Configuration Fields
| Name | Data Type | Required | Default Value | Description |
|-----------|---------------|----------|---------------|---------------------------------------------------------------------------------------------------------|
| `servers` | array of objects | Yes | - | A list of routing configurations for each backend MCP server. |
| `servers[].name` | string | Yes | - | The unique identifier for the MCP server. This must match the prefix used in the `tools/call` request's tool name. |
| `servers[].domain` | string | No | - | The domain (authority) of the backend MCP server. If omitted, the original request's domain will be kept. |
| `servers[].path` | string | Yes | - | The path of the backend MCP server to which the request will be routed. |
## How It Works
When a `tools/call` request is processed by a route with the `mcp-router` plugin enabled, the following occurs:
1. **Tool Name Parsing**: The plugin inspects the `name` parameter within the `params` object of the JSON-RPC request.
2. **Prefix Matching**: It checks if the tool name follows the `server-name/tool-name` format.
- If it does not match this format, the plugin takes no action, and the request proceeds normally.
- If it matches, the plugin extracts the `server-name` and the actual `tool-name`.
3. **Route Lookup**: The extracted `server-name` is used to look up the corresponding routing configuration (domain and path) from the `servers` list in the plugin's configuration.
4. **Header Modification**:
- The `:authority` request header is replaced with the `domain` from the matched server configuration.
- The `:path` request header is replaced with the `path` from the matched server configuration.
5. **Request Body Modification**: The `name` parameter in the JSON-RPC request body is updated to be just the `tool-name` (the `server-name/` prefix is removed).
6. **Rerouting**: After the headers are modified, the gateway's routing engine processes the request again with the new destination information, sending it to the correct backend MCP server.
### Example Configuration
Here is an example of how to configure the `mcp-router` plugin in a `higress-plugins.yaml` file:
```yaml
servers:
- name: random-user-server
domain: mcp.example.com
path: /mcp-servers/mcp-random-user-server
- name: rest-amap-server
domain: mcp.example.com
path: /mcp-servers/mcp-rest-amap-server
```
### Example Usage
Consider a `tools/call` request sent to an endpoint where the `mcp-router` is active:
**Original Request:**
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "rest-amap-server/get-weather",
"arguments": {
"location": "New York"
}
}
}
```
**Plugin Actions:**
1. The plugin identifies the tool name as `rest-amap-server/get-weather`.
2. It extracts `server-name` as `rest-amap-server` and `tool-name` as `get-weather`.
3. It finds the matching configuration: `domain: mcp.example.com`, `path: /mcp-servers/mcp-rest-amap-server`.
4. It modifies the request headers to:
- `:authority`: `mcp.example.com`
- `:path`: `/mcp-servers/mcp-rest-amap-server`
5. It modifies the request body to:
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get-weather",
"arguments": {
"location": "New York"
}
}
}
```
The request is then rerouted to the `rest-amap-server`.

View File

@@ -0,0 +1,89 @@
# MCP Router 插件
## 功能说明
`mcp-router` 插件为 MCP (Model Context Protocol) 的 `tools/call` 请求提供了路由能力。它会检查请求负载中的工具名称,如果名称带有服务器标识符前缀(例如 `server-name/tool-name`),它会动态地将请求重新路由到相应的后端 MCP 服务器。
这使得创建一个统一的 MCP 端点成为可能,该端点可以聚合来自多个不同 MCP 服务器的工具。客户端可以向单个端点发出 `tools/call` 请求,`mcp-router` 将确保请求到达托管该工具的正确底层服务器。
## 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
| `servers` | 对象数组 | 是 | - | 每个后端 MCP 服务器的路由配置列表。 |
| `servers[].name` | 字符串 | 是 | - | MCP 服务器的唯一标识符。这必须与 `tools/call` 请求的工具名称中使用的前缀相匹配。 |
| `servers[].domain` | 字符串 | 否 | - | 后端 MCP 服务器的域名 (authority)。如果省略,将保留原始请求的域名。 |
| `servers[].path` | 字符串 | 是 | - | 请求将被路由到的后端 MCP 服务器的路径。 |
## 工作原理
当一个启用了 `mcp-router` 插件的路由处理 `tools/call` 请求时,会发生以下情况:
1. **工具名称解析**:插件检查 JSON-RPC 请求中 `params` 对象的 `name` 参数。
2. **前缀匹配**:它检查工具名称是否遵循 `server-name/tool-name` 格式。
- 如果不匹配此格式,插件不执行任何操作,请求将正常继续。
- 如果匹配,插件将提取 `server-name` 和实际的 `tool-name`
3. **路由查找**:提取的 `server-name` 用于从插件配置的 `servers` 列表中查找相应的路由配置domain 和 path
4. **Header 修改**
- `:authority` 请求头被替换为匹配的服务器配置中的 `domain`
- `:path` 请求头被替换为匹配的服务器配置中的 `path`
5. **请求体修改**JSON-RPC 请求体中的 `name` 参数被更新为仅包含 `tool-name`(移除了 `server-name/` 前缀)。
6. **重新路由**:在 Header 修改后,网关的路由引擎会使用新的目标信息再次处理请求,将其发送到正确的后端 MCP 服务器。
### 配置示例
以下是在 `higress-plugins.yaml` 文件中配置 `mcp-router` 插件的示例:
```yaml
servers:
- name: random-user-server
domain: mcp.example.com
path: /mcp-servers/mcp-random-user-server
- name: rest-amap-server
domain: mcp.example.com
path: /mcp-servers/mcp-rest-amap-server
```
### 使用示例
假设一个 `tools/call` 请求被发送到激活了 `mcp-router` 的端点:
**原始请求:**
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "rest-amap-server/get-weather",
"arguments": {
"location": "New York"
}
}
}
```
**插件行为:**
1. 插件识别出工具名称为 `rest-amap-server/get-weather`
2. 它提取出 `server-name``rest-amap-server``tool-name``get-weather`
3. 它找到匹配的配置:`domain: mcp.example.com`, `path: /mcp-servers/mcp-rest-amap-server`
4. 它将请求头修改为:
- `:authority`: `mcp.example.com`
- `:path`: `/mcp-servers/mcp-rest-amap-server`
5. 它将请求体修改为:
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get-weather",
"arguments": {
"location": "New York"
}
}
}
```
请求随后被重新路由到 `rest-amap-server`。

View File

@@ -0,0 +1,34 @@
module mcp-router
go 1.24.1
require (
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250612125225-016b165a33c9
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/resp v0.1.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/crypto v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -0,0 +1,71 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250612125225-016b165a33c9 h1:MBIjh29Qie+jmPQ9W61wOzyUoulk/lsOjdj6hoYTRpo=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250612125225-016b165a33c9/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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/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/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b h1:rRI9+ThQbe+nw4jUiYEyOFaREkXCMMW9k1X2gy2d6pE=
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b/go.mod h1:rU3M+Tq5VrQOo0dxpKHGb03Ty0sdWIZfAH+YCOACx/Y=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0 h1:Ta+RBsZYML3hjoenbGJoS2L6aWJN+hqlxKoqzj/Y2SY=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
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.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,133 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/mcp"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
func main() {}
func init() {
mcp.LoadMCPFilter(
mcp.FilterName("mcp-router"),
mcp.SetConfigParser(ParseConfig),
mcp.SetToolCallRequestFilter(ProcessRequest),
)
mcp.InitMCPFilter()
}
// ServerConfig represents the routing configuration for a single MCP server
type ServerConfig struct {
Name string `json:"name"`
Domain string `json:"domain,omitempty"`
Path string `json:"path"`
}
// McpRouterConfig represents the configuration for the mcp-router filter
type McpRouterConfig struct {
Servers []ServerConfig `json:"servers"`
}
func ParseConfig(configBytes []byte, filterConfig *any) error {
var config McpRouterConfig
if err := json.Unmarshal(configBytes, &config); err != nil {
return fmt.Errorf("failed to parse mcp-router config: %v", err)
}
log.Infof("Parsed mcp-router config with %d servers", len(config.Servers))
for _, server := range config.Servers {
log.Debugf("Server: %s -> %s%s", server.Name, server.Domain, server.Path)
}
*filterConfig = config
return nil
}
func ProcessRequest(context wrapper.HttpContext, config any, toolName string, toolArgs gjson.Result, rawBody []byte) types.Action {
routerConfig, ok := config.(McpRouterConfig)
if !ok {
log.Errorf("Invalid config type for mcp-router")
return types.ActionContinue
}
// Extract server name from tool name (format: "serverName/toolName")
parts := strings.SplitN(toolName, "/", 2)
if len(parts) != 2 {
log.Debugf("Tool name '%s' does not contain server prefix, continuing without routing", toolName)
return types.ActionContinue
}
serverName := parts[0]
actualToolName := parts[1]
log.Debugf("Routing tool call: server=%s, tool=%s", serverName, actualToolName)
// Find the server configuration
var targetServer *ServerConfig
for _, server := range routerConfig.Servers {
if server.Name == serverName {
targetServer = &server
break
}
}
if targetServer == nil {
log.Warnf("No routing configuration found for server '%s'", serverName)
return types.ActionContinue
}
log.Infof("Routing to server '%s': domain=[%s], path=[%s]", serverName, targetServer.Domain, targetServer.Path)
// Modify the :authority header (domain) if it's configured
if targetServer.Domain != "" {
if err := proxywasm.ReplaceHttpRequestHeader(":authority", targetServer.Domain); err != nil {
log.Errorf("Failed to set :authority header to '%s': %v", targetServer.Domain, err)
return types.ActionContinue
}
}
// Modify the :path header
if err := proxywasm.ReplaceHttpRequestHeader(":path", targetServer.Path); err != nil {
log.Errorf("Failed to set :path header to '%s': %v", targetServer.Path, err)
return types.ActionContinue
}
// Create a new JSON with the modified tool name
modifiedBody, err := sjson.SetBytes(rawBody, "params.name", actualToolName)
if err != nil {
log.Errorf("Failed to modify tool name, body: %s, err: %v", rawBody, err)
return types.ActionContinue
}
// Replace the request body
if err := proxywasm.ReplaceHttpRequestBody([]byte(modifiedBody)); err != nil {
log.Errorf("Failed to replace request body: %v", err)
return types.ActionContinue
}
log.Infof("Successfully routed request for tool '%s' to server '%s'. New tool name is '%s'.",
toolName, serverName, actualToolName)
return types.ActionContinue
}

View File

@@ -347,7 +347,7 @@ tools:
3. 创建正确格式化 API 请求的 requestTemplate包括带有模板值的头部
4. 创建将 API 响应转换为适合 AI 消费的可读格式的 responseTemplate
模板使用 GJSON Template 语法 (https://github.com/higress-group/gjson_template),该语法结合了 Go 模板和 GJSON 路径语法进行 JSON 处理。模板引擎支持:
模板使用 [GJSON Template 语法](https://github.com/higress-group/gjson_template),该语法结合了 Go 模板和 GJSON 路径语法进行 JSON 处理。模板引擎支持:
1. 基本点表示法访问字段:{{.fieldName}}
2. 用于复杂查询的 gjson 函数:{{gjson "users.#(active==true)#.name"}}

View File

@@ -8,7 +8,7 @@ replace amap-tools => ../amap-tools
require (
amap-tools v0.0.0-00010101000000-000000000000
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250528033743-f88b782fe131
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db
quark-search v0.0.0-00010101000000-000000000000
)
@@ -21,7 +21,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b // indirect
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0 // indirect
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@@ -36,5 +36,6 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/crypto v0.26.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -6,16 +6,8 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507130917-ed12a186173a h1:CvTkMBU9+SGIyJEJYFEvg/esoVbLzQP9WVeoZzMHM9E=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507130917-ed12a186173a/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250513083230-017f47fc2432 h1:Acw2RhWABsw3Mg+agBhKJML+Fk5CbDBJcVhM9HM2lmk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250513083230-017f47fc2432/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250515035738-c8f491db9030 h1:CX3lqAbgKnsrNpLYlfi6xDmnyMKsU8NJcMCCaci8BUI=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250515035738-c8f491db9030/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250526122106-bde03cd884e5 h1:ACvlY5Vu7SN+K1posB3UP3l4G+Iw5+6iMcAEaBKJvH8=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250526122106-bde03cd884e5/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250528033743-f88b782fe131 h1:/efvKhP31Qo4RE48mjJCNC1jpVObgAohNe23bN5hFPA=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250528033743-f88b782fe131/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db h1:hubkTsadmBj/FNfh9gI0glOWI7NEDQeF+UwX0EmO0Es=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db/go.mod h1:ixggLUTsFfFogWS6p95AzTfey/XbPccCWln1gyvkY0M=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
@@ -30,8 +22,8 @@ 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/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b h1:rRI9+ThQbe+nw4jUiYEyOFaREkXCMMW9k1X2gy2d6pE=
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b/go.mod h1:rU3M+Tq5VrQOo0dxpKHGb03Ty0sdWIZfAH+YCOACx/Y=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0 h1:Ta+RBsZYML3hjoenbGJoS2L6aWJN+hqlxKoqzj/Y2SY=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1:xqmtTZI0JQ2O+Lg9/CE6c+Tw9KD6FnvWw8EpLVuuvfg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
@@ -73,6 +65,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -3,8 +3,8 @@ module amap-tools
go 1.24.1
require (
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507122328-b62384cff88a
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80
)
require (
@@ -30,5 +30,6 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/crypto v0.26.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -6,8 +6,8 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507122328-b62384cff88a h1:VQrtP0CR4pgIL3FGnIAb+uY3yRwaMQk2c3AT3p+LVwk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507122328-b62384cff88a/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db h1:hubkTsadmBj/FNfh9gI0glOWI7NEDQeF+UwX0EmO0Es=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db/go.mod h1:ixggLUTsFfFogWS6p95AzTfey/XbPccCWln1gyvkY0M=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
@@ -22,8 +22,8 @@ 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/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b h1:rRI9+ThQbe+nw4jUiYEyOFaREkXCMMW9k1X2gy2d6pE=
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b/go.mod h1:rU3M+Tq5VrQOo0dxpKHGb03Ty0sdWIZfAH+YCOACx/Y=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0 h1:Ta+RBsZYML3hjoenbGJoS2L6aWJN+hqlxKoqzj/Y2SY=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1:xqmtTZI0JQ2O+Lg9/CE6c+Tw9KD6FnvWw8EpLVuuvfg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
@@ -65,6 +65,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -58,11 +58,11 @@ func (t AroundSearchRequest) Call(ctx server.HttpContext, s server.Server) error
url := fmt.Sprintf("http://restapi.amap.com/v3/place/around?key=%s&location=%s&radius=%s&keywords=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Location), url.QueryEscape(t.Radius), url.QueryEscape(t.Keywords))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("around search call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("around search call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -57,11 +57,11 @@ func (t BicyclingRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v4/direction/bicycling?key=%s&origin=%s&destination=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Origin), url.QueryEscape(t.Destination))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("bicycling call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("bicycling call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -57,11 +57,11 @@ func (t DrivingRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v3/direction/driving?key=%s&origin=%s&destination=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Origin), url.QueryEscape(t.Destination))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("driving call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("driving call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -59,11 +59,11 @@ func (t TransitIntegratedRequest) Call(ctx server.HttpContext, s server.Server)
url := fmt.Sprintf("http://restapi.amap.com/v3/direction/transit/integrated?key=%s&origin=%s&destination=%s&city=%s&cityd=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Origin), url.QueryEscape(t.Destination), url.QueryEscape(t.City), url.QueryEscape(t.Cityd))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("transit integrated call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("transit integrated call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -57,11 +57,11 @@ func (t WalkingRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v3/direction/walking?key=%s&origin=%s&destination=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Origin), url.QueryEscape(t.Destination))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("walking call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("walking call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -57,11 +57,11 @@ func (t DistanceRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v3/distance?key=%s&origins=%s&destination=%s&type=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Origins), url.QueryEscape(t.Destination), url.QueryEscape(t.Type))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("distance call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("distance call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -58,11 +58,11 @@ func (t GeoRequest) Call(ctx server.HttpContext, s server.Server) error {
apiKey := serverConfig.ApiKey
url := fmt.Sprintf("https://restapi.amap.com/v3/geocode/geo?key=%s&address=%s&city=%s&source=ts_mcp", apiKey, url.QueryEscape(t.Address), url.QueryEscape(t.City))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("geo call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("geo call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -70,12 +70,12 @@ func (t IPLocationRequest) Call(ctx server.HttpContext, s server.Server) error {
}
url := fmt.Sprintf("https://restapi.amap.com/v3/ip?ip=%s&key=%s&source=ts_mcp", url.QueryEscape(t.IP), serverConfig.ApiKey)
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("ip location call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("ip location call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -56,11 +56,11 @@ func (t ReGeocodeRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v3/geocode/regeo?location=%s&key=%s&source=ts_mcp", url.QueryEscape(t.Location), serverConfig.ApiKey)
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("regeocode call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("regeocode call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -56,11 +56,11 @@ func (t SearchDetailRequest) Call(ctx server.HttpContext, s server.Server) error
url := fmt.Sprintf("http://restapi.amap.com/v3/place/detail?id=%s&key=%s&source=ts_mcp", url.QueryEscape(t.ID), serverConfig.ApiKey)
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("search detail call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("search detail call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -58,11 +58,11 @@ func (t TextSearchRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v3/place/text?key=%s&keywords=%s&city=%s&citylimit=%s&source=ts_mcp", serverConfig.ApiKey, url.QueryEscape(t.Keywords), url.QueryEscape(t.City), url.QueryEscape(t.Citylimit))
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("text search call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("text search call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -56,11 +56,11 @@ func (t WeatherRequest) Call(ctx server.HttpContext, s server.Server) error {
url := fmt.Sprintf("http://restapi.amap.com/v3/weather/weatherInfo?city=%s&key=%s&source=ts_mcp&extensions=all", url.QueryEscape(t.City), serverConfig.ApiKey)
return ctx.RouteCall(http.MethodGet, url,
[][2]string{{"Accept", "application/json"}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
[][2]string{{"Accept", "application/json"}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("weather call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("weather call failed, status: %d", statusCode))
return
}
utils.SendMCPToolTextResult(sendDirectly, ctx, string(responseBody))
utils.SendMCPToolTextResult(ctx, string(responseBody))
})
}

View File

@@ -0,0 +1,61 @@
# Bid Tools MCP Server
## What is Junrun Bidding Digital Employee?
- Bidding is an important channel for enterprises to obtain projects and customers. Junrun Bidding Digital Employee can provide comprehensive and accurate bidding information, helping enterprises effectively improve their bidding capabilities.
## What are the problems with traditional bidding information acquisition channels?
- Obtaining bidding information through bidding platforms was a commonly used method in the past. However, there are many sources of bidding information, and it is difficult for general bidding platforms to cover all of them. There is a large amount of duplication in the bidding information from different platforms, making it difficult to deduplicate and filter.
- Traditional bidding information screening only uses keyword filtering, which is not very accurate. A large amount of bidding information still needs to be screened by experienced personnel, resulting in high labor costs.
## What can Junrun Bidding Digital Employee do?
- According to the subscribed bidding information, it pushes accurate bidding information filtered by AI via email every day. You can view the title, details, and download attachments of the bidding information.
- Based on AI capabilities, it analyzes bidding documents and automatically extracts key information, greatly improving the efficiency of bidding decision-making.
- Based on historical bidding information, it provides future bidding predictions to help enterprises plan in advance.
- Analyzes the historical bidding and winning situations of customers to support the formulation of bidding strategies.
- Analyzes the historical bidding and winning situations of competitors to obtain a list of target customers.
## Why choose Junrun Bidding Digital Employee?
### More comprehensive bidding data
- It cooperates with multiple source data enterprises, covering government bidding information, third - party bidding information, and enterprise self - owned website bidding information. In total, it covers more than 100,000 first - release information source stations, more than 290,000 collection channel addresses, and more than 10,000 new media official accounts (continuously updated). Tens of thousands of bidding and procurement information are updated daily, and the coverage rate of publicly available bidding and procurement information across the network can reach over 98% (the remaining 2% is due to new resources and the restructuring of existing resources).
### More accurate screening results
- Based on AI large - scale models, data training, and matching technology, the accuracy is significantly improved compared to traditional keyword search and rule - based judgment.
### Higher bidding decision - making efficiency
- Analysis of historical winning situations and one - click analysis of bidding documents to extract key information effectively assist in bidding decision - making.
## Data Source Coverage
| Site Type | Number of Sites (100,000+) | Number of Collection Source Addresses (290,000+) | Information Release Volume Proportion |
|------------------|----------|------------|----------------|
| Enterprise Bidding | - |- | 13.1183% |
| E - Procurement Platform | - | - | 33.0614% |
| Financial Banks | - | - | 0.0757% |
| Higher Education | - | - | 0.9645% |
| Education Bureau (Website) | - | - | 0.0173% |
| Hospitals | - | - | 0.8030% |
| Other Healthcare Institutions | - | - | 0.0167% |
| People's Government | - | - | 8.6457% |
| Public Resource Centers | - | - | 11.6248% |
| Government Procurement Centers | - | - | 27.8139% |
| Project Trading Centers | - | - | 3.5826% |
| Administrative Service Centers | - | - | 0.2761% |
## Features
- `getBidlist`: Query bidding information across the network based on keywords. Input multiple keywords and return the bidding query results.
- `getBidinfo`: Query the details of bidding information based on the bidding ID. Input the bidding ID and return the details of the bidding information.
- `Email`: An offline - configured enterprise bidding matching email notification service. It combines the enterprise's industry, business scope, keyword groups, etc. to create a unique enterprise profile. Then, it uses the Qwen3 large model of Alibaba's Bailian platform to deeply analyze daily bidding information and provide accurate bidding matching services for enterprises.
## Tutorial
### Get API Key
1. Register an account [Create an ID](https://moonai-bid.junrunrenli.com?src=higress)
2. Send an email to: yuanpeng@junrunrenli.com with the subject: MCP and the content: Apply for the bidding tool service, and provide your account.
### Knowledge Base (Alibaba Bailian Knowledge Base)
1. The clearer the enterprise's exclusive profile, the better the matching effect. Through continuous optimization, our knowledge base will be continuously improved to provide more accurate bidding matching services for enterprises.
### Configure API Key
In the `mcp - server.yaml` file, set the `jr - api - key` field to a valid API key.
### Integrate into MCP Client
On the user's MCP Client interface, add the relevant configuration to the MCP Server list.

View File

@@ -0,0 +1,67 @@
# Bid Tools MCP Server
## 君润标讯数字员工是什么
- 招投标是企业获得项目和客户的重要渠道,君润标讯数字员工能够提供全面、精准的标讯信息,帮助企业有效提升投标能力。
## 传统的标讯获取渠道存在什么问题
- 通过标讯平台获取标讯是过去常用的方式,但标讯来源多,一般标讯平台难以覆盖全,不同标讯平台的标讯存在大量重复,去重和筛选难
- 传统的标讯筛选仅通过关键词过滤,精准度不高;大量的标讯还需要依赖有经验的人员筛选,人力成本高
## 君润标讯数字员工能做什么
- 根据订阅的标讯信息每天通过邮件推送经过AI筛选后的精准标讯可以查看标讯的标题、详情、下载附件
- 基于AI的能力进行招标书的解析自动提取关键信息大幅提升投标决策的效率
- 基于历史标讯,提供未来的招标预测,提前布局
- 分析客户历史招标和中标情况,为制定投标策略提供支持
- 分析友商的历史投标和中标情况,获得目标客户清单
## 为什么选择君润标讯数字员工
### 标讯数据更全
- 与多家源头数据企业合作,涵盖政府标讯、第三方招标讯、企业自主网站标讯等,共计覆盖 10 万余个首发信息源站29 万余个采集频道地址1 万余个新媒体公众号源持续更新中每日更数万条招标、采购信息全网公开招标采购信息覆盖率可达98%以上(其余 2%由新资源和已有资源改版产生)
### 筛选结果更精准
- 基于AI大模型、数据训练及匹配技术相比传统的关键字搜索、规则判断精准度大幅提高
### 投标决策效率更高
- 历史中标情况分析、招标书一键解标,提取关键信息,有效辅助投标决策
## 数据源覆盖范围
| 站点类型 | 站点数量10万+ | 采集源地址数量29万+ | 信息发布量占比 |
|------------------|----------|------------|----------------|
| 企业招标 | - |- | 13.1183% |
| 电子采购平台 | - | - | 33.0614% |
| 金融银行 | - | - | 0.0757% |
| 高等教育 | - | - | 0.9645% |
| 教育局(网) | - | - | 0.0173% |
| 医院 | - | - | 0.8030% |
| 其他卫生机构 | - | - | 0.0167% |
| 人民政府 | - | - | 8.6457% |
| 公共资源中心 | - | - | 11.6248% |
| 政府采购中心 | - | - | 27.8139% |
| 工程交易中心 | - | - | 3.5826% |
| 行政服务中心 | - | - | 0.2761% |
![标讯治理流程](https://img.alicdn.com/imgextra/i1/O1CN01UMagYW28vlbrvgHAP_!!6000000007995-2-tps-557-262.png)
## 功能
- getBidlist根据关键字查询全网标讯信息。输入多个关键字返回招标查询结果。
- getBidinfo根据标讯ID查询标讯详情。输入标讯ID返回标讯详情。
- Email 企标匹配邮件通知服务(线下配置)根据企业的行业、业务范围、关键词群等等信息组合为企业专属画像再结合阿里百炼平台qwen3大模型深度思考每日标讯为企业匹配精准的标讯匹配服务。
## 使用教程
### 获取 apikey
1. 注册账号 [Create a ID](https://moonai-bid.junrunrenli.com?src=higress)
2. 发送邮件to: yuanpeng@junrunrenli.com 标题MCP 内容:申请标讯工具服务,并提供你的账号。
### 知识库(阿里百炼知识库)
1. 企业专属画像越明确,匹配效果越好。通过持续调优,我们的知识库将不断完善,为企业提供更精准的标讯匹配服务。
### 配置 API Key
`mcp-server.yaml` 文件中,将 `jr-api-key` 字段设置为有效的 API 密钥。
### 集成到 MCP Client
在用户的 MCP Client 界面,将相关配置添加到 MCP Server 列表中。

View File

@@ -0,0 +1,57 @@
server:
name: jr-ohris-bid-mcp
config:
apikey: ""
tools:
- name: getBidList
description: |+
根据关键字查询标讯列表。
- 输入关键字、开始时间、结束时间等信息。
- 支持可选的地区编码和当前页参数。
- 返回标讯列表。
args:
- name: keywords
description: 关键字
type: string
required: true
- name: cr_start_time
description: 开始时间,格式为 yyyy-MM-dd HH:mm:ss
type: string
required: true
- name: cr_end_time
description: 结束时间,格式为 yyyy-MM-dd HH:mm:ss
type: string
required: true
- name: areas
description: 地区编码
type: string
required: false
- name: page
description: 当前页
type: string
required: false
requestTemplate:
argsToUrlParam: false
url: https://agent-bid.junrunrenli.com/agent/tools/getBidList?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getBidInfo
description: |+
根据关键字标讯ID查询详情。
- 输入标讯ID。
- 返回标讯详情。
args:
- name: id
description: 标讯ID
type: string
required: true
requestTemplate:
argsToUrlParam: false
url: https://agent-bid.junrunrenli.com/agent/tools/getBidInfo?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json

View File

@@ -10,23 +10,24 @@
- 根据城市信息工伤情况计算赔付费用。输入工伤等级和薪资信息,返回赔付费用。
- 根据城市信息工亡情况计算赔付费用。输入相关信息,返回赔付费用。
- 详细清单如下:
1.getCityCanbaoYear 根据城市编码查询该城市缴纳残保金年份
2.getCityShebaoBase 根据城市编码和年份查询该城市缴纳残保金基数
3.calcCanbaoCity 计算该城市推荐雇佣残疾人人数和节省费用
4.getCityPersonDeductRules 查询工资薪金个税专项附加扣除
5.calcCityNormal 根据工资计算该城市个税缴纳明细
6.calcCityLaobar 计算一次性劳务报酬应缴纳税额
7.getCityIns 根据城市ID查询该城市社保和公积金缴费信息
8.calcCityYearEndBonus 计算全年一次性奖金应缴纳税额
9.getCityGm 计算该城市工亡赔偿费用
10.getCityAvgSalary 根据城市ID查询该城市上年度平均工资
11.getCityDisabilityLevel 根据城市ID查询该城市伤残等级
12.getCityNurseLevel 根据城市ID查询该城市护理等级
13.getCityCompensateProject 查询所有工伤费用类型
14.getCityInjuryCData 查询工伤费用计算规则
15.getCityCalcInjury 根据城市ID和费用类型项计算工伤费用
16.getshebaoInsOrg 查询指定城市社保政策
17.calculator 计算该城市社保和公积金缴纳明细
-
1. getCityCanbaoYear 根据城市编码查询该城市缴纳残保金年份
2. getCityShebaoBase 根据城市编码和年份查询该城市缴纳残保金基数
3. calcCanbaoCity 计算该城市推荐雇佣残疾人人数和节省费用
4. getCityPersonDeductRules 查询工资薪金个税专项附加扣除
5. calcCityNormal 根据工资计算该城市个税缴纳明细
6. calcCityLaobar 计算一次性劳务报酬应缴纳税额
7. getCityIns 根据城市ID查询该城市社保和公积金缴费信息
8. calcCityYearEndBonus 计算全年一次性奖金应缴纳税额
9. getCityGm 计算该城市工亡赔偿费用
10. getCityAvgSalary 根据城市ID查询该城市上年度平均工资
11. getCityDisabilityLevel 根据城市ID查询该城市伤残等级
12. getCityNurseLevel 根据城市ID查询该城市护理等级
13. getCityCompensateProject 查询所有工伤费用类型
14. getCityInjuryCData 查询工伤费用计算规则
15. getCityCalcInjury 根据城市ID和费用类型项计算工伤费用
16. getshebaoInsOrg 查询指定城市社保政策
17. calculator 计算该城市社保和公积金缴纳明细
## 使用教程

View File

@@ -1,385 +1,522 @@
server:
name: shebao-tools
name: shebao-tools-api-server
config:
apikey: ""
tools:
- name: calculate_social_security
- name: calcCityNormal
description: |+
根据城市信息计算社保、公积金费用
- 输入城市名称和薪资信息。
- 返回社保公积金的详细计算结果。
根据工资计算该城市个税缴纳明细
- 输入税前工资、城市名称、城市编码、城市ID等信息。
- 考虑社保公积金、专项附加扣除等因素。
- 返回个税缴纳明细。
args:
- name: city
description: 城市名称
type: string
required: true
- name: salary
description: 个人薪资
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools?jr-api-key={apikey}
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calculate_disability_insurance
description: |+
根据企业规模计算残保金。
- 输入企业员工数量和平均薪资。
- 返回残保金的计算结果。
args:
- name: employee_count
description: 企业员工数量
- name: salaryPay
description: 税前工资
type: integer
required: true
- name: average_salary
description: 企业平均薪资
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools?jr-api-key={apikey}
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calculate_income_tax
description: |+
根据个人薪资计算个税缴纳费用。
- 输入个人薪资。
- 返回个税缴纳费用。
args:
- name: salary
description: 个人薪资
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools?jr-api-key={apikey}
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calculate_work_injury_compensation
description: |+
根据工伤情况计算赔付费用。
- 输入工伤等级和薪资信息。
- 返回工伤赔付费用。
args:
- name: injury_level
description: 工伤等级
type: string
required: true
- name: salary
description: 个人薪资
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools?jr-api-key={apikey}
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calculate_work_death_compensation
description: |+
根据工亡情况计算赔付费用。
- 输入相关信息。
- 返回工亡赔付费用。
args:
- name: relevant_info
description: 相关信息(可根据实际情况细化)
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools?jr-api-key={apikey}
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityCanbaoYear
description: 根据城市编码查询该城市缴纳残保金年份
args:
- name: city_code
description: 城市编码
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityCanbaoYear
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityShebaoBase
description: 根据城市编码和年份查询该城市缴纳残保金基数
args:
- name: city_code
description: 城市编码
type: string
required: true
- name: year
description: 年份
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityShebaoBase
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calcCanbaoCity
description: 计算该城市推荐雇佣残疾人人数和节省费用
args:
- name: city_code
description: 城市编码
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/calcCanbaoCity
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityPersonDeductRules
description: 查询工资薪金个税专项附加扣除
args:
- name: city_code
description: 城市编码
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityPersonDeductRules
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calcCityNormal
description: 根据工资计算该城市个税缴纳明细
args:
- name: city_code
description: 城市编码
type: string
required: true
- name: salary
description: 个人薪资
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/calcCityNormal
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calcCityLaobar
description: 计算一次性劳务报酬应缴纳税额
args:
- name: city_code
description: 城市编码
type: string
required: true
- name: labor_income
description: 一次性劳务报酬
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/calcCityLaobar
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityIns
description: 根据城市ID查询该城市社保和公积金缴费信息
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityIns
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calcCityYearEndBonus
description: 计算全年一次性奖金应缴纳税额
args:
- name: city_code
description: 城市编码
type: string
required: true
- name: year_end_bonus
description: 全年一次性奖金
type: number
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/calcCityYearEndBonus
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityGm
description: 计算该城市工亡赔偿费用
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityGm
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityAvgSalary
description: 根据城市ID查询该城市上年度平均工资
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityAvgSalary
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityDisabilityLevel
description: 根据城市ID查询该城市伤残等级
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityDisabilityLevel
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityNurseLevel
description: 根据城市ID查询该城市护理等级
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityNurseLevel
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityCompensateProject
description: 查询所有工伤费用类型
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityCompensateProject
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityInjuryCData
description: 查询工伤费用计算规则
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityInjuryCData
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getCityCalcInjury
description: 根据城市ID和费用类型项计算工伤费用
args:
- name: city_id
description: 城市ID
type: string
required: true
- name: expense_type
description: 费用类型项
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getCityCalcInjury
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: getshebaoInsOrg
description: 查询指定城市社保政策
args:
- name: city_id
description: 城市ID
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/getshebaoInsOrg
method: GET
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- name: calculator
description: 计算该城市社保和公积金缴纳明细
args:
- name: city
- name: areaName
description: 城市名称
type: string
required: true
- name: salary
description: 个人薪资
type: number
- name: areaCode
description: 城市编码
type: string
required: true
- name: areaId
description: 城市ID
type: integer
required: true
- name: sbFlag
description: 是否缴纳社保
type: integer
required: false
- name: gjjFlag
description: 是否缴纳公积金
type: integer
required: false
- name: sbCode
description: 城市社保编号
type: string
required: false
- name: sbBase
description: 城市社保基数
type: integer
required: false
- name: gjjCode
description: 城市公积金编号
type: string
required: false
- name: gjjBase
description: 城市公积金基数
type: integer
required: false
- name: znjyCount
description: 子女教育数量
type: string
required: false
- name: znjyCode
description: 子女教育扣除方式
type: string
required: false
- name: zfzjCode
description: 住房租金
type: string
required: false
- name: zfdkCode
description: 住房贷款利息
type: string
required: false
- name: jxjyCode
description: 继续教育
type: string
required: false
- name: sylrCode
description: 赡养老人
type: string
required: false
- name: sylrFee
description: 赡养老人数量
type: string
required: false
- name: yyzhCount
description: 三岁以下婴幼儿照护数量
type: string
required: false
- name: yyzhCode
description: 三岁以下婴幼儿照护扣除方式
type: string
required: false
- name: avgMonthYanglaoFee
description: 平均每月个人养老金
type: string
required: false
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/geshui/calcNormal?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: calculator
description: |+
计算该城市社保和公积金缴纳明细。
- 输入城市名称、城市编码、城市ID等信息。
- 考虑社保、公积金缴纳状态和基数。
- 返回社保和公积金缴纳明细。
args:
- name: areaName
description: 城市名称
type: string
required: true
- name: areaCode
description: 城市编码
type: string
required: true
- name: areaId
description: 城市ID
type: integer
required: true
- name: sbCode
description: 城市社保编号
type: string
required: false
- name: sbTypeText
description: 城市社保类型
type: string
required: false
- name: sbBase
description: 城市社保基数
type: integer
required: false
- name: sbFlag
description: 是否缴纳社保
type: integer
required: false
- name: gjjFlag
description: 是否缴纳公积金
type: integer
required: false
- name: gjjCode
description: 城市公积金编号
type: string
required: false
- name: gjjTypeText
description: 城市公积金类型
type: string
required: false
- name: gjjBase
description: 城市公积金基数
type: integer
required: false
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/shebao/calculator?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: calcCityLaobar
description: |+
计算一次性劳务报酬应缴纳税额。
- 输入劳务报酬。
- 返回应缴纳税额。
args:
- name: laborPay
description: 劳务报酬
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/calculator
method: GET
url: https://agent-tools.jrit.top/agent/tools/shebao/getInsOrg?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: jr-api-key
value: "{{.config.apikey}}"
- key: Content-Type
value: application/json
- name: getCityInjuryCData
description: |+
查询工伤费用计算规则。
- 输入城市ID、伤残等级、护理级别。
- 返回工伤费用计算规则。
args:
- name: areaId
description: 城市ID
type: string
required: true
- name: injuryCDisabilityLevel
description: 伤残等级
type: integer
required: true
- name: injuryCNurseLevel
description: 护理级别
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/searchInitInjuryCData?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityDisabilityLevel
description: |+
根据城市ID查询该城市伤残等级。
- 输入城市ID。
- 返回该城市伤残等级。
args:
- name: areaId
description: 城市ID
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/searchDisabilityLevel?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityCompensateProject
description: |+
查询所有工伤费用类型。
- 返回所有工伤费用类型。
args: []
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/searchCompensateProject?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityNurseLevel
description: |+
根据城市ID查询该城市护理等级。
- 输入城市ID。
- 返回该城市护理等级。
args:
- name: areaId
description: 城市ID
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/searchNurseLevel?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: calcCanbaoCity
description: |+
计算该城市推荐雇佣残疾人人数和节省费用。
- 输入城市名称、城市编码、城市ID等信息。
- 返回推荐雇佣残疾人人数和节省费用。
args:
- name: areaName
description: 城市名称
type: string
required: true
- name: areaCode
description: 城市编码
type: string
required: true
- name: areaId
description: 城市ID
type: integer
required: true
- name: totalPeople
description: 年平均员工数
type: integer
required: true
- name: avgWage
description: 年员工平均月薪
type: integer
required: true
- name: insYear
description: 残保金缴交年份
type: string
required: true
- name: minWage
description: 残疾人月薪
type: string
required: true
- name: shebaoBase
description: 残疾人社保缴纳基数
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/canbao/cal?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityPersonDeductRules
description: |+
查询工资薪金个税专项附加扣除。
- 返回工资薪金个税专项附加扣除信息。
args: []
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/geshui/queryPersonDeductRules?jr-api-key={{.config.apikey}}
method: POST
headers: []
- name: getCityAvgSalary
description: |+
根据城市ID查询该城市上年度平均工资。
- 输入城市ID。
- 返回该城市上年度平均工资。
args:
- name: areaId
description: 城市ID
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/getProvinceAreaAvgSalary?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityGm
description: |+
计算该城市工亡赔偿费用。
- 输入城市ID、城市名称、该城市上年度月平均工资、职工平均工资。
- 返回该城市工亡赔偿费用。
args:
- name: areaId
description: 城市ID
type: string
required: true
- name: areaName
description: 城市名称
type: string
required: true
- name: areaYearAverageSalary
description: 该城市上年度月平均工资
type: number
required: true
- name: avgSalary
description: 职工平均工资
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/submitDeathRefundInfo?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityCanbaoYear
description: |+
根据城市编码查询该城市缴纳残保金年份。
- 输入城市编码。
- 返回该城市缴纳残保金年份。
args:
- name: areaCode
description: 城市编码
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/canbao/allYear?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityShebaoBase
description: |+
根据城市编码和年份查询该城市缴纳残保金基数。
- 输入城市编码和残保金年份。
- 返回该城市缴纳残保金基数。
args:
- name: areaCode
description: 城市编码
type: string
required: true
- name: insYear
description: 残保金年份
type: string
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/canbao/shebaoBase?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityIns
description: |+
根据城市ID查询该城市社保和公积金缴费信息。
- 输入城市ID。
- 返回该城市社保和公积金缴费信息。
args:
- name: areaId
description: 城市ID
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/shebao/getInsOrg?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getshebaoInsOrg
description: |+
查询指定城市社保政策。
- 输入城市ID。
- 返回指定城市社保政策。
args:
- name: areaId
description: 城市ID
type: integer
required: true
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/shebao/getInsOrg?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json
- name: getCityCalcInjury
description: |+
根据城市ID和费用类型项计算工伤费用。
- 输入城市ID、城市名称、伤残等级等信息。
- 返回工伤费用计算结果。
args:
- name: areaId
description: 城市ID
type: string
required: true
- name: areaName
description: 城市名称
type: string
required: true
- name: areaAverageWageAmount
description: 该城市上年度月平均工资
type: number
required: true
- name: injuryCDisabilityLevel
description: 伤残等级
type: integer
required: true
- name: injuryCNurseLevel
description: 护理级别
type: integer
required: true
- name: workerAverageWageAmount
description: 职工平均工资
type: string
required: true
- name: initInjuryCYiLiaoFeiInfo
description: 医疗费
type: object
required: true
- name: initInjuryCTGLXQJGongZiInfo
description: 停工留薪期间工资
type: object
required: false
- name: initInjuryCPCQSHHuLiFeiInfo
description: 评残前生活护理费
type: object
required: false
- name: initInjuryCPCHSHHuLiFeiInfo
description: 评残后生活护理费
type: object
required: false
- name: initInjuryCYCXCSBuZhuJinInfo
description: 一次性伤残补助金
type: object
required: false
- name: initInjuryCYCXGSYLBuZhuJinInfo
description: 一次性工伤医疗补助金
type: object
required: false
- name: initInjuryCYCXSCJYBuZhuJinInfo
description: 一次性伤残就业补助金
type: object
required: false
- name: initInjuryCShangCanJinTieInfo
description: 伤残津贴
type: object
required: false
- name: initInjuryCQiTaPeiChangFeiYongInfo
description: 其他补偿费用
type: object
required: false
- name: initInjuryCKangFuFeiInfo
description: 康复费用
type: object
required: false
- name: initInjuryCZYHSBuZhuFeiInfo
description: 住院治疗
type: object
required: false
- name: initInjuryCJiaoTongShiSuFeiInfo
description: 交通食宿费
type: object
required: false
- name: initInjuryCFuZhuQiJuFeiInfo
description: 辅助器具费
type: object
required: false
requestTemplate:
argsToUrlParam: true
url: https://agent-tools.jrit.top/agent/tools/gongshang/getInitInjuryCData?jr-api-key={{.config.apikey}}
method: POST
headers:
- key: Content-Type
value: application/json

View File

@@ -3,7 +3,7 @@ module quark-search
go 1.24.1
require (
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507122328-b62384cff88a
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db
github.com/tidwall/gjson v1.18.0
)
@@ -16,7 +16,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b // indirect
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0 // indirect
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@@ -30,5 +30,6 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/crypto v0.26.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -6,8 +6,8 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507122328-b62384cff88a h1:VQrtP0CR4pgIL3FGnIAb+uY3yRwaMQk2c3AT3p+LVwk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250507122328-b62384cff88a/go.mod h1:yObZXF1xTx/8peEsSbtHIzz7KlTr/tZCrokIHtwF0Jk=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db h1:hubkTsadmBj/FNfh9gI0glOWI7NEDQeF+UwX0EmO0Es=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250617125129-8731ba4ea3db/go.mod h1:ixggLUTsFfFogWS6p95AzTfey/XbPccCWln1gyvkY0M=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
@@ -22,8 +22,8 @@ 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/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b h1:rRI9+ThQbe+nw4jUiYEyOFaREkXCMMW9k1X2gy2d6pE=
github.com/higress-group/gjson_template v0.0.0-20250413075336-4c4161ed428b/go.mod h1:rU3M+Tq5VrQOo0dxpKHGb03Ty0sdWIZfAH+YCOACx/Y=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0 h1:Ta+RBsZYML3hjoenbGJoS2L6aWJN+hqlxKoqzj/Y2SY=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250402062734-d50d98c305f0/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80 h1:xqmtTZI0JQ2O+Lg9/CE6c+Tw9KD6FnvWw8EpLVuuvfg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250611100342-5654e89a7a80/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
@@ -65,6 +65,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -99,9 +99,9 @@ func (t WebSearch) Call(ctx server.HttpContext, s server.Server) error {
}
return ctx.RouteCall(http.MethodGet, fmt.Sprintf("https://cloud-iqs.aliyuncs.com/search/genericSearch?query=%s", url.QueryEscape(t.Query)),
[][2]string{{"Accept", "application/json"},
{"X-API-Key", serverConfig.ApiKey}}, nil, func(sendDirectly bool, statusCode int, responseHeaders [][2]string, responseBody []byte) {
{"X-API-Key", serverConfig.ApiKey}}, nil, func(statusCode int, responseHeaders [][2]string, responseBody []byte) {
if statusCode != http.StatusOK {
utils.OnMCPToolCallError(sendDirectly, ctx, fmt.Errorf("quark search call failed, status: %d", statusCode))
utils.OnMCPToolCallError(ctx, fmt.Errorf("quark search call failed, status: %d", statusCode))
return
}
jsonObj := gjson.ParseBytes(responseBody)
@@ -125,6 +125,6 @@ func (t WebSearch) Call(ctx server.HttpContext, s server.Server) error {
results = append(results, result.Format())
}
}
utils.SendMCPToolTextResult(sendDirectly, ctx, fmt.Sprintf("# Search Results\n\n%s", strings.Join(results, "\n\n")))
utils.SendMCPToolTextResult(ctx, fmt.Sprintf("# Search Results\n\n%s", strings.Join(results, "\n\n")))
})
}

View File

@@ -24,12 +24,12 @@ const (
IstioMcpAutoGeneratedHttpRouteName = IstioMcpAutoGeneratedPrefix + "-httproute"
IstioMcpAutoGeneratedMcpServerName = IstioMcpAutoGeneratedPrefix + "-mcpserver"
StdioProtocol = "stdio"
HttpProtocol = "http"
HttpsProtocol = "https"
DubboProtocol = "dubbo"
McpSSEProtocol = "mcp-sse"
McpStreambleProtocol = "mcp-streamble"
StdioProtocol = "stdio"
HttpProtocol = "http"
HttpsProtocol = "https"
DubboProtocol = "dubbo"
McpSSEProtocol = "mcp-sse"
McpStreamableProtocol = "mcp-streamable"
)
type McpToolArgsType string

View File

@@ -56,20 +56,20 @@ const (
var (
supportedProtocols = map[string]bool{
provider.HttpProtocol: true,
provider.HttpsProtocol: true,
provider.McpSSEProtocol: true,
provider.McpStreambleProtocol: true,
provider.HttpProtocol: true,
provider.HttpsProtocol: true,
provider.McpSSEProtocol: true,
provider.McpStreamableProtocol: true,
}
protocolUpstreamTypeMapping = map[string]string{
provider.HttpProtocol: mcpserver.UpstreamTypeRest,
provider.HttpsProtocol: mcpserver.UpstreamTypeRest,
provider.McpSSEProtocol: mcpserver.UpstreamTypeSSE,
provider.McpStreambleProtocol: mcpserver.UpstreamTypeStreamable,
provider.HttpProtocol: mcpserver.UpstreamTypeRest,
provider.HttpsProtocol: mcpserver.UpstreamTypeRest,
provider.McpSSEProtocol: mcpserver.UpstreamTypeSSE,
provider.McpStreamableProtocol: mcpserver.UpstreamTypeStreamable,
}
routeRewriteProtocols = map[string]bool{
provider.McpSSEProtocol: true,
provider.McpStreambleProtocol: true,
provider.McpSSEProtocol: true,
provider.McpStreamableProtocol: true,
}
mcpServerRewriteProtocols = map[string]bool{
provider.McpSSEProtocol: true,
@@ -377,7 +377,7 @@ func (w *watcher) processServerConfig(dataId string, services *model.Service, mc
Meta: config.Meta{
GroupVersionKind: gvk.ServiceEntry,
Name: fmt.Sprintf("%s-%s", provider.IstioMcpAutoGeneratedSeName, strings.TrimSuffix(dataId, ".json")),
Namespace: w.namespace,
Namespace: "mcp",
},
Spec: serviceEntry,
}

View File

@@ -0,0 +1,299 @@
# Higress Core
## 📌feature
### Support for Google Cloud Vertex AI service
+ Related PR: [https://github.com/alibaba/higress/pull/2119](https://github.com/alibaba/higress/pull/2119)
+ Contributor: [HecarimV](https://github.com/HecarimV)
+ Change Log: Added support for Google Cloud Vertex AI, allowing proxying of Vertex services through the OpenAI protocol.
+ Feature Value: This feature extends the compatibility of AI proxies, enabling users to leverage models and capabilities provided by Vertex AI.
### New HackMD MCP Server
+ Related PR: [https://github.com/alibaba/higress/pull/2260](https://github.com/alibaba/higress/pull/2260)
+ Contributor: [Whitea029](https://github.com/Whitea029)
+ Change Log: Added a new HackMD MCP server feature, supporting user interaction with the HackMD platform via the MCP protocol, including user data management, note operations, and team collaboration features.
+ Feature Value: This PR adds support for HackMD, extending the functionality of the MCP server and enhancing user collaboration capabilities.
### New Junrun Human Resources Social Security Tool MCP Server
+ Related PR: [https://github.com/alibaba/higress/pull/2303](https://github.com/alibaba/higress/pull/2303)
+ Contributor: [hourmoneys](https://github.com/hourmoneys)
+ Change Log: Submitted MCP to REST configuration for the social security tool MCP server by Junrun Human Resources, detailing its functions, usage, and configuration, including descriptions and examples of multiple API interfaces.
+ Feature Value: Provides developers with a clear guide to using the social security calculation tool, enhancing the tool's integrability and ease of use.
### Add Claude Image Understanding and Tools Invocation Capabilities
+ Related PR: [https://github.com/alibaba/higress/pull/2385](https://github.com/alibaba/higress/pull/2385)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added Claude image understanding and tool invocation capabilities to the AI proxy, supporting streaming output and token statistics, compatible with the OpenAI interface specification, and extending the models interface support.
+ Feature Value: This PR enhances the AI proxy's functionality, enabling it to handle image input and tool invocation, improving compatibility with Claude and user experience.
### New Gemini Model Support
+ Related PR: [https://github.com/alibaba/higress/pull/2380](https://github.com/alibaba/higress/pull/2380)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added support for the Gemini model, including model list interface, image generation interface, and text-to-image conversation capabilities, extending the AI proxy's functional scope.
+ Feature Value: Full support for the Gemini model, enhancing the AI proxy's multi-model compatibility and image generation capabilities.
### New Amazon Bedrock Image Generation Support
+ Related PR: [https://github.com/alibaba/higress/pull/2212](https://github.com/alibaba/higress/pull/2212)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added support for Amazon Bedrock image generation, extending the AI proxy's functionality and allowing text-to-image generation via the Bedrock API.
+ Feature Value: Provides users with a new AI image generation method, enhancing system functionality and flexibility.
### New Model Mapping Regular Expression Support
+ Related PR: [https://github.com/alibaba/higress/pull/2358](https://github.com/alibaba/higress/pull/2358)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added support for regular expressions in model mapping, allowing more flexible model name replacements and solving specific model invocation issues.
+ Feature Value: This PR enhances the AI proxy plugin's functionality, making model mapping more flexible and powerful, improving system configurability and applicability.
### Global Threshold Configuration for Cluster Rate Limiting Rules
+ Related PR: [https://github.com/alibaba/higress/pull/2262](https://github.com/alibaba/higress/pull/2262)
+ Contributor: [hanxiantao](https://github.com/hanxiantao)
+ Change Log: Added support for global threshold configuration of cluster rate limiting rules, enhancing the flexibility and configurability of rate limiting strategies.
+ Feature Value: This PR adds global rate limiting threshold configuration to the cluster rate limiting plugin, allowing unified rate limiting thresholds for the entire custom rule set, enhancing the flexibility and applicability of rate limiting strategies.
### New OpenAI Files and Batches Interface Support
+ Related PR: [https://github.com/alibaba/higress/pull/2355](https://github.com/alibaba/higress/pull/2355)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added support for OpenAI and Qwen /v1/files and /v1/batches interfaces to the AI proxy module, extending AI service compatibility.
+ Feature Value: Added file and batch interface support, enhancing the AI proxy's compatibility with multiple services.
### New OpenAI Compatible Interface Mapping Capability
+ Related PR: [https://github.com/alibaba/higress/pull/2341](https://github.com/alibaba/higress/pull/2341)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added support for OpenAI-compatible image generation, image editing, and audio processing interfaces, extending the AI proxy's functionality and making it compatible with more models.
+ Feature Value: This PR adds OpenAI-compatible interface mapping capability to the AI proxy, enhancing system flexibility and expandability.
### New Access Log Request Plugin
+ Related PR: [https://github.com/alibaba/higress/pull/2265](https://github.com/alibaba/higress/pull/2265)
+ Contributor: [forgottener](https://github.com/forgottener)
+ Change Log: Added the ability to record request headers, request bodies, response headers, and response bodies in Higress access logs, enhancing log traceability.
+ Feature Value: This PR enhances Higress's logging functionality, allowing developers to more comprehensively monitor and debug HTTP communication processes.
### New dify ai-proxy e2e Testing
+ Related PR: [https://github.com/alibaba/higress/pull/2319](https://github.com/alibaba/higress/pull/2319)
+ Contributor: [VinciWu557](https://github.com/VinciWu557)
+ Change Log: Added dify ai-proxy plugin e2e testing, supporting full end-to-end testing of dify models to ensure their functionality and stability.
+ Feature Value: Adds complete e2e testing to the dify ai-proxy plugin, enhancing its reliability and maintainability.
### Frontend Gray Release Unique Identifier Configuration
+ Related PR: [https://github.com/alibaba/higress/pull/2371](https://github.com/alibaba/higress/pull/2371)
+ Contributor: [heimanba](https://github.com/heimanba)
+ Change Log: Added uniqueGrayTag configuration item detection, supporting the setting of unique identifier cookies based on user-defined uniqueGrayTag, enhancing gray release flexibility and configurability.
+ Feature Value: This PR enhances frontend gray release configuration, allowing users to define unique identifiers, optimizing gray traffic control mechanisms, and enhancing system scalability and user experience.
### New Doubao Image Generation Interface Support
+ Related PR: [https://github.com/alibaba/higress/pull/2331](https://github.com/alibaba/higress/pull/2331)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Added support for the Doubao image generation interface, extending the AI proxy's functionality to handle image generation requests.
+ Feature Value: This PR adds support for Doubao image generation to the AI proxy, enhancing system capabilities and flexibility.
### WasmPlugin E2E Testing Skip Building Higress Controller Image
+ Related PR: [https://github.com/alibaba/higress/pull/2264](https://github.com/alibaba/higress/pull/2264)
+ Contributor: [cr7258](https://github.com/cr7258)
+ Change Log: Added the ability to skip building the Higress controller development image during WasmPlugin E2E testing, enhancing testing efficiency.
+ Feature Value: This PR optimizes the WasmPlugin testing process, allowing users to selectively skip unnecessary image building steps, improving testing efficiency.
### MCP Server API Authentication Support
+ Related PR: [https://github.com/alibaba/higress/pull/2241](https://github.com/alibaba/higress/pull/2241)
+ Contributor: [johnlanni](https://github.com/johnlanni)
+ Change Log: This PR introduces comprehensive API authentication for the Higress MCP Server plugin, supporting HTTP Basic, HTTP Bearer, and API Key authentication via OAS3 security schemes, enhancing secure integration with backend REST APIs.
+ Feature Value: This PR adds support for multiple API authentication methods to the MCP Server, enhancing system security and flexibility, and significantly helping the community in building secure microservice architectures.
### GitHub Action for Synchronizing CRD Files
+ Related PR: [https://github.com/alibaba/higress/pull/2268](https://github.com/alibaba/higress/pull/2268)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: This PR adds a GitHub Action to automatically copy CRD definition files from the api folder to the helm folder on the main branch and create a PR.
+ Feature Value: Implements automated synchronization of CRD files, improving the efficiency and consistency of the development process.
### Enhanced Logging for ai-search Plugin
+ Related PR: [https://github.com/alibaba/higress/pull/2323](https://github.com/alibaba/higress/pull/2323)
+ Contributor: [johnlanni](https://github.com/johnlanni)
+ Change Log: Added detailed logging information to the ai-search plugin, including request URL, cluster name, and search rewrite model, aiding in debugging and monitoring.
+ Feature Value: Added more detailed log information, making it easier for developers to diagnose issues and optimize performance.
### Update CRD Files in Helm Folder
+ Related PR: [https://github.com/alibaba/higress/pull/2392](https://github.com/alibaba/higress/pull/2392)
+ Contributor: [github-actions[bot]](https://github.com/apps/github-actions)
+ Change Log: Updated the CRD files in the Helm folder, adding configuration support and metadata fields for MCP servers, enhancing the flexibility and extensibility of resource definitions.
+ Feature Value: Improved Kubernetes resource definitions, providing more comprehensive support for MCP server configurations.
### Add Upstream Operation Support to Wasm ABI
+ Related PR: [https://github.com/alibaba/higress/pull/2387](https://github.com/alibaba/higress/pull/2387)
+ Contributor: [johnlanni](https://github.com/johnlanni)
+ Change Log: This PR adds Wasm ABI related to upstream operations, preparing for future implementation of fine-grained load balancing strategies (e.g., GPU-based LLM scenarios) in Wasm plugins.
+ Feature Value: Lays the foundation for Wasm plugins to support more complex load balancing strategies, enhancing system flexibility and extensibility.
### Modify Log Level for key-auth Plugin
+ Related PR: [https://github.com/alibaba/higress/pull/2275](https://github.com/alibaba/higress/pull/2275)
+ Contributor: [lexburner](https://github.com/lexburner)
+ Change Log: Changed the log level in the key-auth plugin from WARN to DEBUG to reduce unnecessary warning messages and improve log readability and accuracy.
+ Feature Value: Fixed unnecessary warning logs in the key-auth plugin, optimizing log output and enhancing the clarity of system logs.
## 📌bugfix
### Fix WasmPlugin Generation Logic
+ Related PR: [https://github.com/alibaba/higress/pull/2237](https://github.com/alibaba/higress/pull/2237)
+ Contributor: [Erica177](https://github.com/Erica177)
+ Change Log: Fixed the issue of not setting the fail strategy in the WasmPlugin generation logic and added the FAIL_OPEN strategy to improve system stability.
+ Feature Value: Added a default fail strategy to WasmPlugin to prevent system anomalies due to plugin failures.
### Fix OpenAI Custom Path Pass-Through Issue
+ Related PR: [https://github.com/alibaba/higress/pull/2364](https://github.com/alibaba/higress/pull/2364)
+ Contributor: [daixijun](https://github.com/daixijun)
+ Change Log: Fixed the issue where an error occurred when passing unsupported API paths after configuring openaiCustomUrl, and added support for multiple OpenAI API paths.
+ Feature Value: This PR corrects the proxy service logic under custom path configuration, improving compatibility and stability.
### Fix Nacos MCP Tool Configuration Handling Logic
+ Related PR: [https://github.com/alibaba/higress/pull/2394](https://github.com/alibaba/higress/pull/2394)
+ Contributor: [Erica177](https://github.com/Erica177)
+ Change Log: Fixed the Nacos MCP tool configuration handling logic and added unit tests to ensure the stability and correctness of the configuration update and listening mechanism.
+ Feature Value: Improved the configuration handling logic of the MCP service, enhancing system stability and maintainability.
### Fix Mixed Line Break Handling in SSE Responses
+ Related PR: [https://github.com/alibaba/higress/pull/2344](https://github.com/alibaba/higress/pull/2344)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Fixed the issue of mixed line break handling in SSE responses, improving the SSE data parsing logic to ensure correct handling of different line break combinations.
+ Feature Value: This PR resolves the issue of incompatible line break handling in SSE responses, enhancing the system's compatibility and stability with SSE data.
### Fix proxy-wasm-cpp-sdk Dependency Issue
+ Related PR: [https://github.com/alibaba/higress/pull/2281](https://github.com/alibaba/higress/pull/2281)
+ Contributor: [johnlanni](https://github.com/johnlanni)
+ Change Log: Fixed the emsdk configuration issue in the proxy-wasm-cpp-sdk dependency, addressing the memory allocation failure when handling large request bodies.
+ Feature Value: Fixed a critical bug affecting request processing, enhancing system stability.
### Fix URL Encoding Issue for Model Names in Bedrock Requests
+ Related PR: [https://github.com/alibaba/higress/pull/2321](https://github.com/alibaba/higress/pull/2321)
+ Contributor: [HecarimV](https://github.com/HecarimV)
+ Change Log: Fixed the URL encoding issue for model names in Bedrock requests, preventing request failures due to special characters and removing redundant encoding functions.
+ Feature Value: Resolved the issue of request failures due to special characters in model names, enhancing system stability.
### Fix Error When Vector Provider is Not Configured
+ Related PR: [https://github.com/alibaba/higress/pull/2351](https://github.com/alibaba/higress/pull/2351)
+ Contributor: [mirror58229](https://github.com/mirror58229)
+ Change Log: Fixed the issue where 'EnableSemanticCachefalse' was incorrectly set when the vector provider was not configured, preventing errors in the 'handleResponse' function.
+ Feature Value: This PR fixed a bug that could cause error logs, enhancing system stability and user experience.
### Fix Nacos 3 MCP Server Rewrite Configuration Error
+ Related PR: [https://github.com/alibaba/higress/pull/2211](https://github.com/alibaba/higress/pull/2211)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Fixed the rewrite configuration error generated by the Nacos 3 MCP server, ensuring correct traffic routing.
+ Feature Value: Corrected the rewrite configuration of the MCP server to avoid service unavailability due to configuration errors.
### Fix Content-Length Request Header Issue in ai-search Plugin
+ Related PR: [https://github.com/alibaba/higress/pull/2363](https://github.com/alibaba/higress/pull/2363)
+ Contributor: [johnlanni](https://github.com/johnlanni)
+ Change Log: Fixed the issue where the Content-Length request header was not correctly removed in the ai-search plugin, ensuring the integrity of request header processing logic.
+ Feature Value: Fixed the issue of the Content-Length request header not being removed in the ai-search plugin, enhancing the plugin's stability and compatibility.
### Fix Authorization Header Issue in Gemini Proxy Requests
+ Related PR: [https://github.com/alibaba/higress/pull/2220](https://github.com/alibaba/higress/pull/2220)
+ Contributor: [hanxiantao](https://github.com/hanxiantao)
+ Change Log: Fixed the issue where the Authorization request header was incorrectly included in Gemini proxy requests, ensuring that the proxy requests meet Gemini API requirements.
+ Feature Value: Removed the Authorization header from Gemini proxy requests, resolving API call failures.
### Fix ToolArgs Struct Type Definition Issue
+ Related PR: [https://github.com/alibaba/higress/pull/2231](https://github.com/alibaba/higress/pull/2231)
+ Contributor: [Erica177](https://github.com/Erica177)
+ Change Log: Fixed issue #2222 by changing the Items field in the ToolArgs struct from []interface{} to interface{}, to accommodate specific use cases.
+ Feature Value: Fixed a type definition issue, enhancing code flexibility and compatibility.
## 📌refactor
### Refactor MCP Server Configuration Generation Logic
+ Related PR: [https://github.com/alibaba/higress/pull/2207](https://github.com/alibaba/higress/pull/2207)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Refactored the mcpServer.matchList configuration generation logic to support discovering mcp-sse type MCP servers from Nacos 3.x and fixed the ServiceKey issue in DestinationRules.
+ Feature Value: Improved MCP server configuration management, enhanced support for Nacos 3.x, and resolved routing issues for multiple MCP servers.
### Refactor MCP Server Auto-Discovery Logic
+ Related PR: [https://github.com/alibaba/higress/pull/2382](https://github.com/alibaba/higress/pull/2382)
+ Contributor: [Erica177](https://github.com/Erica177)
+ Change Log: Refactored the auto-discovery logic for MCP servers and fixed some issues, improving code maintainability and extensibility.
+ Feature Value: Enhanced the stability and extensibility of the system by refactoring and optimizing the auto-discovery logic for MCP servers, while also fixing some potential issues.
## 📌doc
### Optimize README.md Translation Process
+ Related PR: [https://github.com/alibaba/higress/pull/2208](https://github.com/alibaba/higress/pull/2208)
+ Contributor: [littlejiancc](https://github.com/littlejiancc)
+ Change Log: Optimized the translation process for README.md, supporting streaming transmission and avoiding duplicate PRs, enhancing the maintenance efficiency of multilingual documentation.
+ Feature Value: Improved the automated translation process to ensure document consistency and reduce manual intervention.
### Automated Translation Workflow
+ Related PR: [https://github.com/alibaba/higress/pull/2228](https://github.com/alibaba/higress/pull/2228)
+ Contributor: [MAVRICK-1](https://github.com/MAVRICK-1)
+ Change Log: This PR adds a GitHub Actions workflow for automatically translating non-English issues, PRs, and discussion content, enhancing the internationalization and accessibility of Higress.
+ Feature Value: Enhances the friendliness of Higress to international users and contributors through automated translation, strengthening the project's global reach.
# Higress Console
## 📌feature
### Support for Configuring Multiple Custom OpenAI LLM Provider Endpoints
+ Related PR: [https://github.com/higress-group/higress-console/pull/517](https://github.com/higress-group/higress-console/pull/517)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: This PR supports configuring multiple endpoints for custom OpenAI LLM providers, enhancing system flexibility and scalability. The LLM provider endpoint management logic was refactored to support IP+port format URLs and ensure all URLs have the same protocol and path.
+ Feature Value: This PR enables the system to support multiple custom OpenAI service endpoints, enhancing flexibility and reliability, suitable for multi-instance or load-balancing scenarios.
### Migration of Custom Image URL Patterns and Introduction of Wasm Plugin Service Configuration Class
+ Related PR: [https://github.com/higress-group/higress-console/pull/504](https://github.com/higress-group/higress-console/pull/504)
+ Contributor: [Thomas-Eliot](https://github.com/Thomas-Eliot)
+ Change Log: Migrated custom image URL patterns from the SDK module to the console module and introduced a Wasm plugin service configuration class to support more flexible Wasm plugin management.
+ Feature Value: This PR refactors the configuration management logic, enhancing the system's configurability and extensibility for Wasm plugins and laying the groundwork for future enhancements.
### New Configuration Parameter dependControllerApi
+ Related PR: [https://github.com/higress-group/higress-console/pull/506](https://github.com/higress-group/higress-console/pull/506)
+ Contributor: [Thomas-Eliot](https://github.com/Thomas-Eliot)
+ Change Log: Added a new configuration parameter dependControllerApi, supporting decoupling from the Higress Controller when not using a registry, enhancing architectural flexibility and configurability.
+ Feature Value: This PR introduces a new configuration option, allowing the system to bypass the registry and directly interact with the K8s API in specific scenarios, enhancing system flexibility and adaptability.
### Update Nacos3 Service Source Form to Support Nacos 3.0.1+
+ Related PR: [https://github.com/higress-group/higress-console/pull/521](https://github.com/higress-group/higress-console/pull/521)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Updated the Nacos3 service source form to support Nacos 3.0.1+ and fixed the issue where an error was displayed when creating a new source after deleting one.
+ Feature Value: This PR optimizes the service source configuration interface, enhancing support for Nacos 3.0.1+ and improving the user experience.
### Improve K8s Capability Initialization Logic
+ Related PR: [https://github.com/higress-group/higress-console/pull/513](https://github.com/higress-group/higress-console/pull/513)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Improved the K8s capability initialization logic by adding a retry mechanism and default support for Ingress V1 in case of failure, enhancing system stability and fault tolerance.
+ Feature Value: Fixed the unstable K8s capability detection issue, ensuring the console runs normally and improving the user experience.
### Support JDK 8
+ Related PR: [https://github.com/higress-group/higress-console/pull/497](https://github.com/higress-group/higress-console/pull/497)
+ Contributor: [Thomas-Eliot](https://github.com/Thomas-Eliot)
+ Change Log: Fixed compatibility issues caused by using Java 11 features, making the project compatible with JDK 8. Mainly modified the code using String.repeat() and List.of() methods.
+ Feature Value: This PR resolves the project's JDK 8 compatibility issues, allowing the project to run in a JDK 8 environment.
### Add Security Tips in Certificate Edit Form
+ Related PR: [https://github.com/higress-group/higress-console/pull/512](https://github.com/higress-group/higress-console/pull/512)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Added a security tip in the certificate edit form, clearly informing users that the current certificate and private key data will not be displayed and guiding them to directly enter new data.
+ Feature Value: Provides clearer operational guidance to users, enhancing data security awareness and preventing misoperations.
### Update Display Name for OpenAI Provider Type
+ Related PR: [https://github.com/higress-group/higress-console/pull/510](https://github.com/higress-group/higress-console/pull/510)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Updated the display name for the OpenAI provider type to more clearly indicate its compatibility, enhancing user recognition of the service.
+ Feature Value: Modified the display name of the OpenAI provider, making it easier for users to distinguish between service types and improving the user experience.
## 📌bugfix
### Fix Bug Where Case-Insensitive Path Matching Could Not Be Enabled in AI Routing
+ Related PR: [https://github.com/higress-group/higress-console/pull/508](https://github.com/higress-group/higress-console/pull/508)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Fixed the bug where case-insensitive path matching could not be enabled in AI routing by modifying the path predicate handling logic and adding a normalization function to ensure correct functionality.
+ Feature Value: Fixed the issue of case-insensitive path matching in AI routing configuration, enhancing the flexibility of routing rules and user experience.
### Fix Multiple Issues in higress-config Update Functionality
+ Related PR: [https://github.com/higress-group/higress-console/pull/509](https://github.com/higress-group/higress-console/pull/509)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Fixed multiple issues in the higress-config update functionality, including changing the HTTP method from POST to PUT, adding success prompt messages, and correcting method name spelling errors.
+ Feature Value: Fixed the API call method and prompt logic in the configuration update, enhancing the user experience and system stability.
### Fix Text Display Error in Frontend Pages
+ Related PR: [https://github.com/higress-group/higress-console/pull/503](https://github.com/higress-group/higress-console/pull/503)
+ Contributor: [CH3CHO](https://github.com/CH3CHO)
+ Change Log: Fixed a text display error in the frontend pages, correcting the incorrect text content to an accurate description.
+ Feature Value: Corrected the text content in the interface, enhancing the user's understanding and experience of the feature.
## 📌refactor
### Optimize Pagination Tool Logic
+ Related PR: [https://github.com/higress-group/higress-console/pull/499](https://github.com/higress-group/higress-console/pull/499)
+ Contributor: [Thomas-Eliot](https://github.com/Thomas-Eliot)
+ Change Log: Optimized the pagination tool logic by introducing more efficient collection processing and simplifying the code structure, enhancing the performance and maintainability of the pagination function.
+ Feature Value: Improved the implementation of the pagination tool, increasing data processing efficiency and code readability, positively impacting system performance.

View File

@@ -0,0 +1,303 @@
# Higress Core
## 📌feature
### 支持Google Cloud Vertex AI服务
+ 相关pr[https://github.com/alibaba/higress/pull/2119](https://github.com/alibaba/higress/pull/2119)
+ 贡献者:[HecarimV](https://github.com/HecarimV)
+ 改变记录新增对Google Cloud Vertex AI的支持允许通过OpenAI协议代理Vertex服务。
+ 功能价值该功能扩展了AI代理的兼容性使用户能够利用Vertex AI提供的模型和能力。
### 新增 HackMD MCP Server
+ 相关pr[https://github.com/alibaba/higress/pull/2260](https://github.com/alibaba/higress/pull/2260)
+ 贡献者:[Whitea029](https://github.com/Whitea029)
+ 改变记录:新增 HackMD MCP 服务器功能,支持用户通过 MCP 协议与 HackMD 平台交互,包括用户数据管理、笔记操作和团队协作功能。
+ 功能价值:该 PR 增加了对 HackMD 的支持,扩展了 MCP 服务器的功能,增强了用户的协作能力。
### 新增君润人力社保工具MCP Server
+ 相关pr[https://github.com/alibaba/higress/pull/2303](https://github.com/alibaba/higress/pull/2303)
+ 贡献者:[hourmoneys](https://github.com/hourmoneys)
+ 改变记录君润人力提交的社保工具MCP服务器的mcp to rest配置详细描述了其功能、使用方法和配置方式包括多个API接口的说明和示例。
+ 功能价值:为开发者提供了清晰的社保计算工具使用指南,有助于提升工具的可集成性和易用性。
### 添加 Claude 图片理解和 Tools 调用能力
+ 相关pr[https://github.com/alibaba/higress/pull/2385](https://github.com/alibaba/higress/pull/2385)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录为AI代理添加了Claude图片理解和工具调用功能支持流式输出和tokens统计兼容OpenAI接口规范并扩展了models接口支持。
+ 功能价值该PR增强了AI代理的功能使其能够处理图片输入和调用工具提升了与Claude的兼容性和用户体验。
### 新增Gemini模型支持
+ 相关pr[https://github.com/alibaba/higress/pull/2380](https://github.com/alibaba/higress/pull/2380)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录新增了对Gemini模型的支持包括模型列表接口、生图接口和对话文生图能力扩展了AI代理的功能范围。
+ 功能价值新增了Gemini模型的完整支持提升了AI代理的多模型兼容性和图像生成能力。
### 新增Amazon Bedrock图像生成支持
+ 相关pr[https://github.com/alibaba/higress/pull/2212](https://github.com/alibaba/higress/pull/2212)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录新增对Amazon Bedrock图像生成的支持扩展了AI代理的功能允许通过Bedrock API进行文本到图像的生成。
+ 功能价值为用户提供了一种新的AI图像生成方式增强了系统的功能和灵活性。
### 新增模型映射正则表达式支持
+ 相关pr[https://github.com/alibaba/higress/pull/2358](https://github.com/alibaba/higress/pull/2358)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录:新增了对模型映射的正则表达式支持,允许更灵活地进行模型名称替换,解决了特定场景下的模型调用问题。
+ 功能价值该PR增强了AI代理插件的功能使模型映射更加灵活和强大提高了系统的可配置性和适用性。
### 集群限流规则全局阈值配置
+ 相关pr[https://github.com/alibaba/higress/pull/2262](https://github.com/alibaba/higress/pull/2262)
+ 贡献者:[hanxiantao](https://github.com/hanxiantao)
+ 改变记录:新增了对集群限流规则的全局阈值配置支持,提升了限流策略的灵活性和可配置性。
+ 功能价值该PR为集群限流插件增加了全局限流阈值配置功能允许对整个自定义规则组设置统一的限流阈值增强了限流策略的灵活性和适用性。
### 新增OpenAI文件和批次接口支持
+ 相关pr[https://github.com/alibaba/higress/pull/2355](https://github.com/alibaba/higress/pull/2355)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录为AI代理模块添加了对OpenAI和Qwen的/v1/files与/v1/batches接口的支持扩展了AI服务的兼容性。
+ 功能价值新增文件和批次接口支持提升了AI代理对多种服务的兼容能力。
### 新增OpenAI兼容接口映射能力
+ 相关pr[https://github.com/alibaba/higress/pull/2341](https://github.com/alibaba/higress/pull/2341)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录新增对OpenAI兼容的图片生成、图片编辑和音频处理接口的支持扩展了AI代理的功能使其能够适配更多模型。
+ 功能价值该PR为AI代理增加了对OpenAI兼容接口的映射能力提升了系统灵活性和扩展性。
### 新增访问日志记录请求插件
+ 相关pr[https://github.com/alibaba/higress/pull/2265](https://github.com/alibaba/higress/pull/2265)
+ 贡献者:[forgottener](https://github.com/forgottener)
+ 改变记录新增功能支持在Higress访问日志中记录请求头、请求体、响应头和响应体信息提升日志可追溯性。
+ 功能价值该PR增强了Higress的日志功能使开发者能够更全面地监控和调试HTTP通信过程。
### 新增dify ai-proxy e2e测试
+ 相关pr[https://github.com/alibaba/higress/pull/2319](https://github.com/alibaba/higress/pull/2319)
+ 贡献者:[VinciWu557](https://github.com/VinciWu557)
+ 改变记录:新增 dify ai-proxy 插件 e2e 测试,支持对 dify 模型的完整端到端测试,确保其功能正确性和稳定性。
+ 功能价值:为 dify ai-proxy 插件添加了完整的 e2e 测试,提升了插件的可靠性和可维护性。
### 前端灰度发布唯一标识配置
+ 相关pr[https://github.com/alibaba/higress/pull/2371](https://github.com/alibaba/higress/pull/2371)
+ 贡献者:[heimanba](https://github.com/heimanba)
+ 改变记录新增uniqueGrayTag配置项检测功能支持根据用户自定义的uniqueGrayTag设置唯一标识cookie提升灰度发布灵活性和可配置性。
+ 功能价值该PR增强了前端灰度配置能力允许用户自定义唯一标识优化了灰度流量控制机制提升了系统的可扩展性和用户体验。
### 新增Doubao图像生成接口支持
+ 相关pr[https://github.com/alibaba/higress/pull/2331](https://github.com/alibaba/higress/pull/2331)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录新增对Doubao图像生成接口的支持扩展了AI代理的功能使其能够处理图像生成请求。
+ 功能价值该PR为AI代理添加了对Doubao图像生成功能的支持提升了系统的能力和灵活性。
### WasmPlugin E2E测试跳过构建Higress控制器镜像
+ 相关pr[https://github.com/alibaba/higress/pull/2264](https://github.com/alibaba/higress/pull/2264)
+ 贡献者:[cr7258](https://github.com/cr7258)
+ 改变记录新增了在运行WasmPlugin E2E测试时跳过构建Higress控制器开发镜像的功能提升测试效率。
+ 功能价值该PR优化了WasmPlugin测试流程允许用户选择性地跳过不必要的镜像构建步骤提高测试效率。
### MCP Server API认证支持
+ 相关pr[https://github.com/alibaba/higress/pull/2241](https://github.com/alibaba/higress/pull/2241)
+ 贡献者:[johnlanni](https://github.com/johnlanni)
+ 改变记录该PR为Higress MCP Server插件引入了全面的API认证功能支持通过OAS3的安全方案实现HTTP Basic、HTTP Bearer和API Key认证增强了与后端REST API的安全集成能力。
+ 功能价值该PR为MCP Server增加了对多种API认证方式的支持提升了系统安全性和灵活性对社区在构建安全微服务架构方面有显著帮助。
### GitHub Action同步CRD文件
+ 相关pr[https://github.com/alibaba/higress/pull/2268](https://github.com/alibaba/higress/pull/2268)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录该PR新增了一个GitHub Action用于在main分支上自动将CRD定义文件从api文件夹复制到helm文件夹并创建一个PR。
+ 功能价值实现了自动化同步CRD文件的功能提高了开发流程的效率和一致性。
### ai-search插件日志信息增强
+ 相关pr[https://github.com/alibaba/higress/pull/2323](https://github.com/alibaba/higress/pull/2323)
+ 贡献者:[johnlanni](https://github.com/johnlanni)
+ 改变记录为ai-search插件添加了详细的日志信息包括请求URL、集群名称和搜索重写模型有助于调试和监控。
+ 功能价值:增加了更详细的日志信息,便于开发人员排查问题并优化性能。
### 更新Helm文件夹中的CRD文件
+ 相关pr[https://github.com/alibaba/higress/pull/2392](https://github.com/alibaba/higress/pull/2392)
+ 贡献者:[github-actions[bot]](https://github.com/apps/github-actions)
+ 改变记录更新了Helm文件夹中的CRD文件增加了对MCP服务器的配置支持和元数据字段提升了资源定义的灵活性和扩展性。
+ 功能价值改进了Kubernetes资源定义为MCP服务器配置提供了更全面的支持。
### Wasm ABI添加上游操作支持
+ 相关pr[https://github.com/alibaba/higress/pull/2387](https://github.com/alibaba/higress/pull/2387)
+ 贡献者:[johnlanni](https://github.com/johnlanni)
+ 改变记录该PR添加了与上游操作相关的Wasm ABI为未来在Wasm插件中实现细粒度负载均衡策略如基于GPU的LLM场景做准备。
+ 功能价值为Wasm插件支持更复杂的负载均衡策略奠定了基础提升了系统灵活性和扩展性。
### key-auth插件日志级别修改
+ 相关pr[https://github.com/alibaba/higress/pull/2275](https://github.com/alibaba/higress/pull/2275)
+ 贡献者:[lexburner](https://github.com/lexburner)
+ 改变记录将key-auth插件中的日志级别从WARN修改为DEBUG以减少不必要的警告信息提高日志的可读性和准确性。
+ 功能价值修复了key-auth插件中不必要的警告日志优化了日志输出提升了系统日志的清晰度。
## 📌bugfix
### WasmPlugin生成逻辑修复
+ 相关pr[https://github.com/alibaba/higress/pull/2237](https://github.com/alibaba/higress/pull/2237)
+ 贡献者:[Erica177](https://github.com/Erica177)
+ 改变记录修复了WasmPlugin生成逻辑中未设置fail strategy的问题新增了FAIL_OPEN策略以提高系统稳定性。
+ 功能价值为WasmPlugin添加了默认的fail strategy避免因插件故障导致系统异常。
### 修复OpenAI自定义路径透传问题
+ 相关pr[https://github.com/alibaba/higress/pull/2364](https://github.com/alibaba/higress/pull/2364)
+ 贡献者:[daixijun](https://github.com/daixijun)
+ 改变记录:修复了配置 openaiCustomUrl 后,对不支持的 API 路径透传时出现错误的问题,新增了对多个 OpenAI API 路径的支持。
+ 功能价值:该 PR 修正了代理服务在自定义路径配置下的逻辑问题,提高了兼容性和稳定性。
### 修复Nacos MCP工具配置处理逻辑
+ 相关pr[https://github.com/alibaba/higress/pull/2394](https://github.com/alibaba/higress/pull/2394)
+ 贡献者:[Erica177](https://github.com/Erica177)
+ 改变记录修复了Nacos MCP工具配置处理逻辑并添加了单元测试确保配置更新和监听机制的稳定性与正确性。
+ 功能价值改进了MCP服务的配置处理逻辑提高了系统的稳定性和可维护性。
### 修复SSE响应中混合换行符处理
+ 相关pr[https://github.com/alibaba/higress/pull/2344](https://github.com/alibaba/higress/pull/2344)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录修复了SSE响应中混合换行符的处理问题改进了SSE数据解析逻辑确保支持不同换行符组合的正确处理。
+ 功能价值该PR解决了SSE响应中换行符处理不兼容的问题提升了系统对SSE数据的兼容性和稳定性。
### 修复proxy-wasm-cpp-sdk依赖问题
+ 相关pr[https://github.com/alibaba/higress/pull/2281](https://github.com/alibaba/higress/pull/2281)
+ 贡献者:[johnlanni](https://github.com/johnlanni)
+ 改变记录:修复了 proxy-wasm-cpp-sdk 依赖的 emsdk 配置问题,解决了处理大请求体时内存分配失败的问题。
+ 功能价值:修复了影响请求处理的严重 Bug提升了系统稳定性。
### 修复Bedrock请求中模型名称URL编码问题
+ 相关pr[https://github.com/alibaba/higress/pull/2321](https://github.com/alibaba/higress/pull/2321)
+ 贡献者:[HecarimV](https://github.com/HecarimV)
+ 改变记录修复Bedrock请求中模型名称的URL编码问题避免特殊字符导致的请求失败并移除了冗余的编码函数。
+ 功能价值:解决了模型名称在请求中因特殊字符导致的问题,提升系统稳定性。
### 修复未配置向量提供程序时的错误
+ 相关pr[https://github.com/alibaba/higress/pull/2351](https://github.com/alibaba/higress/pull/2351)
+ 贡献者:[mirror58229](https://github.com/mirror58229)
+ 改变记录:修复了在未配置向量提供程序时,'EnableSemanticCachefalse' 被错误设置的问题,避免了在 'handleResponse' 中出现错误日志。
+ 功能价值该PR修复了一个可能导致错误日志的Bug提升了系统的稳定性和用户体验。
### 修复Nacos 3 MCP服务器重写配置错误
+ 相关pr[https://github.com/alibaba/higress/pull/2211](https://github.com/alibaba/higress/pull/2211)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录修复了Nacos 3 MCP服务器生成的重写配置错误问题确保流量路由正确。
+ 功能价值修正了MCP服务器的重写配置避免因配置错误导致的服务不可用问题。
### 修复ai-search插件Content-Length请求头问题
+ 相关pr[https://github.com/alibaba/higress/pull/2363](https://github.com/alibaba/higress/pull/2363)
+ 贡献者:[johnlanni](https://github.com/johnlanni)
+ 改变记录修复了ai-search插件中未正确移除Content-Length请求头的问题确保请求头处理逻辑的完整性。
+ 功能价值修复了ai-search插件中Content-Length请求头未被移除的问题提升了插件的稳定性和兼容性。
### 修复Gemini代理请求中的Authorization头问题
+ 相关pr[https://github.com/alibaba/higress/pull/2220](https://github.com/alibaba/higress/pull/2220)
+ 贡献者:[hanxiantao](https://github.com/hanxiantao)
+ 改变记录修复了AI代理Gemini时错误携带Authorization请求头的问题确保代理请求符合Gemini API的要求。
+ 功能价值移除了Gemini代理请求中的Authorization头解决了API调用失败的问题。
### 修复ToolArgs结构体类型定义问题
+ 相关pr[https://github.com/alibaba/higress/pull/2231](https://github.com/alibaba/higress/pull/2231)
+ 贡献者:[Erica177](https://github.com/Erica177)
+ 改变记录修复了issue #2222将ToolArgs结构体中的Items字段从[]interface{}改为interface{},以适配特定的使用场景。
+ 功能价值:修复了一个类型定义问题,提高了代码的灵活性和兼容性。
## 📌refactor
### MCP服务器配置生成逻辑重构
+ 相关pr[https://github.com/alibaba/higress/pull/2207](https://github.com/alibaba/higress/pull/2207)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录重构了mcpServer.matchList配置生成逻辑支持从Nacos 3.x发现mcp-sse类型的MCP服务器并修复了DestinationRules的ServiceKey问题。
+ 功能价值改进了MCP服务器的配置管理增强了对Nacos 3.x的支持并解决了多MCP服务器的路由问题。
### MCP服务器自动发现逻辑重构
+ 相关pr[https://github.com/alibaba/higress/pull/2382](https://github.com/alibaba/higress/pull/2382)
+ 贡献者:[Erica177](https://github.com/Erica177)
+ 改变记录:重构了 MCP 服务器的自动发现逻辑,并修复了一些问题,提高了代码的可维护性和扩展性。
+ 功能价值:通过重构和优化 MCP 服务器的自动发现逻辑,提升了系统的稳定性和可扩展性,同时修复了一些潜在的问题。
## 📌doc
### 优化README.md翻译流程
+ 相关pr[https://github.com/alibaba/higress/pull/2208](https://github.com/alibaba/higress/pull/2208)
+ 贡献者:[littlejiancc](https://github.com/littlejiancc)
+ 改变记录优化了README.md的翻译流程支持流式传输并避免重复PR提升了多语言文档的维护效率。
+ 功能价值:改进了自动化翻译流程,确保文档一致性并减少人工干预。
### 自动化翻译工作流
+ 相关pr[https://github.com/alibaba/higress/pull/2228](https://github.com/alibaba/higress/pull/2228)
+ 贡献者:[MAVRICK-1](https://github.com/MAVRICK-1)
+ 改变记录该PR添加了一个GitHub Actions工作流用于自动翻译非英文的issue、PR和讨论内容提高Higress的国际化和可访问性。
+ 功能价值通过自动化翻译提升Higress对国际用户和贡献者的友好度增强项目全球化能力。
# Higress Console
## 📌feature
### 支持配置多个自定义OpenAI LLM提供者端点
+ 相关pr[https://github.com/higress-group/higress-console/pull/517](https://github.com/higress-group/higress-console/pull/517)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录该PR为自定义OpenAI LLM提供者支持配置多个端点增强了系统的灵活性和可扩展性。通过重构LLM提供者端点管理逻辑实现了对IP+端口格式的URL的支持并确保所有URL具有相同的协议和路径。
+ 功能价值该PR使系统能够支持多个自定义OpenAI服务端点提升了系统的灵活性和可靠性适用于需要多实例或负载均衡的场景。
### 自定义图片URL模式迁移与Wasm插件服务配置类引入
+ 相关pr[https://github.com/higress-group/higress-console/pull/504](https://github.com/higress-group/higress-console/pull/504)
+ 贡献者:[Thomas-Eliot](https://github.com/Thomas-Eliot)
+ 改变记录将自定义图片URL模式从SDK模块迁移到控制台模块并引入Wasm插件服务配置类以支持更灵活的Wasm插件管理。
+ 功能价值该PR重构了配置管理逻辑提升了系统对Wasm插件的可配置性和扩展性为后续功能增强打下基础。
### 新增配置参数dependControllerApi
+ 相关pr[https://github.com/higress-group/higress-console/pull/506](https://github.com/higress-group/higress-console/pull/506)
+ 贡献者:[Thomas-Eliot](https://github.com/Thomas-Eliot)
+ 改变记录新增配置参数dependControllerApi支持在不使用注册中心时解耦对Higress Controller的依赖提升架构灵活性和可配置性。
+ 功能价值该PR通过引入新配置项使系统在特定场景下可以绕过注册中心直接与K8s API交互增强了系统的灵活性和适应性。
### 更新Nacos3服务源表单以支持Nacos 3.0.1+
+ 相关pr[https://github.com/higress-group/higress-console/pull/521](https://github.com/higress-group/higress-console/pull/521)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录该PR更新了nacos3服务源的表单以支持nacos 3.0.1+版本,并修复了删除服务源后创建新源时显示错误的问题。
+ 功能价值该PR优化了服务源配置界面提升了对nacos 3.0.1+版本的支持,同时改善用户体验。
### 改进K8s能力初始化逻辑
+ 相关pr[https://github.com/higress-group/higress-console/pull/513](https://github.com/higress-group/higress-console/pull/513)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录改进了K8s能力初始化逻辑增加了重试机制和失败后默认支持Ingress V1的处理提升了系统稳定性和容错性。
+ 功能价值修复了K8s能力检测不稳定的问题确保控制台正常运行提升用户体验。
### 支持JDK 8
+ 相关pr[https://github.com/higress-group/higress-console/pull/497](https://github.com/higress-group/higress-console/pull/497)
+ 贡献者:[Thomas-Eliot](https://github.com/Thomas-Eliot)
+ 改变记录修复了代码中使用Java 11特性导致的兼容性问题使其支持JDK 8。主要修改了代码中使用String.repeat()方法和List.of()等Java 11特性的部分。
+ 功能价值该PR解决了项目对JDK 8的兼容性问题使项目可以在JDK 8环境中正常运行。
### 在证书编辑表单中添加安全提示信息
+ 相关pr[https://github.com/higress-group/higress-console/pull/512](https://github.com/higress-group/higress-console/pull/512)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录:在证书编辑表单中添加了一条安全提示信息,明确告知用户当前证书和私钥数据不会显示,并指导用户直接输入新数据。
+ 功能价值:为用户提供更清晰的操作指引,提升数据安全性意识,避免误操作。
### 更新OpenAI提供者类型的显示名称
+ 相关pr[https://github.com/higress-group/higress-console/pull/510](https://github.com/higress-group/higress-console/pull/510)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录更新了OpenAI提供者类型的显示名称使其更明确地表明其兼容性提升用户对服务的识别度。
+ 功能价值修改了OpenAI提供者的显示名称使用户能更清楚地区分服务类型提升使用体验。
## 📌bugfix
### 修复AI路由中无法启用路径大小写忽略匹配的Bug
+ 相关pr[https://github.com/higress-group/higress-console/pull/508](https://github.com/higress-group/higress-console/pull/508)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录修复了AI路由中无法启用路径大小写忽略匹配的Bug通过修改路径谓词的处理逻辑并新增规范化函数确保功能正确性。
+ 功能价值修复了AI路由配置中路径大小写匹配的问题提升了路由规则的灵活性和用户体验。
### 修复higress-config更新功能中的多个问题
+ 相关pr[https://github.com/higress-group/higress-console/pull/509](https://github.com/higress-group/higress-console/pull/509)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录修复了higress-config更新功能中的多个问题包括将HTTP方法从POST改为PUT、添加成功提示信息以及修复方法名拼写错误。
+ 功能价值修复了配置更新的API调用方式和提示逻辑提升了用户体验和系统稳定性。
### 修复前端页面中的文本显示错误
+ 相关pr[https://github.com/higress-group/higress-console/pull/503](https://github.com/higress-group/higress-console/pull/503)
+ 贡献者:[CH3CHO](https://github.com/CH3CHO)
+ 改变记录:修复了前端页面中一个文本显示错误的问题,将原本不正确的文本内容更正为准确的描述。
+ 功能价值:修正了界面中文本内容,提升了用户对功能的理解和使用体验。
## 📌refactor
### 优化分页工具逻辑
+ 相关pr[https://github.com/higress-group/higress-console/pull/499](https://github.com/higress-group/higress-console/pull/499)
+ 贡献者:[Thomas-Eliot](https://github.com/Thomas-Eliot)
+ 改变记录:优化了分页工具的逻辑,通过引入更高效的集合处理方式和简化代码结构,提升了分页功能的性能和可维护性。
+ 功能价值:优化了分页工具的实现方式,提高了数据处理效率和代码可读性,对系统性能有积极影响。

View File

@@ -55,7 +55,7 @@ var WasmPluginsAiProxy = suite.ConformanceTest{
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"360gpt-turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -77,19 +77,19 @@ var WasmPluginsAiProxy = suite.ConformanceTest{
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"360gpt-turbo","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -115,7 +115,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -137,19 +137,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"baichuan2-13b-chat-v1","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -175,7 +175,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -197,19 +197,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"ernie-3.5-8k","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -235,7 +235,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"deepseek-reasoner","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -257,19 +257,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"deepseek-reasoner","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -295,7 +295,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -317,19 +317,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"fake_doubao_endpoint","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -355,7 +355,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -377,19 +377,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"cohere-command-r-08-2024","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -415,7 +415,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"llama3-8b-8192","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -437,19 +437,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"llama3-8b-8192","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -475,7 +475,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -497,19 +497,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -535,7 +535,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -557,21 +557,21 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
Body: []byte(`data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你"},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"好"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"好"},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":""}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":""},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你"},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"是"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"是"},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"谁"}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"谁"},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":""}}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":""},"finish_reason":"","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"name":"MM智能助理","role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"abab6.5s-chat","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}
`),
},
@@ -595,7 +595,7 @@ data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"name":"MM智
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"mistral-tiny","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -617,19 +617,19 @@ data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"name":"MM智
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"mistral-tiny","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -655,7 +655,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"qwen-turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -677,19 +677,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"qwen-turbo","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -717,7 +717,7 @@ data: [DONE]
ContentType: http.ContentTypeApplicationJson,
// Since the "created" field is generated by the ai-proxy plugin based on the current timestamp, it is ignored during comparison
JsonBodyIgnoreFields: []string{"created"},
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":1738218357,"model":"qwen-turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":1738218357,"model":"qwen-turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -739,7 +739,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"step-1-8k","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -761,19 +761,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"step-1-8k","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -799,7 +799,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -821,19 +821,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"meta-llama/Meta-Llama-3-8B-Instruct-Turbo","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -859,7 +859,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"Yi-Medium","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -881,19 +881,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"Yi-Medium","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -919,7 +919,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop"}],"created":10,"model":"glm-4-plus","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"你好,你是谁?"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -941,19 +941,19 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":null,"logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"是"},"finish_reason":null,"logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":"谁"},"finish_reason":null,"logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""}}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop","logprobs":null}],"created":10,"model":"glm-4-plus","object":"chat.completion.chunk","usage":null}
data: [DONE]
@@ -979,7 +979,7 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeApplicationJson,
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"USER: \n你好\n"},"finish_reason":"stop"}],"created":10,"model":"dify","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
Body: []byte(`{"id":"chatcmpl-llm-mock","choices":[{"index":0,"message":{"role":"assistant","content":"USER: \n你好\n"},"finish_reason":"stop","logprobs":null}],"created":10,"model":"dify","object":"chat.completion","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}`),
},
},
},
@@ -1001,27 +1001,27 @@ data: [DONE]
ExpectedResponse: http.Response{
StatusCode: 200,
ContentType: http.ContentTypeTextEventStream,
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"U"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
Body: []byte(`data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"U"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"S"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"S"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"E"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"E"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"R"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"R"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":":"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":":"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":" "}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":" "},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"你"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"你"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"好"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"好"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"}}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":{}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"\n"},"finish_reason":null,"logprobs":null}],"created":10,"model":"dify","object":"chat.completion.chunk","usage":null}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"USER: \n你好\n"},"finish_reason":"stop"}],"model":"dify","object":"chat.completion.chunk","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}
data: {"id":"chatcmpl-llm-mock","choices":[{"index":0,"delta":{"role":"assistant","content":"USER: \n你好\n"},"finish_reason":"stop","logprobs":null}],"model":"dify","object":"chat.completion.chunk","usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10}}
`),
},