feat: enhance OpenAI response rendering to include reasoning and improve tool call detection from thinking channel, and refactor testing scripts for unified unit test execution.

This commit is contained in:
CJACK
2026-02-20 03:30:39 +08:00
parent 2781951ce7
commit dec9d03fc5
13 changed files with 133 additions and 27 deletions

View File

@@ -86,7 +86,7 @@ Manually build WebUI to `static/admin/`:
go test ./...
# End-to-end live tests (real accounts)
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
```
## Project Structure

View File

@@ -86,7 +86,7 @@ docker-compose -f docker-compose.dev.yml up
go test ./...
# 端到端全链路测试(真实账号)
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
```
## 项目结构

View File

@@ -518,7 +518,7 @@ curl http://127.0.0.1:5001/v1/chat/completions \
Run the full live testsuite before release (real account tests):
```bash
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
```
With custom flags:

View File

@@ -518,7 +518,7 @@ curl http://127.0.0.1:5001/v1/chat/completions \
建议在发布前执行完整的端到端测试集(使用真实账号):
```bash
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
```
可自定义参数:

View File

@@ -350,8 +350,10 @@ ds2api/
│ ├── components/ # AccountManager / ApiTester / BatchImport / VercelSync / Login / LandingPage
│ └── locales/ # 中英文语言包zh.json / en.json
├── scripts/
── build-webui.sh # WebUI 手动构建脚本
│ └── testsuite/ # 测试集运行脚本
── build-webui.sh # WebUI 手动构建脚本
── tests/
│ ├── compat/ # 兼容性测试夹具与期望输出
│ └── scripts/ # 统一测试脚本入口unit/e2e
├── static/admin/ # WebUI 构建产物(不提交到 Git
├── .github/
│ ├── workflows/ # GitHub ActionsRelease 自动构建)
@@ -379,11 +381,11 @@ ds2api/
## 测试
```bash
# 单元测试
go test ./...
# 单元测试Go + Node
./tests/scripts/run-unit-all.sh
# 一键端到端全链路测试(真实账号,生成完整请求/响应日志)
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
# 或自定义参数
go run ./cmd/ds2api-tests \

View File

@@ -350,8 +350,10 @@ ds2api/
│ ├── components/ # AccountManager / ApiTester / BatchImport / VercelSync / Login / LandingPage
│ └── locales/ # Language packs (zh.json / en.json)
├── scripts/
── build-webui.sh # Manual WebUI build script
│ └── testsuite/ # Testsuite runner scripts
── build-webui.sh # Manual WebUI build script
── tests/
│ ├── compat/ # Compatibility fixtures and expected outputs
│ └── scripts/ # Unified test script entrypoints (unit/e2e)
├── static/admin/ # WebUI build output (not committed to Git)
├── .github/
│ ├── workflows/ # GitHub Actions (Release artifact automation)
@@ -379,11 +381,11 @@ ds2api/
## Testing
```bash
# Unit tests
go test ./...
# Unit tests (Go + Node)
./tests/scripts/run-unit-all.sh
# One-command live end-to-end tests (real accounts, full request/response logs)
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
# Or with custom flags
go run ./cmd/ds2api-tests \

View File

@@ -8,8 +8,10 @@ DS2API 提供两个层级的测试:
| 层级 | 命令 | 说明 |
| --- | --- | --- |
| 单元测试 | `go test ./...` | 不需要真实账号 |
| 端到端测试 | `./scripts/testsuite/run-live.sh` | 使用真实账号执行全链路测试 |
| 单元测试Go | `./tests/scripts/run-unit-go.sh` | 不需要真实账号 |
| 单元测试Node | `./tests/scripts/run-unit-node.sh` | 不需要真实账号 |
| 单元测试(全部) | `./tests/scripts/run-unit-all.sh` | 不需要真实账号 |
| 端到端测试 | `./tests/scripts/run-live.sh` | 使用真实账号执行全链路测试 |
端到端测试集会录制完整的请求/响应日志,用于故障排查。
@@ -20,17 +22,19 @@ DS2API 提供两个层级的测试:
### 单元测试 | Unit Tests
```bash
go test ./...
./tests/scripts/run-unit-all.sh
```
```bash
node --test api/helpers/stream-tool-sieve.test.js api/chat-stream.test.js api/compat/js_compat_test.js
# 或按语言拆分执行
./tests/scripts/run-unit-go.sh
./tests/scripts/run-unit-node.sh
```
### 端到端测试 | End-to-End Tests
```bash
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
```
**默认行为**
@@ -179,7 +183,7 @@ go run ./cmd/ds2api-tests \
```bash
# 确保 config.json 存在且包含有效测试账号
./scripts/testsuite/run-live.sh
./tests/scripts/run-live.sh
exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "Tests failed! Check artifacts for details."

View File

@@ -47,27 +47,46 @@ func BuildResponseObject(responseID, model, finalPrompt, finalThinking, finalTex
// produced a standalone structured payload. This prevents accidental
// empty output_text on normal prose that merely contains tool_call-like text.
detected := util.ParseStandaloneToolCalls(finalText, toolNames)
toolCallsFromThinking := false
if len(detected) == 0 && strings.TrimSpace(finalThinking) != "" {
detected = util.ParseStandaloneToolCalls(finalThinking, toolNames)
toolCallsFromThinking = len(detected) > 0
}
exposedOutputText := finalText
output := make([]any, 0, 2)
if len(detected) > 0 {
exposedOutputText = ""
if !toolCallsFromThinking || strings.TrimSpace(finalText) != "" {
exposedOutputText = ""
} else {
exposedOutputText = finalThinking
}
if strings.TrimSpace(finalThinking) != "" {
output = append(output, map[string]any{
"type": "reasoning",
"text": finalThinking,
})
}
output = append(output, map[string]any{
"type": "tool_calls",
"tool_calls": util.FormatOpenAIToolCalls(detected),
})
} else {
content := []any{
map[string]any{
"type": "output_text",
"text": finalText,
},
}
content := make([]any, 0, 2)
if finalThinking != "" {
content = append([]any{map[string]any{
"type": "reasoning",
"text": finalThinking,
}}, content...)
}
if strings.TrimSpace(finalText) != "" {
content = append(content, map[string]any{
"type": "output_text",
"text": finalText,
})
}
if strings.TrimSpace(finalText) == "" && strings.TrimSpace(finalThinking) != "" {
exposedOutputText = finalThinking
}
output = append(output, map[string]any{
"type": "message",
"id": "msg_" + strings.ReplaceAll(uuid.NewString(), "-", ""),

View File

@@ -87,3 +87,60 @@ func TestBuildResponseObjectKeepsOutputTextForMixedProse(t *testing.T) {
t.Fatalf("expected output type message, got %#v", first["type"])
}
}
func TestBuildResponseObjectReasoningOnlyFallsBackToOutputText(t *testing.T) {
obj := BuildResponseObject(
"resp_test",
"gpt-4o",
"prompt",
"internal thinking content",
"",
nil,
)
outputText, _ := obj["output_text"].(string)
if outputText == "" {
t.Fatalf("expected output_text fallback from reasoning when final text is empty")
}
output, _ := obj["output"].([]any)
if len(output) != 1 {
t.Fatalf("expected one output item, got %#v", obj["output"])
}
first, _ := output[0].(map[string]any)
if first["type"] != "message" {
t.Fatalf("expected output type message, got %#v", first["type"])
}
content, _ := first["content"].([]any)
if len(content) == 0 {
t.Fatalf("expected reasoning content, got %#v", first["content"])
}
block0, _ := content[0].(map[string]any)
if block0["type"] != "reasoning" {
t.Fatalf("expected first content block reasoning, got %#v", block0["type"])
}
}
func TestBuildResponseObjectDetectsToolCallFromThinkingChannel(t *testing.T) {
obj := BuildResponseObject(
"resp_test",
"gpt-4o",
"prompt",
`{"tool_calls":[{"name":"search","input":{"q":"from-thinking"}}]}`,
"",
[]string{"search"},
)
output, _ := obj["output"].([]any)
if len(output) != 2 {
t.Fatalf("expected reasoning + tool_calls outputs, got %#v", obj["output"])
}
first, _ := output[0].(map[string]any)
if first["type"] != "reasoning" {
t.Fatalf("expected first output reasoning, got %#v", first["type"])
}
second, _ := output[1].(map[string]any)
if second["type"] != "tool_calls" {
t.Fatalf("expected second output tool_calls, got %#v", second["type"])
}
}

8
tests/scripts/run-unit-all.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT_DIR"
./tests/scripts/run-unit-go.sh
./tests/scripts/run-unit-node.sh

7
tests/scripts/run-unit-go.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT_DIR"
go test ./... "$@"

7
tests/scripts/run-unit-node.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT_DIR"
node --test api/helpers/stream-tool-sieve.test.js api/chat-stream.test.js api/compat/js_compat_test.js "$@"