Skip to content

Commit 61b797e

Browse files
committed
update
1 parent f3684f5 commit 61b797e

File tree

3 files changed

+200
-15
lines changed

3 files changed

+200
-15
lines changed

backend/README.md

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,103 @@ DATABASE_URL=postgres://user:password@localhost:5432/dbname?sslmode=disable
267267

268268
Add PostgreSQL build support in Dockerfile, similar to existing MySQL and SQLite builds.
269269

270-
This extension approach maintains the integrity of the existing architecture while providing flexible database support.
270+
This extension approach maintains the integrity of the existing architecture while providing flexible database support.
271+
272+
### Adding New LLM Adapters (Adapters)
273+
274+
Adapters are responsible for handling direct communication with upstream LLM APIs, including request building, authentication, and key rotation. Core code is located in `backend/internal/adapters`.
275+
276+
#### 1. Define Adapter Structure
277+
Implement the `LLMAdapter` interface:
278+
279+
```go
280+
type LLMAdapter interface {
281+
ProcessRequest() (*services.TargetRequest, error)
282+
}
283+
```
284+
285+
#### 2. Implement Core Logic
286+
Embed `BaseLLMAdapter` to reuse common logic (like key rotation):
287+
288+
```go
289+
type XAIAdapter struct {
290+
*BaseLLMAdapter
291+
}
292+
293+
func (a *XAIAdapter) ProcessRequest() (*services.TargetRequest, error) {
294+
// 1. Verify Proxy Key
295+
// 2. Rotate Upstream Key
296+
upstreamKey, err := a.RotateUpstreamKey()
297+
298+
// 3. Build Request (Filtering headers, removing gzip, etc.)
299+
headers := utils.FilterRequestHeaders(a.c.Request.Header, []string{"authorization", "accept-encoding"})
300+
headers["Authorization"] = "Bearer " + upstreamKey
301+
302+
// 4. Return TargetRequest object
303+
return &services.TargetRequest{...}, nil
304+
}
305+
```
306+
307+
### Extending New API Formats (Converters)
308+
309+
Converters are responsible for handling transformations between client-side formats and backend API expected formats. Core code is located in `backend/internal/converters`.
310+
311+
#### 1. Register New Format
312+
Register the new format identifier in `backend/internal/converters/formats/registry.go`:
313+
314+
```go
315+
// Example: Adding a new format identifier
316+
const (
317+
FormatClaudeNative = "claude_native"
318+
)
319+
```
320+
321+
#### 2. Implement Format Handler
322+
Implement the `FormatHandler` interface in `backend/internal/converters/formats/claude_native/`:
323+
324+
```go
325+
type ClaudeNativeHandler struct{}
326+
327+
// Build Request: Convert universal request format to Claude native format
328+
func (h *ClaudeNativeHandler) BuildRequest(req *types.UniversalRequest) ([]byte, error) {
329+
// Implement conversion logic...
330+
}
331+
332+
// Parse Response: Convert Claude native response to universal response format
333+
func (h *ClaudeNativeHandler) ParseResponse(body []byte) (*types.UniversalResponse, error) {
334+
// Implement parsing logic...
335+
}
336+
```
337+
338+
#### 3. Handle Streaming Responses (Optional)
339+
If streaming support is required, implement the `StreamHandler` interface to handle SSE (Server-Sent Events) transformations. This is critical for chat interaction experiences.
340+
341+
```go
342+
type StreamHandler interface {
343+
// Parse stream chunk: Parse format-specific SSE data line into universal stream chunk
344+
ParseStreamChunk(chunk []byte) (*UniversalStreamChunk, error)
345+
346+
// Build stream chunk: Build format-specific SSE data line from universal stream chunk
347+
BuildStreamChunk(chunk *UniversalStreamChunk) ([]byte, error)
348+
349+
// Build start event (e.g., OpenAI's role delta)
350+
BuildStartEvent(model string, id string) [][]byte
351+
352+
// Build end event (e.g., [DONE])
353+
BuildEndEvent() [][]byte
354+
}
355+
```
356+
357+
Implementation Example:
358+
359+
```go
360+
func (h *ClaudeStreamHandler) ParseStreamChunk(chunk []byte) (*UniversalStreamChunk, error) {
361+
// 1. Parse SSE line (e.g., "data: {...}")
362+
// 2. Extract content delta
363+
// 3. Construct UniversalStreamChunk
364+
return &UniversalStreamChunk{
365+
Content: deltaContent,
366+
FinishReason: finishReason,
367+
}, nil
368+
}
369+
```

backend/README_CN.md

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,103 @@ DATABASE_URL=postgres://user:password@localhost:5432/dbname?sslmode=disable
269269

270270
在Dockerfile中添加PostgreSQL构建支持,类似于现有的MySQL和SQLite构建。
271271

272-
这样的扩展方式保持了现有架构的完整性,同时提供了灵活的数据库支持。
272+
这样的扩展方式保持了现有架构的完整性,同时提供了灵活的数据库支持。
273+
274+
### 接入新的LLM适配器 (Adapters)
275+
276+
适配器负责处理与上游 LLM API 的直接通信,包括请求构建、认证和密钥轮询。核心代码位于 `backend/internal/adapters`
277+
278+
#### 1. 定义适配器结构
279+
实现 `LLMAdapter` 接口:
280+
281+
```go
282+
type LLMAdapter interface {
283+
ProcessRequest() (*services.TargetRequest, error)
284+
}
285+
```
286+
287+
#### 2. 实现核心逻辑
288+
继承 `BaseLLMAdapter` 以复用通用逻辑(如密钥轮询):
289+
290+
```go
291+
type XAIAdapter struct {
292+
*BaseLLMAdapter
293+
}
294+
295+
func (a *XAIAdapter) ProcessRequest() (*services.TargetRequest, error) {
296+
// 1. 验证代理密钥 (Proxy Key)
297+
// 2. 轮询上游密钥 (Upstream Key)
298+
upstreamKey, err := a.RotateUpstreamKey()
299+
300+
// 3. 构建请求 (Header处理,移除 Gzip 等)
301+
headers := utils.FilterRequestHeaders(a.c.Request.Header, []string{"authorization", "accept-encoding"})
302+
headers["Authorization"] = "Bearer " + upstreamKey
303+
304+
// 4. 返回目标请求对象
305+
return &services.TargetRequest{...}, nil
306+
}
307+
```
308+
309+
### 扩展新的API格式 (Converters)
310+
311+
转换器负责处理客户端格式与后端 API 期望格式之间的相互转换。核心代码位于 `backend/internal/converters`
312+
313+
#### 1. 注册新格式
314+
`backend/internal/converters/formats/registry.go` 中注册新的格式标识:
315+
316+
```go
317+
// 示例:添加新的格式标识
318+
const (
319+
FormatClaudeNative = "claude_native"
320+
)
321+
```
322+
323+
#### 2. 实现格式处理器
324+
`backend/internal/converters/formats/claude_native/` 下实现 `FormatHandler` 接口:
325+
326+
```go
327+
type ClaudeNativeHandler struct{}
328+
329+
// 转换请求:将通用请求格式转换为 Claude 原生格式
330+
func (h *ClaudeNativeHandler) BuildRequest(req *types.UniversalRequest) ([]byte, error) {
331+
// 实现转换逻辑...
332+
}
333+
334+
// 转换响应:将 Claude 原生响应转换为通用响应格式
335+
func (h *ClaudeNativeHandler) ParseResponse(body []byte) (*types.UniversalResponse, error) {
336+
// 实现解析逻辑...
337+
}
338+
```
339+
340+
#### 3. 处理流式响应 (可选)
341+
如果需要支持流式传输,还需实现 `StreamHandler` 接口以处理 SSE (Server-Sent Events) 转换。这对于聊天交互体验至关重要。
342+
343+
```go
344+
type StreamHandler interface {
345+
// 解析流分块:将特定格式的 SSE 数据行解析为通用流分块
346+
ParseStreamChunk(chunk []byte) (*UniversalStreamChunk, error)
347+
348+
// 构建流分块:将通用流分块构建为特定格式的 SSE 数据行
349+
BuildStreamChunk(chunk *UniversalStreamChunk) ([]byte, error)
350+
351+
// 构建起始事件 (如 OpenAI 的 role delta)
352+
BuildStartEvent(model string, id string) [][]byte
353+
354+
// 构建结束事件 (如 [DONE])
355+
BuildEndEvent() [][]byte
356+
}
357+
```
358+
359+
实现示例:
360+
361+
```go
362+
func (h *ClaudeStreamHandler) ParseStreamChunk(chunk []byte) (*UniversalStreamChunk, error) {
363+
// 1. 解析 SSE 行 (e.g., "data: {...}")
364+
// 2. 提取 content delta
365+
// 3. 构造 UniversalStreamChunk
366+
return &UniversalStreamChunk{
367+
Content: deltaContent,
368+
FinishReason: finishReason,
369+
}, nil
370+
}
371+
```

backend/internal/handlers/llm_proxy.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ func (h *LLMProxyHandler) prepareLLMRequest(c *gin.Context, slug, action string)
9797
convertedAction := action
9898
if needConversion {
9999
logger.Infof("Request format conversion enabled: %s -> %s", clientFormat, apiFormat)
100-
logger.Infof("Original request body: %s", string(bodyBytes))
101100

102101
converter, err := converters.NewConverter(clientFormat, apiFormat)
103102
if err != nil {
@@ -111,7 +110,6 @@ func (h *LLMProxyHandler) prepareLLMRequest(c *gin.Context, slug, action string)
111110
return nil, nil, fmt.Errorf("failed to convert request format: %w", err)
112111
}
113112
bodyBytes = convertedBody
114-
logger.Infof("Converted request body: %s", string(bodyBytes))
115113

116114
// 转换请求路径
117115
convertedAction = converter.GetTargetPath(action)
@@ -266,9 +264,6 @@ func (h *LLMProxyHandler) forwardLLMRequest(c *gin.Context, target *services.Tar
266264
}
267265

268266
if needConversion && converter != nil {
269-
// 调试日志:打印原始响应内容
270-
logger.Infof("Original response body (first 500 chars): %s", truncateString(string(body), 500))
271-
272267
// 转换响应格式
273268
convertedBody, err := converter.ConvertResponse(body)
274269
if err != nil {
@@ -378,11 +373,3 @@ func extractModelFromBody(body []byte) string {
378373

379374
return ""
380375
}
381-
382-
// truncateString truncates a string to the specified max length
383-
func truncateString(s string, maxLen int) string {
384-
if len(s) <= maxLen {
385-
return s
386-
}
387-
return s[:maxLen] + "..."
388-
}

0 commit comments

Comments
 (0)