complete method

  1. @override
Future<ChatResult> complete(
  1. ChatRequest request
)
override

Executes a non-streaming completion request.

Implementation

@override
Future<ChatResult> complete(ChatRequest request) async {
  final contents = request.messages
      .where((message) => message.role != 'system')
      .map((message) => {
            'role': message.role == 'assistant' ? 'model' : 'user',
            'parts': [
              {'text': message.content}
            ],
          })
      .toList(growable: false);

  final system = request.messages
      .where((message) => message.role == 'system')
      .map((message) => message.content)
      .join('\n');

  AiHttpResponse response;
  try {
    response = await httpClient.send(
      AiHttpRequest(
        method: 'POST',
        uri: endpoint,
        headers: {
          'Content-Type': 'application/json',
          if (bearerToken != null && bearerToken!.isNotEmpty)
            'Authorization': 'Bearer $bearerToken'
          else
            'x-goog-api-key': apiKey!,
        },
        body: jsonEncode({
          'contents': contents,
          if (system.isNotEmpty)
            'systemInstruction': {
              'parts': [
                {'text': system}
              ],
            },
          if (request.temperature != null || request.maxTokens != null)
            'generationConfig': {
              if (request.temperature != null)
                'temperature': request.temperature,
              if (request.maxTokens != null)
                'maxOutputTokens': request.maxTokens,
            },
          ...request.metadata,
        }),
      ),
    );
  } catch (error) {
    throw AiProviderException(
      providerId: id,
      message: 'Gemini request transport failed',
      cause: error,
    );
  }

  if (!response.isSuccess) {
    throw AiProviderException(
      providerId: id,
      message: 'Gemini request failed',
      statusCode: response.statusCode,
      responseBody: response.body,
    );
  }

  final payload = Map<String, dynamic>.from(response.jsonBody() as Map);
  final candidates = payload['candidates'] as List? ?? const [];
  final content = candidates.isEmpty
      ? const <String, dynamic>{}
      : Map<String, dynamic>.from(
          Map<String, dynamic>.from(candidates.first as Map)['content']
                  as Map? ??
              const {},
        );
  final parts = content['parts'] as List? ?? const [];
  final firstPart = parts.isEmpty
      ? const <String, dynamic>{}
      : Map<String, dynamic>.from(parts.first as Map);
  final usage = Map<String, dynamic>.from(
    payload['usageMetadata'] as Map? ?? const {},
  );

  return ChatResult(
    providerId: id,
    model: request.model,
    content: firstPart['text']?.toString() ?? '',
    usage: ChatUsage(
      inputTokens: usage['promptTokenCount'] as int?,
      outputTokens: usage['candidatesTokenCount'] as int?,
      totalTokens: usage['totalTokenCount'] as int?,
    ),
    raw: payload,
  );
}