docs: 添加使用示范和启动指南
- examples/demo_usage.py: Python 使用示范(openai SDK + requests) - examples/demo_usage.go: Go 使用示范(net/http) - run.md: 完整启动指南(前置条件、3种启动方式、API Key 配置、验证命令)
This commit is contained in:
@@ -0,0 +1,141 @@
|
|||||||
|
// Edge AI Gateway 使用示范 (Go 版本)
|
||||||
|
//
|
||||||
|
// 演示 Go 应用如何调用 Edge AI Gateway 的 OpenAI 兼容 API。
|
||||||
|
//
|
||||||
|
// 运行:go run examples/demo_usage.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BaseURL = "http://localhost:8080"
|
||||||
|
APIKey = "test-key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// === 1. 非流式对话 ===
|
||||||
|
fmt.Println("=== 非流式对话 ===")
|
||||||
|
resp := doPost(BaseURL+"/v1/chat/completions", map[string]any{
|
||||||
|
"model": "general-chat",
|
||||||
|
"messages": []map[string]string{{"role": "user", "content": "你好,介绍一下你自己"}},
|
||||||
|
"stream": false,
|
||||||
|
})
|
||||||
|
fmt.Printf("回复: %s\n", resp["choices"].([]any)[0].(map[string]any)["message"].(map[string]any)["content"])
|
||||||
|
fmt.Printf("模型: %s\n", resp["actual_model"])
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// === 2. 流式对话 (SSE) ===
|
||||||
|
fmt.Println("=== 流式对话 ===")
|
||||||
|
doStream(BaseURL+"/v1/chat/completions", map[string]any{
|
||||||
|
"model": "fast-chat",
|
||||||
|
"messages": []map[string]string{{"role": "user", "content": "写一个Go hello world"}},
|
||||||
|
"stream": true,
|
||||||
|
})
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// === 3. 查看模型列表 ===
|
||||||
|
fmt.Println("=== 可用模型 ===")
|
||||||
|
resp2 := doGet(BaseURL + "/v1/models")
|
||||||
|
for _, m := range resp2["data"].([]any) {
|
||||||
|
fmt.Printf(" - %s\n", m.(map[string]any)["id"])
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// === 4. 会话管理 ===
|
||||||
|
fmt.Println("=== 会话管理 ===")
|
||||||
|
sess := doPost(BaseURL+"/v1/sessions", map[string]any{
|
||||||
|
"application_id": "demo-app",
|
||||||
|
"user_id": "user1",
|
||||||
|
})
|
||||||
|
sessionID := sess["session_id"].(string)
|
||||||
|
fmt.Printf("创建会话: %s\n\n", sessionID)
|
||||||
|
|
||||||
|
// === 5. 错误处理 ===
|
||||||
|
fmt.Println("=== 错误处理 ===")
|
||||||
|
// 无认证
|
||||||
|
resp3, _ := http.Get(BaseURL + "/v1/models")
|
||||||
|
fmt.Printf("无认证: HTTP %d\n", resp3.StatusCode)
|
||||||
|
resp3.Body.Close()
|
||||||
|
// 缺少 model
|
||||||
|
resp4 := doPost(BaseURL+"/v1/chat/completions", map[string]any{
|
||||||
|
"messages": []map[string]string{{"role": "user", "content": "hi"}},
|
||||||
|
})
|
||||||
|
if err, ok := resp4["error"]; ok {
|
||||||
|
fmt.Printf("缺少model: %s\n", err.(map[string]any)["code"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\n✅ 示范完成!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPost(url string, body map[string]any) map[string]any {
|
||||||
|
b, _ := json.Marshal(body)
|
||||||
|
req, _ := http.NewRequest("POST", url, strings.NewReader(string(b)))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+APIKey)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("请求失败: %v\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, _ := io.ReadAll(resp.Body)
|
||||||
|
var result map[string]any
|
||||||
|
json.Unmarshal(data, &result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func doGet(url string) map[string]any {
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+APIKey)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("请求失败: %v\n", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, _ := io.ReadAll(resp.Body)
|
||||||
|
var result map[string]any
|
||||||
|
json.Unmarshal(data, &result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func doStream(url string, body map[string]any) {
|
||||||
|
b, _ := json.Marshal(body)
|
||||||
|
req, _ := http.NewRequest("POST", url, strings.NewReader(string(b)))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+APIKey)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("请求失败: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if !strings.HasPrefix(line, "data: ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data := strings.TrimPrefix(line, "data: ")
|
||||||
|
if data == "[DONE]" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var chunk map[string]any
|
||||||
|
json.Unmarshal([]byte(data), &chunk)
|
||||||
|
choices := chunk["choices"].([]any)
|
||||||
|
delta := choices[0].(map[string]any)["delta"].(map[string]any)
|
||||||
|
if content, ok := delta["content"]; ok {
|
||||||
|
fmt.Print(content.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
fmt.Printf("\nstream error: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Edge AI Gateway 使用示范
|
||||||
|
|
||||||
|
本文件演示其他应用如何通过 OpenAI 兼容 API 调用 Edge AI Gateway。
|
||||||
|
支持两种方式:
|
||||||
|
1. 使用 openai SDK(推荐)
|
||||||
|
2. 使用 requests 库直接调用
|
||||||
|
|
||||||
|
运行前请确保:
|
||||||
|
- Edge AI Gateway 已启动 (默认 http://localhost:8080)
|
||||||
|
- 已配置有效的 API Key
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 方式一:使用 openai SDK(推荐)
|
||||||
|
# ============================================================
|
||||||
|
# pip install openai
|
||||||
|
|
||||||
|
from openai import OpenAI
|
||||||
|
|
||||||
|
client = OpenAI(
|
||||||
|
base_url="http://localhost:8080/v1",
|
||||||
|
api_key="test-key", # 替换为你的 API Key
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- 非流式对话 ---
|
||||||
|
print("=== 非流式对话 ===")
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="general-chat", # 逻辑模型名,网关自动路由到实际模型
|
||||||
|
messages=[
|
||||||
|
{"role": "system", "content": "你是一个简洁的助手"},
|
||||||
|
{"role": "user", "content": "你好,介绍一下你自己"},
|
||||||
|
],
|
||||||
|
stream=False,
|
||||||
|
)
|
||||||
|
print(f"回复: {response.choices[0].message.content}")
|
||||||
|
print(f"Token: 输入={response.usage.prompt_tokens}, 输出={response.usage.completion_tokens}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# --- 流式对话 (SSE) ---
|
||||||
|
print("=== 流式对话 ===")
|
||||||
|
stream = client.chat.completions.create(
|
||||||
|
model="fast-chat",
|
||||||
|
messages=[
|
||||||
|
{"role": "user", "content": "写一个 Python 的 hello world"},
|
||||||
|
],
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
for chunk in stream:
|
||||||
|
delta = chunk.choices[0].delta.content
|
||||||
|
if delta:
|
||||||
|
print(delta, end="", flush=True)
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
# --- 带优先级的请求 (P0 最高) ---
|
||||||
|
print("=== 高优先级请求 ===")
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="general-chat",
|
||||||
|
messages=[{"role": "user", "content": "1+1=?"}],
|
||||||
|
extra_headers={"X-Priority": "P0"}, # 通过 header 传递优先级
|
||||||
|
)
|
||||||
|
print(f"回复: {response.choices[0].message.content}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# --- 查看可用模型 ---
|
||||||
|
print("=== 可用模型 ===")
|
||||||
|
models = client.models.list()
|
||||||
|
for m in models.data:
|
||||||
|
print(f" - {m.id}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 方式二:使用 requests 库直接调用
|
||||||
|
# ============================================================
|
||||||
|
# pip install requests
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:8080"
|
||||||
|
API_KEY = "test-key"
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bearer {API_KEY}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 非流式 ---
|
||||||
|
print("=== requests: 非流式 ===")
|
||||||
|
resp = requests.post(
|
||||||
|
f"{BASE_URL}/v1/chat/completions",
|
||||||
|
headers=HEADERS,
|
||||||
|
json={
|
||||||
|
"model": "general-chat",
|
||||||
|
"messages": [{"role": "user", "content": "你好"}],
|
||||||
|
"stream": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
data = resp.json()
|
||||||
|
print(f"状态: {data.get('status')}")
|
||||||
|
print(f"回复: {data['choices'][0]['message']['content']}")
|
||||||
|
print(f"模型: {data.get('actual_model')}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# --- 流式 (SSE) ---
|
||||||
|
print("=== requests: 流式 ===")
|
||||||
|
resp = requests.post(
|
||||||
|
f"{BASE_URL}/v1/chat/completions",
|
||||||
|
headers=HEADERS,
|
||||||
|
json={
|
||||||
|
"model": "fast-chat",
|
||||||
|
"messages": [{"role": "user", "content": "讲个笑话"}],
|
||||||
|
"stream": True,
|
||||||
|
},
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
for line in resp.iter_lines():
|
||||||
|
if line:
|
||||||
|
line = line.decode("utf-8")
|
||||||
|
if line.startswith("data: ") and line != "data: [DONE]":
|
||||||
|
import json
|
||||||
|
chunk = json.loads(line[6:])
|
||||||
|
delta = chunk["choices"][0]["delta"].get("content", "")
|
||||||
|
if delta:
|
||||||
|
print(delta, end="", flush=True)
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
# --- 会话管理 ---
|
||||||
|
print("=== 会话管理 ===")
|
||||||
|
# 创建会话
|
||||||
|
resp = requests.post(
|
||||||
|
f"{BASE_URL}/v1/sessions",
|
||||||
|
headers=HEADERS,
|
||||||
|
json={"application_id": "my-app", "user_id": "user123"},
|
||||||
|
)
|
||||||
|
session = resp.json()
|
||||||
|
session_id = session["session_id"]
|
||||||
|
print(f"创建会话: {session_id}")
|
||||||
|
|
||||||
|
# 查询会话
|
||||||
|
resp = requests.get(
|
||||||
|
f"{BASE_URL}/v1/sessions/{session_id}",
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
print(f"会话信息: {resp.json()}")
|
||||||
|
|
||||||
|
# 删除会话
|
||||||
|
resp = requests.delete(
|
||||||
|
f"{BASE_URL}/v1/sessions/{session_id}",
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
|
print(f"删除会话: HTTP {resp.status_code}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# --- 错误处理 ---
|
||||||
|
print("=== 错误处理 ===")
|
||||||
|
# 无认证
|
||||||
|
resp = requests.get(f"{BASE_URL}/v1/models")
|
||||||
|
print(f"无认证: {resp.status_code} - {resp.json()['error']['code']}")
|
||||||
|
|
||||||
|
# 无效 key
|
||||||
|
resp = requests.get(
|
||||||
|
f"{BASE_URL}/v1/models",
|
||||||
|
headers={"Authorization": "Bearer invalid-key"},
|
||||||
|
)
|
||||||
|
print(f"无效key: {resp.status_code} - {resp.json()['error']['code']}")
|
||||||
|
|
||||||
|
# 缺少 model
|
||||||
|
resp = requests.post(
|
||||||
|
f"{BASE_URL}/v1/chat/completions",
|
||||||
|
headers=HEADERS,
|
||||||
|
json={"messages": [{"role": "user", "content": "hi"}]},
|
||||||
|
)
|
||||||
|
print(f"缺少model: {resp.status_code} - {resp.json()['error']['code']}")
|
||||||
|
|
||||||
|
# 未知 model
|
||||||
|
resp = requests.post(
|
||||||
|
f"{BASE_URL}/v1/chat/completions",
|
||||||
|
headers=HEADERS,
|
||||||
|
json={"model": "nonexistent", "messages": [{"role": "user", "content": "hi"}]},
|
||||||
|
)
|
||||||
|
print(f"未知model: {resp.status_code} - {resp.json()['error']['code']}")
|
||||||
|
|
||||||
|
print("\n✅ 示范完成!")
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# Edge AI Gateway 启动指南
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
### 1. 安装 Go
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install go
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
wget https://go.dev/dl/go1.23.0.linux-amd64.tar.gz
|
||||||
|
sudo tar -C /usr/local -xzf go1.23.0.linux-amd64.tar.gz
|
||||||
|
export PATH=$PATH:/usr/local/go/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 安装 Ollama(本地推理引擎)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install ollama
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
curl -fsSL https://ollama.com/install.sh | sh
|
||||||
|
|
||||||
|
# 拉取模型
|
||||||
|
ollama pull deepseek-r1:1.5b
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 安装 SQLite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS(自带)
|
||||||
|
# Linux
|
||||||
|
sudo apt install sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 启动方式
|
||||||
|
|
||||||
|
### 方式一:直接运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 创建数据目录
|
||||||
|
mkdir -p /tmp/edgeai-data
|
||||||
|
|
||||||
|
# 2. 启动 Ollama(另一个终端)
|
||||||
|
ollama serve
|
||||||
|
|
||||||
|
# 3. 启动 Edge AI Gateway
|
||||||
|
EDGEAI_DB_PATH=/tmp/edgeai-data go run ./cmd/gateway/ --config configs/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
服务器将在 `http://0.0.0.0:8080` 启动。
|
||||||
|
|
||||||
|
### 方式二:编译后运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 编译
|
||||||
|
go build -o bin/gateway ./cmd/gateway/
|
||||||
|
|
||||||
|
# 2. 启动
|
||||||
|
EDGEAI_DB_PATH=/tmp/edgeai-data ./bin/gateway --config configs/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式三:Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deploy
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
服务端口:
|
||||||
|
- Gateway: http://localhost:8080
|
||||||
|
- Ollama: http://localhost:11434
|
||||||
|
- Prometheus: http://localhost:9090
|
||||||
|
- Grafana: http://localhost:3000
|
||||||
|
|
||||||
|
## 配置 API Key
|
||||||
|
|
||||||
|
首次启动后需要创建 API Key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 计算 key 的 SHA-256 哈希
|
||||||
|
HASH=$(echo -n "your-api-key" | shasum -a 256 | awk '{print $1}')
|
||||||
|
|
||||||
|
# 2. 插入数据库
|
||||||
|
sqlite3 /tmp/edgeai-data/auth.db "INSERT INTO api_keys (app_id, tenant_id, name, key_hash, allowed_models, allowed_priorities, is_admin, enabled) VALUES ('my-app', 'my-tenant', 'my-key', '$HASH', '[]', '[]', 1, 1);"
|
||||||
|
|
||||||
|
# 3. 重启服务器使 key 生效
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
| 变量 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `EDGEAI_DB_PATH` | `/var/lib/edgeai` | 数据库目录 |
|
||||||
|
| `EDGEAI_SERVER_PORT` | `8080` | 服务端口 |
|
||||||
|
| `EDGEAI_LOG_LEVEL` | `info` | 日志级别 |
|
||||||
|
| `EDGEAI_CONFIG_PATH` | `configs/config.yaml` | 配置文件路径 |
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 健康检查
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
|
||||||
|
# 查看模型(需 API Key)
|
||||||
|
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/v1/models
|
||||||
|
|
||||||
|
# 非流式对话
|
||||||
|
curl -X POST http://localhost:8080/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer your-api-key" \
|
||||||
|
-d '{"model":"general-chat","messages":[{"role":"user","content":"你好"}]}'
|
||||||
|
|
||||||
|
# 流式对话
|
||||||
|
curl -N -X POST http://localhost:8080/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer your-api-key" \
|
||||||
|
-d '{"model":"fast-chat","messages":[{"role":"user","content":"你好"}],"stream":true}'
|
||||||
|
|
||||||
|
# Prometheus 指标
|
||||||
|
curl http://localhost:8080/metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可选:启动 vLLM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 vLLM
|
||||||
|
pip install vllm
|
||||||
|
|
||||||
|
# 启动 vLLM 服务(端口 8000)
|
||||||
|
vllm serve --model deepseek-ai/deepseek-r1-1.5b --port 8000
|
||||||
|
|
||||||
|
# 然后在 Gateway 配置中使用 vllm-chat 模型
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 单元测试
|
||||||
|
go test ./internal/... -count=1
|
||||||
|
|
||||||
|
# 集成测试
|
||||||
|
go test ./test/integration/ -count=1
|
||||||
|
|
||||||
|
# 全部测试
|
||||||
|
go test ./internal/... -count=1 && \
|
||||||
|
go test ./test/integration/ -count=1 && \
|
||||||
|
go test ./test/security/ -count=1 && \
|
||||||
|
go test ./test/compatibility/ -count=1 && \
|
||||||
|
go test ./test/performance/ -count=1 && \
|
||||||
|
go test ./test/chaos/ -count=1
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user