Agentic Workflows

respond is the unified agentic verb. The same call targets OpenAI's Responses API by default, or Google's Gemini Interactions API by setting service=GEMINIServiceEndpoint — identical inputs, tools, lifecycle, and usage/cost accounting.

One call, two providers

result = respond("Explain multiple dispatch in one sentence.", model="gpt-5.4-mini")
if result isa ResponseSuccess
    println(output_text(result))
else
    println("Request failed — ", output_text(result))
end
Request failed — Error: KeyError: key "OPENAI_API_KEY" not found

Swap the provider with a single keyword:

result = respond("Explain multiple dispatch in one sentence."; service=GEMINIServiceEndpoint, model="gemini-3.1-flash-lite")
if result isa ResponseSuccess
    println(output_text(result))
else
    println("Request failed — ", output_text(result))
end
Request failed — Error: KeyError: key "GEMINI_API_KEY" not found

Hosted tools (Gemini)

Control Gemini thinking with the same typed configuration used by OpenAI:

r = Respond(service=GEMINIServiceEndpoint, model="gemini-3.8-flash", input="Explain recursion briefly.",
            reasoning=Reasoning(effort="low", summary="auto"), max_output_tokens=1024)

This maps to Interactions generation_config.thinking_level and thinking_summaries. Gemini 3.8 supports low, medium, and high effort and rejects temperature and top_p. Continue with previous_response_id to preserve server state and signatures. Google thinking documentation.

Gemini Interactions exposes server-side hosted tools via gemini_google_search, gemini_code_execution, and gemini_url_context — pass them in tools=:

result = respond("What are the latest stable Julia releases?";
                 service=GEMINIServiceEndpoint, tools=[gemini_google_search()], model="gemini-3.1-flash-lite")
if result isa ResponseSuccess
    println(output_text(result))
else
    println("Request failed — ", output_text(result))
end
Request failed — Error: KeyError: key "GEMINI_API_KEY" not found

Constraining tool choice

Force a specific function with tool_choice_function (works on both providers). The other builders — tool_choice_hosted, tool_choice_mcp, tool_choice_custom, tool_choice_allowed — are OpenAI-Responses selectors and raise an error on Gemini.

respond("What's the weather in Paris?";
        tools=[my_function_tool],
        tool_choice=tool_choice_function("get_weather"))

Automated tool loop

The tool_loop driver (see the Tool Calling guide) runs the call/execute/respond cycle automatically, and works across providers — add service= to target Gemini:

ct = CallableTool(function_tool("get_weather", "Get weather",
        parameters=Dict("type" => "object",
                        "properties" => Dict("location" => Dict("type" => "string")))),
    (name, args) -> "22C, sunny")
result = tool_loop("What's the weather in Paris?"; service=GEMINIServiceEndpoint, tools=[ct])
# result.completed == true when the model returns a final text answer

Feeding tool output back manually

Use tool_result to return a function's output on the next turn:

r2 = respond(Respond(; service=GEMINIServiceEndpoint,
                     previous_response_id=r1.response.id,
                     input=[tool_result("call_abc", "get_weather", "72F and sunny")]))

Lifecycle (background requests)

get_response and cancel_response take a service= keyword, so background Gemini Interactions are managed the same way as OpenAI:

status = get_response("<interaction_id>"; service=GEMINIServiceEndpoint)
cancel_response("<interaction_id>"; service=GEMINIServiceEndpoint)

Note: a freshly created background interaction id may briefly return a 403 permission_denied ResponseFailure before the resource becomes retrievable. Poll with a short delay before treating that as a real permission error. The automatic retry policy deliberately does not retry 403 (permission errors are normally permanent), so this is the caller's poll loop to handle.

Usage & cost (cross-provider)

token_usage and estimated_cost work for Gemini too — the Interactions decoder normalizes usage into the shared shape and DEFAULT_PRICING includes gemini-3.8-flash (hosted-tool per-call fees are not modeled).

r = respond("What is 2+2?"; service=GEMINIServiceEndpoint, model="gemini-3.1-flash-lite")
if r isa ResponseSuccess
    println("usage: ", token_usage(r))
    println("est. cost: \$", round(estimated_cost(r); digits=6))
else
    println("Request failed — ", output_text(r))
end
Request failed — Error: KeyError: key "GEMINI_API_KEY" not found

Streaming

Streamed Gemini Interactions assemble function calls from the wire's incremental step events: a streamed respond(...; tools=…, stream=true) finishes with status requires_action and its function_calls populated, exactly like the non-streamed form. Thought steps stream their signature and surface verbatim in output. Initial text in step.start and subsequent deltas are both included, in step order. Thought summaries stay separate from answer text, while signatures remain available in the provider's raw steps.

See Also