商业产业 Hacker News AI 2026-10-28T02:30 正文 ok 2641 words

用 YAML 替代 Python:Docker 开源 docker-agent 让 AI Agent 开发走向声明式配置

英文标题:🔶 Writing AI agents in YAML instead of Python

Docker 推出了开源工具 docker-agent,允许开发者通过单一 YAML 文件声明式地配置与运行 AI Agent,无需编写繁琐的 Python 编排胶水代码。该工具内置了免外部依赖的本地 RAG 机制(基于 SQLite 自动切片与向量化)以及包含 MCP 在内的 30 多种工具集,并支持通过命令行一键切换底层模型与定制 Prompt。此外,docker-agent 还引入了 Flavors 变体机制以支持混合检索调优,并支持单文件多 Agent 架构,大幅降低了 AI Agent 的维护成本与实验门槛。
打开原文相关 3 · 机会 1
media16.0 KB media18.5 KB media9.2 KB media25.3 KB media195.5 KB media10.3 KB media24.6 KB media9.3 KB media24.0 KB media13.0 KB media34.9 KB

中文

目录

引言

目前构建 AI Agent 的框架有很多,例如 LangChain 或 LlamaIndex。但是,随着你添加的模型、工具和功能越来越多,Agent 很快就会变成难以维护的大规模代码库。代码越多,当需要做出调整时出错的概率就越大。

docker-agent 将整个工作流保存在一个 YAML 文件中。通过这种方式,你可以自由混搭模型、工具与功能,并能对整个 Agent 的配置一览无余。

在本文中,你将通过构建一个用于回答 Polars 相关问题的 Agent,了解 docker-agent 的出色特性。

💻 获取代码:Agent 配置文件及评测文件已开源在 GitHub。

什么是 docker-agent?

docker-agent 是 Docker 官方推出的用于构建和运行 AI Agent 的开源工具。

docker-agent 对 AI Agent 的意义,就像 Docker 当年对应用程序的意义一样。你不再需要费力配置运行环境和编写繁复的代码,只需在一个 YAML 文件中描述 Agent,然后通过一条命令运行它即可。

环境准备

你可以通过 Homebrew 安装 docker-agent,也可以直接使用 Docker Desktop 4.63 及以上版本自带的 docker agent 命令:

bash brew install docker-agent

该 Agent 将检索 Polars user guide 并回答相关问题。

项目目录结构如下:

text docker-agent-docs-qa/ └── docs/polars/ # Agent 将检索的 72 个 markdown 文档

创建 Agent

要创建 Agent,需要在名为 embeddings.yaml 的 YAML 文件中定义其模型、指令和工具。首先从 agents 部分开始:

yaml agents: root: # 接收你的消息的主 agent model: openai/gpt-5-mini description: Answers questions about Polars from the user guide instruction: | You answer questions about the Polars DataFrame library. Always search the knowledge base before answering. Answer only from what the search returns. End with a "Sources:" list of the file paths you used, relative to docs/polars.

为了让 Agent 能够检索文档,为其分配一个 rag 工具,并指定该工具检索的目录:

yaml agents: root: ... toolsets: - type: rag ref: polars_docs # 使用下方定义的源

rag: polars_docs: # ref 指向的名称 tool: description: The Polars user guide docs: [./docs/polars] # 检索的目标目录 strategies: # 检索策略 - type: chunked-embeddings # 将每个文件切片并向量化 embedding_model: openai/text-embedding-3-small vector_dimensions: 1536 # 每个向量的维度

常规的 RAG 搭建通常需要配置专门的向量数据库和单独的索引构建步骤。docker-agent 会自动处理这两件事:它将向量保存在本地的 SQLite 文件中,在 Agent 首次启动时完成目录索引,并在文件发生变动时自动重新索引。

rag 只是 30 多个内置工具集之一。其他一些实用的工具集包括:

  • shell:运行 shell 命令,例如 Python 脚本或 duckdb
  • filesystem:在目录中读取、写入和搜索文件
  • fetch:读取网页和在线文档
  • mcp:调用外部服务,例如 GitHub 或通过 MCP 访问数据库
  • memory:实现跨会话的事实记忆

创建好 YAML 文件后,即可向 Agent 提问。--exec 标志用于直接执行 Agent 并输出结果,而不进入交互式终端界面:

bash docker agent run --exec embeddings.yaml "How do I read a Parquet file without loading it all into memory?"

输出展示了 Agent 执行的检索过程以及最终回答。以下截取了检索结果与模型的推理摘要:

text Calling polars_docs(query: "read Parquet without loading all into memory scan_parquet scan_parquet streaming lazy read_parquet memory efficient 'scan_parquet' 'read_parquet' 'scan' 'Parquet'")

Use a lazy scan and streaming collect so Polars parses and processes the file in batches instead of materializing everything in RAM.

  • Use pl.scan_parquet(...) (returns a LazyFrame) instead of pl.read_parquet(...). Scanning delays parsing and enables column/row pruning via the optimizer (projection & predicate pushdown).
  • Apply selects/filters on the LazyFrame so the reader can skip unnecessary columns/rows.
  • When collecting, use streaming execution to process in batches: .collect(engine="streaming").

Sources:

  • io/parquet.md
  • lazy/sources_sinks.md
  • lazy/execution.md

回答非常准确,而且每一个结论都能溯源到具体可查阅的文件。

如需提出另一个问题,只需运行相同的命令并传入新问题,无需修改任何 YAML 文件:

bash docker agent run --exec embeddings.yaml "When should I use join_asof?"

切换模型

如果想要尝试更轻量、更便宜的模型而不想直接修改配置文件,只需在单次运行时传入 --model 参数即可:

text embeddings.yaml --model what runs ┌───────────────────┐ ┌───────────────────┐ │ model: gpt-5-mini │ ── gpt-5-nano ──> │ model: gpt-5-nano │ │ instruction: ... │ │ instruction: ... │ │ toolsets: rag │ │ toolsets: rag │ └───────────────────┘ └───────────────────┘ 保持不变 仅对本次运行生效

例如,使用 gpt-5-nano 运行同一个问题:

bash docker agent run --exec embeddings.yaml --model openai/gpt-5-nano
"How do I read a Parquet file without loading it all into memory?"

text

  • Use scan_parquet to read lazily. This returns a LazyFrame and does not parse the data into memory until you collect. ... Sources: io/parquet.md

这种设计具有诸多显著优势:

  • 你可以迅速对比不同模型的回答效果
  • 由于实验过程中无需修改 YAML 配置文件,团队协作其他成员不受影响
  • 选定满意模型后,直接编辑 YAML 即可永久生效

跨问题复用指令

很多时候你可能需要根据受众、输出篇幅或语气来灵活调整 Prompt 指令。

在 docker-agent 中,你可以将不同的提示词指令保存在独立文件中,然后使用 --prompt-file 参数进行调用。

例如,你可以创建一个 pandas-users.md 文件用于给熟悉 pandas 的用户提供说明,以及一个 sql-users.md 文件用于给熟悉 SQL 的用户提供说明。

text "How do I read a Parquet file without loading it all into memory?" │ ┌───────────────────┴───────────────────┐ ▼ ▼ --prompt-file pandas-users.md --prompt-file sql-users.md │ │ ▼ ▼ Pandas: read_parquet() loads SQL: SELECT * FROM the file into memory read_parquet('data.parquet')

先测试 pandas-users.md。首先创建该文件:

markdown The reader knows pandas. Show the pandas equivalent next to each Polars answer. Keep each answer under 8 lines.

然后通过 --prompt-file 启动交互式会话:

bash docker agent run embeddings.yaml --prompt-file pandas-users.md

此时,即使你在提问中完全没有提到 pandas,Agent 的所有回答也会严格遵循 pandas-users.md 中的要求:

text

How do I read a Parquet file without loading it all into memory?

Polars: use scan_parquet("file.parquet") to get a LazyFrame (parsing is delayed). Collect with streaming to process in batches: lf.collect(engine="streaming"). ... Pandas: read_parquet("file.parquet") loads the file into memory (no built-in lazy/streaming scan).

如果你的受众更熟悉 SQL,可以创建名为 sql-users.md 的提示词文件:

markdown The reader knows SQL. Show the Polars SQL equivalent next to each Polars answer. Keep each answer under 8 lines.

换用该文件启动会话:

bash docker agent run embeddings.yaml --prompt-file sql-users.md

同样的问题现在会输出对应的 SQL 对比解答:

text

How do I read a Parquet file without loading it all into memory?

  • Polars: Use lazy scanning so the file isn't parsed upfront: lf = pl.scan_parquet("data.parquet"), which returns a LazyFrame. SQL: SELECT * FROM read_parquet('data.parquet');
  • Polars: Push down projections/filters so only needed columns/rows are read. SQL: SELECT col1, col2 FROM read_parquet('data.parquet') WHERE ...; ...

使用 Flavors 添加变体

有时你可能需要同一个 Agent 的多个变体版本(例如采用不同的检索算法或不同模型)。如果大多数配置保持一致,为每个版本各写一份独立的 YAML 文件会带来大量重复。

docker-agent 允许在同一文件中定义变体(Flavors)。每个 Flavor 仅需声明发生变更的配置项,其余配置则全部继承默认值。

text --flavor hybrid off on │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ embedding search │ │ embedding search │ │ │ │ + keyword search │ └──────────────────┘ └──────────────────┘ base config base + hybrid

在本示例中,我们添加一个名为 hybrid 的变体,在现有向量检索策略的基础上引入关键词检索(BM25):

yaml

embeddings.yaml

agents: root: ...

rag: polars_docs: ... strategies: - type: chunked-embeddings # 向量检索

flavors: # 文件底部新增的配置项 hybrid: rag: polars_docs: strategies+: # 保留 chunked-embeddings,追加 bm25 - type: bm25 # 关键词检索 results: fusion: strategy: rrf # 融合关键词与向量检索结果 k: 60 deduplicate: true limit: 5

若要使用某个变体,只需在运行时指定 --flavor 参数:

bash

仅使用向量检索

docker agent run embeddings.yaml

使用向量检索 + 关键词检索

docker agent run embeddings.yaml --flavor hybrid

在应用了 hybrid 变体之后,docker-agent 生效的实际配置如下:

yaml rag: polars_docs: ... strategies: - type: chunked-embeddings # 继承自基础配置 - type: bm25 # 来自 hybrid 变体 results: fusion: strategy: rrf # 来自 hybrid 变体 ...

为了直观感受其效果,可以询问文档中包含的类名 SQLContext。如果不开启该变体,Agent 仅依赖向量检索,将无法查到相关内容:

bash docker agent run --exec embeddings.yaml "What is SQLContext?"

text Calling polars_docs(query: "SQLContext") Calling polars_docs(query: "SQL")

I searched the Polars documentation for "SQLContext" (and for "SQL") but found no matching documents, so I cannot provide a definition from the docs.

而开启 --flavor hybrid 之后,每次检索都会并发执行两种策略。关键词检索能精准匹配到类名本身,从而准确定位并召回对应页面:

bash docker agent run --exec embeddings.yaml --flavor hybrid "What is SQLContext?"

text Calling polars_docs(query: "SQLContext")

SQLContext is the Polars object that manages SQL queries...

Sources:

  • sql/intro.md

在单个文件中运行不同的 Agent

在很多业务场景中,你需要针对不同任务使用不同的 Agent。例如,可以使用轻量级模型来负责问答,使用能力更强的模型来做代码审查。

docker-agent 支持在同一个文件中定义多个 Agent。它们可以共享文件中已声明的工具与知识库,避免重复配置:

text root reviewer ┌───────────────────┐ ┌───────────────────┐ │ gpt-5-mini │ │ gpt-5 │ │ answers questions │ │ reviews code │ └─────────┬─────────┘ └─────────┬─────────┘ │ │ └──────────────┐ ┌───────────────┘ ▼ ▼ ┌─────────────────┐ │ polars_docs │ │ defined once │ └─────────────────┘

在下面的代码中,我们追加了第二个名为 reviewer 的 Agent。它使用能力更强的模型处理代码审查任务,同时直接复用了 root Agent 的 polars_docs 知识库工具:

yaml agents: root: model: openai/gpt-5-mini # 用于日常问答的小模型 instruction: | You answer questions about the Polars DataFrame library. ... toolsets: - type: rag ref: polars_docs # 共享检索工具

reviewer: model: openai/gpt-5 # 适合处理复杂任务的更强模型 instruction: | You review Polars code. Search the Polars documentation to verify API usage. toolsets: - type: rag ref: polars_docs # 共享同一个检索工具

English / 原文

Table of Contents

Introduction

There are plenty of frameworks for building AI agents, such as LangChain or LlamaIndex. But as you add models, tools, and features, the agent quickly grows into a lot of code to maintain. More code means more places for mistakes when you need to make a change.

docker-agent keeps the entire workflow in one YAML file. With this approach, you can mix and match models, tools, and features, and see how the whole agent is set up at a glance.

In this article, you will learn nice features of docker-agent by building an agent that answers questions about Polars.

💻 Get the Code : The agent configs and eval files are on GitHub.

What Is docker-agent?

docker-agent is Docker’s open-source tool for building and running AI agents.

docker-agent does for AI agents what Docker did for apps. Instead of setting up an environment and writing code, you describe the agent in one YAML file and run it with one command.

Stay Current with CodeCut

Easy-to-digest articles on Python, AI, and open-source tools. Delivered twice a week.

Setup

Install docker-agent with Homebrew, or use the docker agent command that ships with Docker Desktop 4.63 and later:

brew install docker-agent

The agent will search the Polars user guide and answer questions about it.

The project folder looks like this:

docker-agent-docs-qa/
└── docs/polars/    # 72 markdown files the agent will search

Create the Agent

To create the agent, define its model, instructions, and tools in a YAML file called embeddings.yaml. Start with the agents section.

agents:
  root:    # the agent that receives your message
    model: openai/gpt-5-mini
    description: Answers questions about Polars from the user guide
    instruction: |
      You answer questions about the Polars DataFrame library.
      Always search the knowledge base before answering.
      Answer only from what the search returns.
      End with a "Sources:" list of the file paths you used, relative to docs/polars.

To let the agent search the docs, give it a rag tool and tell the tool which folder to search.

agents:
  root:
    ...
    toolsets:
      - type: rag
        ref: polars_docs          # use the source defined below

rag:
  polars_docs:                    # the name the ref points to
    tool:
      description: The Polars user guide
    docs: [./docs/polars]         # what to search
    strategies:                   # how to search
      - type: chunked-embeddings  # split each file into chunks and embed them
        embedding_model: openai/text-embedding-3-small
        vector_dimensions: 1536   # number of values per vector

RAG setups usually need a vector database and a separate indexing step. docker-agent handles both for you. It stores the vectors in a local SQLite file, indexes the folder the first time the agent starts, and re-indexes files when they change.

rag is one of 30 built-in toolsets. Here are a few others that are useful:

  • shell: run shell commands, such as a Python script or duckdb
  • filesystem: read, write, and search files in a folder
  • fetch: read web pages and online docs
  • mcp: call external services like GitHub or a database through MCP
  • memory: remember facts across sessions

After the YAML file is created, ask the agent a question. The --exec flag runs the agent without opening the interactive terminal UI.

docker agent run --exec embeddings.yaml "How do I read a Parquet file without loading it all into memory?"

The output shows the search the agent made, followed by its answer. The search results and the model’s reasoning summary are trimmed here:

Calling polars_docs(query: "read Parquet without loading all into memory scan_parquet
scan_parquet streaming lazy read_parquet memory efficient 'scan_parquet' 'read_parquet'
'scan' 'Parquet'")

Use a lazy scan and streaming collect so Polars parses and processes the file in
batches instead of materializing everything in RAM.

- Use pl.scan_parquet(...) (returns a LazyFrame) instead of pl.read_parquet(...).
  Scanning delays parsing and enables column/row pruning via the optimizer
  (projection & predicate pushdown).
- Apply selects/filters on the LazyFrame so the reader can skip unnecessary
  columns/rows.
- When collecting, use streaming execution to process in batches:
  .collect(engine="streaming").

Sources:
- io/parquet.md
- lazy/sources_sinks.md
- lazy/execution.md

The answer is correct, and every claim traces back to a file you can open.

To ask another question, run the same command with a new question without changing the YAML file.

docker agent run --exec embeddings.yaml "When should I use join_asof?"

Swap the Model

To try a cheaper model without committing to it, you can simply pass --model for a single run.

 embeddings.yaml          --model           what runs
┌───────────────────┐                     ┌───────────────────┐
│ model: gpt-5-mini │ ── gpt-5-nano ──>   │ model: gpt-5-nano │
│ instruction: ...  │                     │ instruction: ...  │
│ toolsets: rag     │                     │ toolsets: rag     │
└───────────────────┘                     └───────────────────┘
     unchanged                               this run only

For example, this runs the same question on gpt-5-nano.

docker agent run --exec embeddings.yaml --model openai/gpt-5-nano \
  "How do I read a Parquet file without loading it all into memory?"



- Use scan_parquet to read lazily. This returns a LazyFrame and does not parse the
  data into memory until you collect.
...
Sources:
io/parquet.md

This is very nice for several reasons:

  • You can quickly try different models and compare their answers
  • Because the YAML file does not change during the experiment, your teammates are not affected
  • Once you like a model, you can edit the YAML file to use it permanently

Reuse Instructions Across Questions

You might want to vary the instructions based on the context, such as the audience, the length, or the tone.

With docker-agent, you can write different instructions in separate files, then pick one with the --prompt-file flag.

For example, you can create a pandas-users.md file with the instructions for pandas users and a sql-users.md file with the instructions for SQL users.

 "How do I read a Parquet file without loading it all into memory?"
                               │
           ┌───────────────────┴───────────────────┐
           ▼                                       ▼
 --prompt-file pandas-users.md          --prompt-file sql-users.md
           │                                       │
           ▼                                       ▼
 Pandas: read_parquet() loads          SQL: SELECT * FROM
 the file into memory                  read_parquet('data.parquet')

Let’s try the pandas-users.md file first. Start by creating the file.

The reader knows pandas. Show the pandas equivalent next to each Polars answer.
Keep each answer under 8 lines.

Then start an interactive chat with the file passed to --prompt-file.

docker agent run embeddings.yaml --prompt-file pandas-users.md

Now every answer follows the instructions in pandas-users.md, even though the questions never mention pandas.

> How do I read a Parquet file without loading it all into memory?

Polars: use scan_parquet("file.parquet") to get a LazyFrame (parsing is delayed).
Collect with streaming to process in batches: lf.collect(engine="streaming").
...
Pandas: read_parquet("file.parquet") loads the file into memory (no built-in
lazy/streaming scan).

If your audience knows SQL instead, create a prompt file called sql-users.md.

The reader knows SQL. Show the Polars SQL equivalent next to each Polars answer.
Keep each answer under 8 lines.

Start a chat with that file instead.

docker agent run embeddings.yaml --prompt-file sql-users.md

The same question now gets a SQL answer instead of a pandas one.

> How do I read a Parquet file without loading it all into memory?

- Polars: Use lazy scanning so the file isn't parsed upfront:
  lf = pl.scan_parquet("data.parquet"), which returns a LazyFrame.
  SQL: SELECT * FROM read_parquet('data.parquet');
- Polars: Push down projections/filters so only needed columns/rows are read.
  SQL: SELECT col1, col2 FROM read_parquet('data.parquet') WHERE ...;
...

Add Variants with Flavors

Sometimes you want more than one version of an agent, such as one with a different search method or a different model. It is redundant to write another YAML file for each version with most of the settings staying the same.

With docker-agent, you can add a flavor to the same file. A flavor lists only the settings that change, and everything else stays the same.

               --flavor hybrid
            off               on
             │                 │
             ▼                 ▼
   ┌──────────────────┐  ┌──────────────────┐
   │ embedding search │  │ embedding search │
   │                  │  │ + keyword search │
   └──────────────────┘  └──────────────────┘
       base config         base + hybrid

In this example, we add a flavor called hybrid that adds keyword search to the existing embedding search strategy.

# embeddings.yaml
agents:
  root: ...

rag:
  polars_docs:
    ...
    strategies:
      - type: chunked-embeddings  # embedding search

flavors:                          # new section at the bottom of the file
  hybrid:
    rag:
      polars_docs:
        strategies+:              # keep chunked-embeddings and add bm25
          - type: bm25            # keyword search
        results:
          fusion:
            strategy: rrf         # merge the keyword and embedding results
            k: 60
          deduplicate: true
          limit: 5

To use a flavor, pass its name with --flavor:

# embedding search only
docker agent run embeddings.yaml

# embedding search + keyword search
docker agent run embeddings.yaml --flavor hybrid

Here is what the config looks like after docker-agent applies the hybrid flavor.

rag:
  polars_docs:
    ...
    strategies:
      - type: chunked-embeddings      # from the base file
      - type: bm25                    # from hybrid
    results:
      fusion:
        strategy: rrf                 # from hybrid
      ...

To see how this is useful, ask about SQLContext, a class name that appears in the docs. Without the flavor, the agent uses the embedding search strategy and finds nothing.

docker agent run --exec embeddings.yaml "What is SQLContext?"



Calling polars_docs(query: "SQLContext")
Calling polars_docs(query: "SQL")

I searched the Polars documentation for "SQLContext" (and for "SQL") but found
no matching documents, so I cannot provide a definition from the docs.

With the hybrid flavor, every search runs both strategies. Keyword search finds the exact name, so the right page comes back.

docker agent run --exec embeddings.yaml --flavor hybrid "What is SQLContext?"



Calling polars_docs(query: "SQLContext")

SQLContext is the Polars object that manages SQL queries...

Sources:
- sql/intro.md

Run Different Agents from One File

Sometimes you might want to have multiple agents for different purposes. For example, you might want to have a small model for answering questions and a stronger model for reviewing code.

docker-agent lets you add more agents to the same file. They can share tools already defined there, so you don’t need to repeat the configuration.

       root                               reviewer
 ┌───────────────────┐              ┌───────────────────┐
 │ gpt-5-mini        │              │ gpt-5             │
 │ answers questions │              │ reviews code      │
 └─────────┬─────────┘              └─────────┬─────────┘
           │                                  │
           └──────────────┐   ┌───────────────┘
                          ▼   ▼
                  ┌─────────────────┐
                  │  polars_docs    │
                  │  defined once   │
                  └─────────────────┘

In the code below, we add a second agent called reviewer that uses a stronger model for reviewing code but uses the same polars_docs tool as the root agent.

agents:
  root:
    model: openai/gpt-5-mini        # small model for answering questions
    instruction: |
      You answer questions about the Polars DataFrame library.
      ...
    toolsets:
      - type: rag
        ref: polars_docs          # shared search

  reviewer:
    model: openai/gpt-5             # stronger model for the harder task
    instruction: |
      You review Polars code.
      Search the docs for each function the code uses.
      Point out slower or outdated patterns and suggest the fix the docs recommend.
      ...
    toolsets:
      - type: rag
        ref: polars_docs          # same shared search

By default, docker-agent runs the first agent in the file. To run the reviewer instead, pass --agent.

docker agent run --exec embeddings.yaml --agent reviewer \
  'Review this code: df = pl.read_parquet("big.parquet").filter(pl.col("amount") > 0).select(["id", "amount"])'



Findings:
- Using read_parquet eagerly loads the entire file before your filter/select are
  applied. This prevents predicate and projection pushdown, which is slower and
  uses more memory on large Parquet files.

Recommended fix:
- Switch to scan_parquet and collect at the end:
  df = (
      pl.scan_parquet("big.parquet")
        .filter(pl.col("amount") > 0)
        .select(["id", "amount"])
        .collect()
  )
...

The reviewer spots the eager read_parquet call and suggests switching to scan_parquet.

Test the Agent

As you experiment with different models and settings, you might want to make sure the agent keeps working as expected. It can be easy to miss mistakes by just observing the output.

docker-agents provides an easy way to define the test cases and run them against the agent.

 evals/streaming-parquet.json
 ┌──────────────────────────────────────────────────┐
 │ question: How do I read a Parquet file without   │
 │           loading it all into memory?            │
 └────────────────────────┬─────────────────────────┘
                          ▼
                   ┌─────────────┐
                   │    agent    │
                   └──────┬──────┘
                          ▼
                       answer
                          │
       ┌──────────────────┼──────────────────┐
       ▼                  ▼                  ▼
 ✓ uses               ✓ mentions         ✓ searched
   scan_parquet         streaming          the docs

To define a test case, create a JSON file in the evals/ folder, such as evals/streaming-parquet.json.

{
  "title": "Large Parquet file question",
  "messages": [
    {"message": {"message": {"role": "user", "content": "How do I read a Parquet file without loading it all into memory?"}}}
  ],
  "evals": {
    "assertions": [
      { "name": "uses scan_parquet", "type": "contains", "value": "scan_parquet" },
      { "name": "mentions streaming", "type": "contains", "value": "streaming" },
      { "name": "searched the docs", "type": "tool_called", "value": "polars_docs" }
    ]
  }
}

For this question to pass, the answer must mention scan_parquet and streaming, and the agent must have searched the docs.

Now run the suite with docker agent eval:

docker agent eval embeddings.yaml ./evals



✓ Asof join question ($0.001918)
  ✓ assertions 3/3
✓ Large Parquet file question ($0.002629)
  ✓ assertions 3/3
[██████████] 100% (2/2) ✓2 ✗0

✅     Assertions: 6/6 passed (100.0%)

Total Cost: $0.004546
Total Time: 26s

Each run reports which tests passed, what each answer cost, and how long it took. Together, they tell you whether a change broke the agent, made it pricier, or slowed it down.

Since each eval runs in its own container, one test can’t leave files behind that change another’s result.

Compare variants before you switch

Because eval takes --flavor too, you can test a variant before adopting it. Let’s say you want to know whether the cheaper gpt-5-nano is good enough for your use case. Add it as a second flavor called cheap.

flavors:
  hybrid: ...
  cheap:
    agents:
      root:
        model: openai/gpt-5-nano  # smallest, lowest-cost GPT-5 model

Then compare the default model against the cheap flavor. Since answers vary between runs, --repeat 3 runs each test three times.

docker agent eval embeddings.yaml ./evals --repeat 3
docker agent eval embeddings.yaml ./evals --flavor cheap --repeat 3



                      gpt-5-mini   gpt-5-nano (cheap)
assertions passed     18/18        13/18      <- less reliable
Parquet eval          3/3 runs     0/3 runs
asof join eval        3/3 runs     2/3 runs
total cost            $0.015       $0.007     <- less than half the cost

The cheap model saves more than half the cost, but its answers are less reliable. It passed 13 of 18 checks, compared with 18 of 18 for the default model.

Share the Agent

Once you are happy with a configuration, you might want to share it with your teammates.

Sharing a Python agent usually means your teammates have to download the code, install the dependencies, and run a setup script before they can use it.

docker-agent makes sharing agents as easy as sharing Docker images. Teammates run it with one command instead of setting up a Python project.

To push the YAML file to a registry such as Docker Hub (after docker login), run:

docker agent share push embeddings.yaml docker.io/<username>/polars-docs:v1

Teammates can then run the agent by name, without downloading the file first.

docker agent run docker.io/<username>/polars-docs:v1

To get a local copy of the YAML file instead, use docker agent share pull.

Note that only the YAML file is shared. Any files it points to, such as docs folders or prompt files, must exist in the folder where you run the agent.

Final Thoughts

What I like about docker-agent is that it takes so little code to get a working agent. An LLM can help you write a Python agent, but you still have to maintain that code, and more code means more places for mistakes.

Keeping everything in one YAML file also makes agents easy to change and easy to read, even for people who do not write Python every day. And with Docker-style sharing, your teammates can try your agent with one command.

But docker-agent is not the right fit for everything. If you need to control each step the agent takes, with custom branches or retries, a code-first framework like LangGraph is a better fit.

The same principle of keeping configuration separate from code is a core chapter in Production-Ready Data Science.

References

  • docker-agent RAG docs (Docker, 2026): retrieval strategies, the default 0.5 similarity threshold, hybrid search with RRF fusion, and SQLite index storage.

Related Tutorials

Stay Current with CodeCut

Easy-to-digest articles on Python, AI, and open-source tools. Delivered twice a week.