Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e13f9fb
fix(plugins): record content_formatter failure class in BigQuery rows
caohy1988 Sep 28, 2026
48dd52c
Merge remote-tracking branch 'origin/main' into fix/bqaa-formatter-er…
caohy1988 Sep 29, 2026
def458b
fix: stamp Redis sessions with the event timestamp
feiiiiii5 Sep 29, 2026
2b9fbd0
fix(plugins): stop formatter diagnostics leaking names or dropping rows
caohy1988 Sep 29, 2026
3d94259
Merge remote-tracking branch 'origin/main' into fix/bqaa-formatter-er…
caohy1988 Sep 29, 2026
974e464
fix(plugins): keep formatter diagnosis from ever dropping the row
caohy1988 Sep 29, 2026
7fae965
fix(plugins): close remaining formatter paths that leak or drop rows
caohy1988 Sep 29, 2026
f64500a
fix(plugins): honor genuine interrupts and isolate all plugin logging
caohy1988 Sep 29, 2026
9f16d70
docs(plugins): note that closing a rejected coroutine is contained too
caohy1988 Sep 29, 2026
c50ade3
feat: honor tool_thread_pool_config for sync tools outside live mode
GWeale Sep 29, 2026
643df96
fix: drop unpairable trailing FRs in rearrange
a2105z Sep 29, 2026
7298e09
fix: replay a parallel tool call that never ran when a sibling answered
GWeale Sep 29, 2026
ace3bce
chore(scripts): detect added files through git only
xuanyang15 Sep 29, 2026
b4c5272
fix(tools): surface NodeTool failures to on_tool_error and return dic…
DeanChensj Sep 29, 2026
e738c26
feat(mcp): add an opt-in modern-protocol connect path for MCP SDK 2.x
wukath Sep 29, 2026
5a0421c
feat(workflow): propagate skip_summarization from node tools to tool …
DeanChensj Sep 29, 2026
fd2ca87
fix(eval): skip content-less events when mapping Vertex multi-turn turns
GWeale Sep 29, 2026
8632980
fix: detect a dead MCP session whose transport sits behind a dispatcher
GWeale Sep 29, 2026
4d241bf
fix: only apply --avatar_config to live sessions requesting video
wuliang229 Sep 29, 2026
fd14aec
fix: follow redirects when downloading skills in GcpSkillRegistry
codebee-aoki Sep 29, 2026
f7146ac
test: cover api server auto create session
YASHcode-IIITV Sep 29, 2026
0960104
feat: propagate grounding metadata from MCP _meta
claxman Sep 29, 2026
96319fc
docs: explain local lockfile needed by tox in adk-setup skill
iarjunganesh Sep 29, 2026
4d06641
feat: let BigQuery tools run where CMEK is required
vishal-bulbule Sep 29, 2026
b2da4c6
feat: add the model consult session context handover layer
xuanyang15 Sep 29, 2026
28c47b5
feat(tools): invoke advisor models without tools for model_consult
xuanyang15 Sep 29, 2026
84cc99a
feat(tools): add ModelConsultTool with turn and session budgets
xuanyang15 Sep 30, 2026
89ebdec
docs(tools): add ModelConsultTool developer guide and sample agent
xuanyang15 Sep 30, 2026
6f30039
fix: isolate and clean up single_turn LlmAgent node_input events
abhayjoshi201 Sep 30, 2026
d312c0e
refactor(tools): extract shared URL validation helpers into _url_vali…
jazhang00 Sep 30, 2026
b8f50f8
fix(plugins): chain re-raised interrupts to nothing, look up handle p…
caohy1988 Sep 30, 2026
c6b818d
Merge remote-tracking branch 'origin/main' into fix/bqaa-formatter-er…
caohy1988 Sep 30, 2026
f3d2ceb
Merge remote-tracking branch 'my-fork/main' into fix/bqaa-formatter-e…
caohy1988 Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .agents/skills/adk-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ every dependency extra, pre-commit hooks, and a green unit-test run.
python3 --version
```

2. **uv.** Dependencies are pinned in `uv.lock`; a hand-rolled `pip`/`venv`
environment will not reproduce the locked versions.
2. **uv.** Dependencies are declared in `pyproject.toml`. The `uv sync` step
below creates a local `uv.lock`, which this repository ignores. Run that step
before `tox`, whose lock runner requires the file.

```bash
uv --version
Expand Down
131 changes: 131 additions & 0 deletions contributing/samples/tools/model_consult/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# ADK Model Consult Sample

## Overview

This sample demonstrates how an e-commerce order support assistant, `order_support_agent`, pairs routine lookup and action tools, `get_order`, `get_customer_profile`, and `issue_refund`, with `ModelConsultTool` to escalate multi-rule refund policy decisions to a stronger advisor model mid-generation.

The primary agent gathers order and customer details directly and follows the default escalation policy that `ModelConsultTool` adds to its system instruction: it calls `model_consult` before committing to a refund decision and, on longer tasks, again before declaring the task done. The advisor adds the most value when multiple policy exceptions interact, such as late returns, opened electronics restocking fees, defect bulletins, and Gold-tier loyalty exemptions. The agent then executes `issue_refund` based on the advisor's guidance.

## Sample Inputs

- `Customer CUST-108 wants a full refund to their original payment method for order ORD-502 (wireless headphones bought 45 days ago, opened, battery drains quickly). Check the order and customer profile, process the appropriate refund, and explain the decision.`

*The agent calls `get_order('ORD-502')` and `get_customer_profile('CUST-108')`, consults `model_consult` to reconcile the 30-day return cutoff against defect bulletin `SB-2026-04` and the customer's Gold-tier loyalty status with a `2.4%` return rate, executes `issue_refund(order_id='ORD-502', method='original_payment', amount_usd=280.0, ...)`, and summarizes the approved refund.*

- `Customer CUST-10 wants to return order ORD-101 (unopened USB-C cable delivered 5 days ago) for a refund.`

*The agent looks up the order and customer profile and confirms the item is unopened within the 30-day return window. Because the default escalation policy asks the agent to consult before committing to a decision, the agent usually still calls `model_consult` once or twice here, the advisor confirms the straightforward decision, and `max_uses=2` caps the number of consultations in the turn. The agent then processes the full `$19.00` refund to `original_payment`.*

## Graph

```mermaid
graph TD
Agent[order_support_agent] -->|calls| GetOrder(get_order)
Agent -->|calls| GetProfile(get_customer_profile)
Agent -->|calls| Consult(model_consult / ModelConsultTool)
Agent -->|calls| IssueRefund(issue_refund)
```

## How To

Define your domain tools, `get_order`, `get_customer_profile`, and `issue_refund`, and attach `ModelConsultTool` to the `Agent`:

```python
from google.adk import Agent
from google.adk.tools import ModelConsultTool


def get_order(order_id: str) -> dict[str, str | int | float | bool | None]:
"""Looks up an order by its identifier.

Args:
order_id: Order identifier such as 'ORD-101' or 'ORD-502'.

Returns:
A dictionary with the order details and any active defect bulletin.
"""
return {
"order_id": order_id,
"price_usd": 280.0,
"days_since_delivery": 45,
"opened": True,
"defect_bulletin": (
"SB-2026-04: 90-day warranty replacement or store credit; cash refund"
" past 30 days requires Gold-tier loyalty exemption."
),
}


def get_customer_profile(customer_id: str) -> dict[str, str | int | float]:
"""Looks up a customer's loyalty tier and return history.

Args:
customer_id: Customer identifier such as 'CUST-10' or 'CUST-108'.

Returns:
A dictionary with the customer's loyalty tier and return rate percentage.
"""
return {"customer_id": customer_id, "tier": "gold", "return_rate_pct": 2.4}


def issue_refund(
order_id: str,
method: str,
amount_usd: float,
reason: str,
) -> dict[str, str | float]:
"""Issues a refund or replacement for an order.

Args:
order_id: Order identifier being refunded.
method: One of 'original_payment', 'store_credit', or 'replacement'.
amount_usd: Dollar amount to refund.
reason: Short explanation of the policy rule applied.

Returns:
A confirmation record for the processed refund.
"""
return {
"status": "processed",
"order_id": order_id,
"method": method,
"amount_usd": amount_usd,
"reason": reason,
}


root_agent = Agent(
name="order_support_agent",
instruction=(
"You are an e-commerce order support assistant. Look up the order and"
" customer profile before calling issue_refund, and summarize the"
" outcome for the customer."
),
tools=[
get_order,
get_customer_profile,
issue_refund,
ModelConsultTool(
max_uses=2,
session_max_uses=5,
thinking_level="high",
),
],
)
```

Run the sample interactively from the repository root with the ADK CLI:

```bash
adk run contributing/samples/tools/model_consult
```

Or launch the ADK web UI pointed at `contributing/samples/tools` and select `model_consult`:

```bash
adk web contributing/samples/tools
```

## Related Guides

- [ModelConsultTool and ModelConsultContextConfig](../../../../docs/guides/tools/model_consult/model_consult_tool/index.md) - Escalating hard decisions mid-generation to a stronger advisor model with per-turn and session budgets.
15 changes: 15 additions & 0 deletions contributing/samples/tools/model_consult/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from . import agent
157 changes: 157 additions & 0 deletions contributing/samples/tools/model_consult/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Order support and refund policy sample using ModelConsultTool."""

from __future__ import annotations

from google.adk import Agent
from google.adk.tools import ModelConsultTool


def get_order(order_id: str) -> dict[str, str | int | float | bool | None]:
"""Looks up an order by its identifier.

Args:
order_id: Order identifier such as 'ORD-101' or 'ORD-502'.

Returns:
A dictionary with the order details and any active defect bulletin.
"""
orders = {
'ORD-101': {
'order_id': 'ORD-101',
'customer_id': 'CUST-10',
'item': 'USB-C Braided Cable',
'category': 'accessories',
'price_usd': 19.0,
'days_since_delivery': 5,
'opened': False,
'defect_bulletin': None,
},
'ORD-502': {
'order_id': 'ORD-502',
'customer_id': 'CUST-108',
'item': 'ProNC Wireless Headphones (Batch 2026-B)',
'category': 'electronics',
'price_usd': 280.0,
'days_since_delivery': 45,
'opened': True,
'defect_bulletin': (
'SB-2026-04: Batch 2026-B battery drain defect — eligible for'
' 90-day warranty replacement or full store credit; cash refund'
' past 30 days requires Gold-tier loyalty exemption.'
),
},
}
return orders.get(
order_id, {'order_id': order_id, 'error': f'Order {order_id!r} not found'}
)


def get_customer_profile(customer_id: str) -> dict[str, str | int | float]:
"""Looks up a customer's loyalty tier and return history.

Args:
customer_id: Customer identifier such as 'CUST-10' or 'CUST-108'.

Returns:
A dictionary with the customer's loyalty tier and return rate percentage.
"""
customers = {
'CUST-10': {
'customer_id': 'CUST-10',
'tier': 'standard',
'lifetime_orders': 3,
'return_rate_pct': 0.0,
},
'CUST-108': {
'customer_id': 'CUST-108',
'tier': 'gold',
'lifetime_orders': 42,
'return_rate_pct': 2.4,
},
}
return customers.get(
customer_id,
{
'customer_id': customer_id,
'error': f'Customer {customer_id!r} not found',
},
)


def issue_refund(
order_id: str,
method: str,
amount_usd: float,
reason: str,
) -> dict[str, str | float]:
"""Issues a refund or replacement for an order.

Args:
order_id: Order identifier being refunded.
method: One of 'original_payment', 'store_credit', or 'replacement'.
amount_usd: Dollar amount to refund (use 0.0 for 'replacement').
reason: Short explanation of the policy rule applied.

Returns:
A confirmation record for the processed refund.
"""
return {
'status': 'processed',
'order_id': order_id,
'method': method,
'amount_usd': round(amount_usd, 2),
'reason': reason,
}


_TASK_INSTRUCTION = """\
You are an e-commerce order support assistant. Handle refund requests according
to the store's policy:
- Unopened items within 30 days of delivery qualify for a full
`original_payment` refund.
- Opened electronics within 30 days incur a 15% restocking fee (refund 85% of
`price_usd`), unless covered by an active `defect_bulletin`.
- Returns past 30 days are normally declined, with two exceptions:
1. Items with an active `defect_bulletin` qualify for `replacement` or full
`store_credit` up to 90 days after delivery.
2. `gold` tier customers with `return_rate_pct < 5.0` may convert a
defect-bulletin store credit into a full `original_payment` refund with no
restocking fee.

Always call `get_order` and `get_customer_profile` to gather the order and
loyalty facts before calling `issue_refund`, and then summarize the outcome for
the customer.
"""

root_agent = Agent(
name='order_support_agent',
description=(
'Handles customer order returns, warranty defect bulletins, and loyalty'
' refund policies.'
),
instruction=_TASK_INSTRUCTION,
tools=[
get_order,
get_customer_profile,
issue_refund,
ModelConsultTool(
max_uses=2,
session_max_uses=5,
thinking_level='high',
),
],
)
1 change: 1 addition & 0 deletions docs/guides/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ This directory contains specific developer guides for the ADK Python implementat
* [TelemetryConfig](telemetry/telemetry_config/index.md) - What ADK puts in its OpenTelemetry traces, and whether the text of prompts and replies is copied onto exported spans.

### Tools
* [ModelConsultTool and ModelConsultContextConfig](tools/model_consult/model_consult_tool/index.md) - Escalating hard decisions mid-generation to a stronger advisor model, with per-turn and session budgets.
* [Node as tool](tools/node_tool/index.md) - Exposing workflows and deterministic nodes as agent tools with isolated runtime branching and resume support.
* [to_mcp_server](tools/mcp_tool/agent_to_mcp/index.md) - Expose an ADK agent as an MCP server so any MCP host can drive it as a single tool (the MCP counterpart of to_a2a).

Expand Down
12 changes: 9 additions & 3 deletions docs/guides/integrations/bigquery/bigquery_toolset/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ Platform. If it is not provided, the tools attempt to use environment-specific
defaults.

The `bigquery_tool_config` controls the operational limits of the tools. For
example, it defines the maximum number of rows a query can return and whether
the agent is allowed to perform write operations. If this is omitted, the
toolset uses a default `BigQueryToolConfig` instance.
example, it defines the maximum number of rows a query can return,
customer-managed encryption keys (`kms_key_name`), and whether the agent is
allowed to perform write operations. If this is omitted, the toolset uses a
default `BigQueryToolConfig` instance.

## Advanced applications

Expand Down Expand Up @@ -108,6 +109,11 @@ modules, such as metadata inspection and SQL execution. It does not support
every BigQuery API feature, such as managing IAM policies or creating
reservation slots.

The `kms_key_name` option on `BigQueryToolConfig` covers `SELECT` results only.
BigQuery rejects a job-level key for DDL, DML, and multi-statement scripts, so
those run without it, requiring a project default key under policies like
`constraints/gcp.restrictNonCmekServices`.

## Related samples

- [bigquery_agent](../../../../../contributing/samples/a2a/a2a_auth/remote_a2a/bigquery_agent/agent.py) - An agent that manages user data on BigQuery using OAuth2.
Expand Down
Loading
Loading