feat: 调用 DeepSeek 时关闭思考模式
DeepSeek 默认开启思考,思考 token 按输出计费,且可能耗尽较小的 max_tokens 导致 JSON 截断。对 deepseek.com 地址:chat/completions 发送 thinking.type=disabled,Responses 发送 reasoning.effort=none; 其他服务商请求体保持不变。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<Map<String, dynamic>> captureBody(String endpoint) async {
|
||||
AiService.instance.setFallbackApiKey('test-key');
|
||||
Map<String, dynamic>? 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<String, dynamic>;
|
||||
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');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user