generateWithTools static method

Future<ToolCallingResult> generateWithTools(
  1. String prompt, {
  2. ToolCallingOptions? options,
})

Generate text with tool calling support.

This is the main entry point for tool-enabled generation. Handles the full tool calling loop:

  1. Format tools into system prompt
  2. Generate LLM response
  3. Parse tool calls from output
  4. Execute tools (if autoExecute is true)
  5. Continue generation with tool results
  6. Repeat until no more tool calls or max iterations reached

prompt User's question or request options Tool calling options (optional)

Example:

final result = await RunAnywhereTools.generateWithTools(
  'What is the weather in San Francisco?',
);
print(result.text); // "The weather in San Francisco is 72°F and Sunny."
print(result.toolCalls); // [ToolCall(name: 'get_weather', ...)]

Implementation

static Future<ToolCallingResult> generateWithTools(
  String prompt, {
  ToolCallingOptions? options,
}) async {
  final opts = options ?? const ToolCallingOptions();
  final tools = opts.tools ?? getRegisteredTools();
  final formatName = opts.formatName;

  if (tools.isEmpty) {
    // No tools - just do regular generation
    final result = await RunAnywhere.generate(prompt);
    return ToolCallingResult(
      text: result.text,
      toolCalls: [],
      toolResults: [],
      isComplete: true,
    );
  }

  // Build tools JSON
  final toolsJson = toolsToJson(tools);
  _logger.debug('Tools JSON: $toolsJson');
  _logger.debug('Using tool call format: $formatName');

  // Build initial prompt with tools using the specified format
  final toolsPrompt = DartBridgeToolCalling.shared.formatToolsPromptWithFormat(
    toolsJson,
    formatName,
  );

  // Build the full prompt with system instructions and user query
  final formattedPrompt = '$toolsPrompt\n\nUser: $prompt';
  _logger.debug('Formatted prompt: ${formattedPrompt.substring(0, formattedPrompt.length.clamp(0, 200))}...');

  // Track all tool calls and results
  final allToolCalls = <ToolCall>[];
  final allToolResults = <ToolResult>[];

  var currentPrompt = formattedPrompt;
  var iterations = 0;
  final maxIterations = opts.maxToolCalls;

  while (iterations < maxIterations) {
    iterations++;

    // Lower temperature for more consistent tool calling behavior
    final genOptions = LLMGenerationOptions(
      maxTokens: opts.maxTokens ?? 1024,
      temperature: opts.temperature ?? 0.3,
    );

    // Use streaming like Swift does, then collect all tokens
    final streamResult = await RunAnywhere.generateStream(currentPrompt, options: genOptions);
    final buffer = StringBuffer();
    await for (final token in streamResult.stream) {
      buffer.write(token);
    }
    final responseText = buffer.toString();

    _logger.debug('LLM output (iter $iterations): ${responseText.substring(0, responseText.length.clamp(0, 200))}...');

    // Parse for tool calls using C++ bridge (auto-detection like Swift)
    final parseResult = DartBridgeToolCalling.shared.parseToolCall(responseText);

    if (!parseResult.hasToolCall || parseResult.toolName == null) {
      // No tool call - return final result
      return ToolCallingResult(
        text: parseResult.cleanText,
        toolCalls: allToolCalls,
        toolResults: allToolResults,
        isComplete: true,
      );
    }

    // Create tool call
    final toolCall = ToolCall(
      toolName: parseResult.toolName!,
      arguments: parseResult.arguments != null
          ? dynamicMapToToolValueMap(parseResult.arguments!)
          : {},
      callId: parseResult.callId.toString(),
    );
    allToolCalls.add(toolCall);

    _logger.info('Tool call detected: ${toolCall.toolName}');

    if (!opts.autoExecute) {
      // Return for manual execution
      return ToolCallingResult(
        text: parseResult.cleanText,
        toolCalls: allToolCalls,
        toolResults: allToolResults,
        isComplete: false,
      );
    }

    // Execute the tool
    final toolResult = await executeTool(toolCall);
    allToolResults.add(toolResult);

    // Build follow-up prompt with tool result
    final resultJson = toolResult.result != null
        ? toolResultToJsonString(toolResult.result!)
        : '{"error": "${toolResult.error ?? 'Unknown error'}"}';

    currentPrompt = DartBridgeToolCalling.shared.buildFollowupPrompt(
      originalPrompt: prompt,
      toolsPrompt: opts.keepToolsAvailable
          ? DartBridgeToolCalling.shared.formatToolsPrompt(toolsJson)
          : null,
      toolName: toolCall.toolName,
      toolResultJson: resultJson,
      keepToolsAvailable: opts.keepToolsAvailable,
    );

    _logger.debug('Follow-up prompt: ${currentPrompt.substring(0, currentPrompt.length.clamp(0, 200))}...');
  }

  // Max iterations reached - return what we have
  _logger.warning('Max tool call iterations ($maxIterations) reached');
  return ToolCallingResult(
    text: '',
    toolCalls: allToolCalls,
    toolResults: allToolResults,
    isComplete: true,
  );
}