render method

RenderedPrompt render(
  1. Iterable<ChatMessage> messages, {
  2. Iterable<AIFunctionDeclaration> tools = const <AIFunctionDeclaration>[],
  3. bool enableThinking = true,
  4. bool addGenerationPrompt = true,
})

Renders messages (with optional tools) into a Qwen prompt.

Qwen3 thinks by default; enableThinking false pre-fills the generation prompt with an empty <think></think> block — exactly what the upstream template's enable_thinking=false emits — so the model skips the reasoning pass instead of burning decode tokens on it.

Implementation

RenderedPrompt render(
  Iterable<ChatMessage> messages, {
  Iterable<AIFunctionDeclaration> tools = const <AIFunctionDeclaration>[],
  bool enableThinking = true,
  bool addGenerationPrompt = true,
}) {
  final all = messages.toList();
  final out = StringBuffer();
  final images = <Uint8List>[];

  var loopStart = 0;
  final systemParts = <String>[];
  if (all.isNotEmpty && all.first.role == ChatRole.system) {
    final text = all.first.text;
    if (text.isNotEmpty) systemParts.add(text);
    loopStart = 1;
  }
  final toolsSection = hermesToolsSection(tools);
  if (toolsSection.isNotEmpty) systemParts.add(toolsSection);
  if (systemParts.isNotEmpty) {
    out
      ..write(imStart)
      ..write('system\n')
      ..write(systemParts.join('\n\n'))
      ..write(imEnd)
      ..write('\n');
  }

  final body = all.sublist(loopStart);
  var i = 0;
  while (i < body.length) {
    if (body[i].role == ChatRole.tool) {
      // Qwen groups a run of consecutive tool results into one `user` turn,
      // each wrapped in `<tool_response>` (matching the upstream template).
      final responses = <String>[];
      while (i < body.length && body[i].role == ChatRole.tool) {
        for (final r in body[i].contents.whereType<FunctionResultContent>()) {
          responses.add(
            '$toolResponseOpen\n${_resultText(r.result)}\n$toolResponseClose',
          );
        }
        i++;
      }
      out
        ..write(imStart)
        ..write('user\n')
        ..write(responses.join('\n'))
        ..write(imEnd)
        ..write('\n');
      continue;
    }
    out
      ..write(imStart)
      ..write(body[i].role.value)
      ..write('\n')
      ..write(_contentFor(body[i], images))
      ..write(imEnd)
      ..write('\n');
    i++;
  }

  if (addGenerationPrompt) {
    out
      ..write(imStart)
      ..write('assistant\n');
    if (!enableThinking) {
      out.write('$thinkOpen\n\n$thinkClose\n\n');
    }
  }

  return RenderedPrompt(
    text: out.toString(),
    stopSequences: stopSequences,
    media: images,
  );
}