From 202bb64ececc0c47af87ce0e149c58f788321190 Mon Sep 17 00:00:00 2001 From: shenlei Date: Thu, 17 Sep 2026 14:32:25 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E7=94=A8=20DeepSeek=20?= =?UTF-8?q?=E6=97=B6=E5=85=B3=E9=97=AD=E6=80=9D=E8=80=83=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DeepSeek 默认开启思考,思考 token 按输出计费,且可能耗尽较小的 max_tokens 导致 JSON 截断。对 deepseek.com 地址:chat/completions 发送 thinking.type=disabled,Responses 发送 reasoning.effort=none; 其他服务商请求体保持不变。 Co-Authored-By: Claude Opus 5 --- kouyu_english/lib/core/ai_service.dart | 26 ++++++++ .../test/deepseek_thinking_test.dart | 64 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 kouyu_english/test/deepseek_thinking_test.dart diff --git a/kouyu_english/lib/core/ai_service.dart b/kouyu_english/lib/core/ai_service.dart index 5a78344..1ef23b9 100644 --- a/kouyu_english/lib/core/ai_service.dart +++ b/kouyu_english/lib/core/ai_service.dart @@ -221,6 +221,11 @@ class AiService { static bool _isHttpUri(Uri uri) => uri.scheme == 'https' || uri.scheme == 'http'; + static bool _isDeepSeek(Uri uri) { + final host = uri.host.toLowerCase(); + return host == 'deepseek.com' || host.endsWith('.deepseek.com'); + } + static bool _isSuccess(http.Response response) => response.statusCode >= 200 && response.statusCode < 300; @@ -373,6 +378,27 @@ class AiService { String reasoningEffort = 'low', }) { final isResponses = uri.path.endsWith('/responses'); + // DeepSeek thinks by default. Every request here is a short JSON answer + // with a small token cap, so reasoning only adds cost and can exhaust the + // cap before the JSON is written. Other providers may reject these + // DeepSeek-specific switches, so they keep the original payload. + if (_isDeepSeek(uri)) { + return isResponses + ? { + 'model': model, + 'input': messages, + 'reasoning': {'effort': 'none'}, + 'temperature': ?temperature, + 'max_output_tokens': ?maxTokens, + } + : { + 'model': model, + 'messages': messages, + 'thinking': {'type': 'disabled'}, + 'temperature': ?temperature, + 'max_tokens': ?maxTokens, + }; + } if (isResponses) { return { 'model': model, diff --git a/kouyu_english/test/deepseek_thinking_test.dart b/kouyu_english/test/deepseek_thinking_test.dart new file mode 100644 index 0000000..d1e9dcc --- /dev/null +++ b/kouyu_english/test/deepseek_thinking_test.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:kouyu_english/core/ai_service.dart'; +import 'package:kouyu_english/core/models.dart'; + +void main() { + const definition = '{"definition":"苹果"}'; + + Future> captureBody(String endpoint) async { + AiService.instance.setFallbackApiKey('test-key'); + Map? body; + final result = await http.runWithClient( + () => AiService.instance.temporaryDefinition( + provider: AiProviderType.compatible, + endpoint: endpoint, + model: 'deepseek-flash', + text: 'apple', + ), + () => MockClient((request) async { + body = jsonDecode(request.body) as Map; + return http.Response( + jsonEncode({ + 'choices': [ + { + 'message': {'content': definition}, + }, + ], + 'output_text': definition, + }), + 200, + headers: {'content-type': 'application/json; charset=utf-8'}, + ); + }), + ); + expect(result, '苹果'); + return body!; + } + + test('DeepSeek chat completions request disables thinking', () async { + final body = await captureBody('https://api.deepseek.com'); + + expect(body['thinking'], {'type': 'disabled'}); + expect(body.containsKey('reasoning_effort'), isFalse); + expect(body['max_tokens'], 200); + }); + + test('DeepSeek responses request sets reasoning effort to none', () async { + final body = await captureBody('https://api.deepseek.com/v1/responses'); + + expect(body['reasoning'], {'effort': 'none'}); + expect(body.containsKey('reasoning_effort'), isFalse); + expect(body.containsKey('thinking'), isFalse); + }); + + test('other compatible endpoints keep their original payload', () async { + final body = await captureBody('https://example.test/v1'); + + expect(body.containsKey('thinking'), isFalse); + expect(body['reasoning_effort'], 'low'); + }); +}