Series · Aliyun Bailian · Chapter 7

Aliyun Bailian (7): Built-in Web Search and Code Interpreter

Server-side web_search, web_extractor, and code_interpreter through the Responses API — a mini-agent without writing the tool loop.

The previous article built a coding agent by hand: define tools, loop, dispatch, repeat. That’s the right pattern when the tools are yours (your filesystem, your test runner). But for two extremely common tools — searching the web and running a snippet of code — Model Studio now runs them server-side. You enable a flag, the platform does the search-or-execute round trip inside one API call, and you get the final answer back. This article is the practical guide to those built-in tools.


There are two distinct entry points, and picking the wrong one wastes a day.

The simple one — enable_search on Chat Completions. If all you want is “answer this using current web info”, the cheapest change to your existing chat code is a single parameter. With the native DashScope SDK:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import os, dashscope

response = dashscope.Generation.call(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    model="qwen3-max",
    messages=[{"role": "user", "content": "What will the weather be in Hangzhou tomorrow?"}],
    enable_search=True,
    result_format="message",
)
print(response.output.choices[0].message.content)

That’s it. The model decides whether a search is needed, runs it, and folds the results into the answer. Both thinking and non-thinking modes support the agent and agent_max search strategies, where the model can issue multiple, refining queries rather than one shot. For a chatbot that occasionally needs fresh facts, enable_search=True is all you need.

The powerful one — the Responses API with explicit tools. When you want the model to not just search but also open pages and run code over what it finds, you move to client.responses.create(...) and pass tools explicitly. This is the rest of the article.

The Responses API and the built-in tool block#

The Responses API is the newer, agent-oriented surface. The request is flatter — input instead of messages — and you attach built-in tools by type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    # intl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
)

response = client.responses.create(
    model="qwen3-max-2026-01-23",
    input="Find Alibaba Cloud's latest Model Studio announcement and summarize the key points.",
    tools=[
        {"type": "web_search"},
        {"type": "web_extractor"},
        {"type": "code_interpreter"},
    ],
    extra_body={"enable_thinking": True},
)
print(response.output_text)

Three things to know up front:

  • The three tools are meant to be used together. web_search finds URLs, web_extractor opens them and pulls the full content, code_interpreter runs analysis over whatever was found. The docs explicitly recommend enabling all three for best results on complex tasks.
  • web_extractor must be paired with web_search — it has nothing to extract on its own.
  • qwen3-max / qwen3-max-2026-01-23 require thinking mode for these tools, hence the enable_thinking=True. qwen3.5-plus does not require it. Get this wrong and the tools silently don’t fire.

Reading the output: it’s not just text#

response.output_text gives you the final answer string, which is enough for a chat UI. But the interesting part — what the model searched, what it executed — lives in response.output, an array of typed items. Iterate it when you need transparency or citations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for item in response.output:
    if item.type == "reasoning":
        pass  # the thinking chain; I log it, don't show it
    elif item.type == "web_search_call":
        print("searched:", item.action.query)
    elif item.type == "web_extractor_call":
        print("opened a page")
    elif item.type == "message":
        # the final answer, may carry annotations (citations)
        print(item.content)

This typed stream is how you build a “the assistant searched X, then read Y, then computed Z” trace for the user — the same UX pattern as a research agent, but the orchestration happened server-side. To count how many tool invocations a request used (for cost attribution), the usage block carries it:

1
2
3
usage = response.usage
if hasattr(usage, "x_tools") and usage.x_tools:
    print("web_search count:", usage.x_tools.get("web_search", {}).get("count", 0))

Code interpreter: the model runs Python for you#

code_interpreter gives the model a sandboxed Python runtime. It writes code, the platform executes it, the model reads the result, and continues. This is the right tool for anything the LLM is bad at doing in its head: exact arithmetic, parsing a messy CSV, computing a statistic, generating a chart.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
response = client.responses.create(
    model="qwen3.5-plus",
    input=(
        "Here is monthly revenue: Jan 12000, Feb 9800, Mar 15400, Apr 11200, "
        "May 18900, Jun 16700. Compute the month-over-month growth rates and the "
        "compound monthly growth rate, and tell me which month grew fastest."
    ),
    tools=[{"type": "code_interpreter"}],
)
print(response.output_text)

The win here is correctness. Ask qwen3.5-plus to compute a compound growth rate in its head and it will sometimes be subtly wrong; ask it with code_interpreter enabled and it writes the formula, runs it, and reports a number you can trust. For data-analysis chat features this is the difference between a demo and a product. Note the same thinking-mode rule: qwen3-max needs enable_thinking=True, qwen3.5-plus does not.

When to use built-in vs. roll-your-own#

Built-in tools vs roll-your-own function tools
Built-in tools run server-side with zero infrastructure; roll-your-own function tools give you full control. Choose by whether the task has to touch your own systems.

I reach for the built-in tools when:

  • The tool is web search or generic code execution — the two things the platform does well and I’d otherwise have to operate (a search API key, a sandbox) myself.
  • I want the orchestration handled server-side — no tool loop, no parsing tool_calls, no retry logic. One call in, answer out.
  • The task is occasional, so paying per built-in tool call beats running my own search/sandbox infrastructure.

I roll my own (the article-6 pattern) when:

  • The tools touch my systems — my database, my files, my internal APIs. The platform can’t reach those.
  • I need fine control over each step — custom retry, caching, auditing of every tool input/output.
  • Volume is high enough that per-call tool pricing loses to running my own infra.

A useful note on cost: at the time of writing, web_extractor and code_interpreter are free for a limited promotional period, while web_search is metered per call (that’s the usage.x_tools count above). Don’t architect around “free forever” — meter your tool calls from day one so the bill doesn’t surprise you when the promo ends.

A combined example: research-then-compute#

The three tools shine together. “Find the current USD/CNY rate, then convert these invoice totals” needs a search, a page read, and arithmetic — one Responses call does all of it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
response = client.responses.create(
    model="qwen3-max-2026-01-23",
    input=(
        "Look up today's USD to CNY exchange rate, then convert these USD "
        "invoice totals to CNY and give me the sum: 1250.00, 880.50, 3340.75."
    ),
    tools=[{"type": "web_search"}, {"type": "web_extractor"}, {"type": "code_interpreter"}],
    extra_body={"enable_thinking": True},
)
print(response.output_text)

The model searches for the rate, extracts it from a finance page, then uses code_interpreter to do the multiplication and the sum — accurately. Building that by hand means a search integration, an HTML scraper, and a calculation step. Here it’s one call.

What’s next#

That closes the loop on this seven-part series: platform and pricing (1), the LLM API and function calling (2), multimodal Qwen-Omni (3), Wanxiang video (4), Qwen TTS (5), coding agents (6), and built-in tools (7). The throughline has been the same: Bailian keeps moving capability from “you orchestrate it” to “the platform orchestrates it” — function calling, then agents, now server-side tools. The next frontier on the platform is full hosted agents (Agent 2.0), which unify knowledge bases and MCP under the model’s own planning — a natural follow-up once you’ve outgrown a single Responses call.

In this series

Aliyun Bailian 7 parts

  1. 01 Aliyun Bailian (1): Platform Overview and First Request
  2. 02 Aliyun Bailian (2): The Qwen LLM API in Production
  3. 03 Aliyun Bailian (3): Qwen-Omni for Video, Audio, and Image Understanding
  4. 04 Aliyun Bailian (4): Wanxiang Video Generation End-to-End
  5. 05 Aliyun Bailian (5): Qwen-TTS for Multilingual Voice
  6. 06 Aliyun Bailian (6): Qwen3-Coder and Coding Agents
  7. 07 Aliyun Bailian (7): Built-in Web Search and Code Interpreter you are here

Liked this piece?

Follow on GitHub for the next one — usually one a week.

GitHub