parseOpenAiResponse function

LLMCompletionResponse parseOpenAiResponse(
  1. Map<String, dynamic> data
)

OpenAI 互換のレスポンスボディを LLMCompletionResponse に変換する。

Implementation

LLMCompletionResponse parseOpenAiResponse(Map<String, dynamic> data) {
  final choices = (data['choices'] as List?) ?? const [];
  final choice = choices.isNotEmpty
      ? choices.first as Map<String, dynamic>
      : const <String, dynamic>{};
  final message =
      (choice['message'] as Map<String, dynamic>?) ?? const <String, dynamic>{};
  final parts = <LLMContentPart>[];

  final reasoning = message['reasoning_content'] ?? message['reasoning'];
  if (reasoning is String && reasoning.isNotEmpty) {
    parts.add(LLMReasoningPart(reasoning));
  }

  final rawContent = message['content'];
  if (rawContent is String && rawContent.isNotEmpty) {
    parts.add(LLMTextPart(rawContent));
  } else if (rawContent is List) {
    _appendOpenAiContentParts(rawContent, parts);
  }

  // 生成画像(OpenRouter は message.images で返す)。
  final images = message['images'];
  if (images is List) _appendOpenAiImages(images, parts);

  final toolCalls = message['tool_calls'];
  if (toolCalls is List) {
    for (final tc in toolCalls) {
      if (tc is Map<String, dynamic>) {
        final fn = tc['function'];
        final fnMap =
            fn is Map<String, dynamic> ? fn : const <String, dynamic>{};
        parts.add(
          LLMToolCallPart(
            id: (tc['id'] ?? '').toString(),
            name: (fnMap['name'] ?? '').toString(),
            arguments: _decodeArgs(fnMap['arguments']),
          ),
        );
      }
    }
  }

  final audio = message['audio'];
  if (audio is Map<String, dynamic>) {
    final adata = audio['data'];
    if (adata is String && adata.isNotEmpty) {
      parts.add(
        LLMAudioPart.base64(adata, transcript: audio['transcript'] as String?),
      );
    }
  }

  final usage =
      (data['usage'] as Map<String, dynamic>?) ?? const <String, dynamic>{};
  return LLMCompletionResponse(
    content: LLMContent(role: LLMRole.model, parts: parts),
    usage: _parseOpenAiUsage(usage),
    finishReason: LLMFinishReason.parse(choice['finish_reason'] as String?),
    model: data['model'] as String?,
    id: data['id'] as String?,
  );
}