Skip to content

Commit b76d145

Browse files
committed
Merge remote-tracking branch 'origin/main' into b401261155-autosummary
2 parents 4ad474b + 08c0c0c commit b76d145

File tree

26 files changed

+532
-113
lines changed

26 files changed

+532
-113
lines changed

bigframes/bigquery/_operations/ai.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def generate(
8888
or pandas Series.
8989
connection_id (str, optional):
9090
Specifies the connection to use to communicate with the model. For example, `myproject.us.myconnection`.
91-
If not provided, the connection from the current session will be used.
91+
If not provided, the query uses your end-user credential.
9292
endpoint (str, optional):
9393
Specifies the Vertex AI endpoint to use for the model. For example `"gemini-2.5-flash"`. You can specify any
9494
generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and
@@ -131,7 +131,7 @@ def generate(
131131

132132
operator = ai_ops.AIGenerate(
133133
prompt_context=tuple(prompt_context),
134-
connection_id=_resolve_connection_id(series_list[0], connection_id),
134+
connection_id=connection_id,
135135
endpoint=endpoint,
136136
request_type=request_type,
137137
model_params=json.dumps(model_params) if model_params else None,
@@ -186,7 +186,7 @@ def generate_bool(
186186
or pandas Series.
187187
connection_id (str, optional):
188188
Specifies the connection to use to communicate with the model. For example, `myproject.us.myconnection`.
189-
If not provided, the connection from the current session will be used.
189+
If not provided, the query uses your end-user credential.
190190
endpoint (str, optional):
191191
Specifies the Vertex AI endpoint to use for the model. For example `"gemini-2.5-flash"`. You can specify any
192192
generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and
@@ -216,7 +216,7 @@ def generate_bool(
216216

217217
operator = ai_ops.AIGenerateBool(
218218
prompt_context=tuple(prompt_context),
219-
connection_id=_resolve_connection_id(series_list[0], connection_id),
219+
connection_id=connection_id,
220220
endpoint=endpoint,
221221
request_type=request_type,
222222
model_params=json.dumps(model_params) if model_params else None,
@@ -267,7 +267,7 @@ def generate_int(
267267
or pandas Series.
268268
connection_id (str, optional):
269269
Specifies the connection to use to communicate with the model. For example, `myproject.us.myconnection`.
270-
If not provided, the connection from the current session will be used.
270+
If not provided, the query uses your end-user credential.
271271
endpoint (str, optional):
272272
Specifies the Vertex AI endpoint to use for the model. For example `"gemini-2.5-flash"`. You can specify any
273273
generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and
@@ -297,7 +297,7 @@ def generate_int(
297297

298298
operator = ai_ops.AIGenerateInt(
299299
prompt_context=tuple(prompt_context),
300-
connection_id=_resolve_connection_id(series_list[0], connection_id),
300+
connection_id=connection_id,
301301
endpoint=endpoint,
302302
request_type=request_type,
303303
model_params=json.dumps(model_params) if model_params else None,
@@ -348,7 +348,7 @@ def generate_double(
348348
or pandas Series.
349349
connection_id (str, optional):
350350
Specifies the connection to use to communicate with the model. For example, `myproject.us.myconnection`.
351-
If not provided, the connection from the current session will be used.
351+
If not provided, the query uses your end-user credential.
352352
endpoint (str, optional):
353353
Specifies the Vertex AI endpoint to use for the model. For example `"gemini-2.5-flash"`. You can specify any
354354
generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and
@@ -378,7 +378,7 @@ def generate_double(
378378

379379
operator = ai_ops.AIGenerateDouble(
380380
prompt_context=tuple(prompt_context),
381-
connection_id=_resolve_connection_id(series_list[0], connection_id),
381+
connection_id=connection_id,
382382
endpoint=endpoint,
383383
request_type=request_type,
384384
model_params=json.dumps(model_params) if model_params else None,

bigframes/core/compile/sqlglot/expressions/ai_ops.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,13 @@ def _construct_named_args(op: ops.NaryOp) -> list[sge.Kwarg]:
104104

105105
op_args = asdict(op)
106106

107-
connection_id = op_args["connection_id"]
108-
args.append(
109-
sge.Kwarg(this="connection_id", expression=sge.Literal.string(connection_id))
110-
)
107+
connection_id = op_args.get("connection_id", None)
108+
if connection_id is not None:
109+
args.append(
110+
sge.Kwarg(
111+
this="connection_id", expression=sge.Literal.string(connection_id)
112+
)
113+
)
111114

112115
endpoit = op_args.get("endpoint", None)
113116
if endpoit is not None:

bigframes/display/anywidget.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ class TableWidget(WIDGET_BASE):
5555

5656
page = traitlets.Int(0).tag(sync=True)
5757
page_size = traitlets.Int(0).tag(sync=True)
58-
row_count = traitlets.Int(0).tag(sync=True)
58+
row_count = traitlets.Union(
59+
[traitlets.Int(), traitlets.Instance(type(None))],
60+
default_value=None,
61+
allow_none=True,
62+
).tag(sync=True)
5963
table_html = traitlets.Unicode().tag(sync=True)
6064
_initial_load_complete = traitlets.Bool(False).tag(sync=True)
6165
_batches: Optional[blocks.PandasBatches] = None
@@ -94,12 +98,17 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame):
9498
# SELECT COUNT(*) query. It is a must have however.
9599
# TODO(b/428238610): Start iterating over the result of `to_pandas_batches()`
96100
# before we get here so that the count might already be cached.
97-
# TODO(b/452747934): Allow row_count to be None and check to see if
98-
# there are multiple pages and show "page 1 of many" in this case
99101
self._reset_batches_for_new_page_size()
100-
if self._batches is None or self._batches.total_rows is None:
101-
self._error_message = "Could not determine total row count. Data might be unavailable or an error occurred."
102-
self.row_count = 0
102+
103+
if self._batches is None:
104+
self._error_message = "Could not retrieve data batches. Data might be unavailable or an error occurred."
105+
self.row_count = None
106+
elif self._batches.total_rows is None:
107+
# Total rows is unknown, this is an expected state.
108+
# TODO(b/461536343): Cheaply discover if we have exactly 1 page.
109+
# There are cases where total rows is not set, but there are no additional
110+
# pages. We could disable the "next" button in these cases.
111+
self.row_count = None
103112
else:
104113
self.row_count = self._batches.total_rows
105114

@@ -131,11 +140,22 @@ def _validate_page(self, proposal: Dict[str, Any]) -> int:
131140
Returns:
132141
The validated and clamped page number as an integer.
133142
"""
134-
135143
value = proposal["value"]
144+
145+
if value < 0:
146+
raise ValueError("Page number cannot be negative.")
147+
148+
# If truly empty or invalid page size, stay on page 0.
149+
# This handles cases where row_count is 0 or page_size is 0, preventing
150+
# division by zero or nonsensical pagination, regardless of row_count being None.
136151
if self.row_count == 0 or self.page_size == 0:
137152
return 0
138153

154+
# If row count is unknown, allow any non-negative page. The previous check
155+
# ensures that invalid page_size (0) is already handled.
156+
if self.row_count is None:
157+
return value
158+
139159
# Calculate the zero-indexed maximum page number.
140160
max_page = max(0, math.ceil(self.row_count / self.page_size) - 1)
141161

@@ -229,6 +249,23 @@ def _set_table_html(self) -> None:
229249
# Get the data for the current page
230250
page_data = cached_data.iloc[start:end]
231251

252+
# Handle case where user navigated beyond available data with unknown row count
253+
is_unknown_count = self.row_count is None
254+
is_beyond_data = self._all_data_loaded and len(page_data) == 0 and self.page > 0
255+
if is_unknown_count and is_beyond_data:
256+
# Calculate the last valid page (zero-indexed)
257+
total_rows = len(cached_data)
258+
if total_rows > 0:
259+
last_valid_page = max(0, math.ceil(total_rows / self.page_size) - 1)
260+
# Navigate back to the last valid page
261+
self.page = last_valid_page
262+
# Recursively call to display the correct page
263+
return self._set_table_html()
264+
else:
265+
# If no data at all, stay on page 0 with empty display
266+
self.page = 0
267+
return self._set_table_html()
268+
232269
# Generate HTML table
233270
self.table_html = bigframes.display.html.render_html(
234271
dataframe=page_data,

bigframes/display/table_widget.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,21 @@ function render({ model, el }) {
8585
const rowCount = model.get(ModelProperty.ROW_COUNT);
8686
const pageSize = model.get(ModelProperty.PAGE_SIZE);
8787
const currentPage = model.get(ModelProperty.PAGE);
88-
const totalPages = Math.ceil(rowCount / pageSize);
89-
90-
rowCountLabel.textContent = `${rowCount.toLocaleString()} total rows`;
91-
paginationLabel.textContent = `Page ${(
92-
currentPage + 1
93-
).toLocaleString()} of ${(totalPages || 1).toLocaleString()}`;
94-
prevPage.disabled = currentPage === 0;
95-
nextPage.disabled = currentPage >= totalPages - 1;
88+
89+
if (rowCount === null) {
90+
// Unknown total rows
91+
rowCountLabel.textContent = "Total rows unknown";
92+
paginationLabel.textContent = `Page ${(currentPage + 1).toLocaleString()} of many`;
93+
prevPage.disabled = currentPage === 0;
94+
nextPage.disabled = false; // Allow navigation until we hit the end
95+
} else {
96+
// Known total rows
97+
const totalPages = Math.ceil(rowCount / pageSize);
98+
rowCountLabel.textContent = `${rowCount.toLocaleString()} total rows`;
99+
paginationLabel.textContent = `Page ${(currentPage + 1).toLocaleString()} of ${rowCount.toLocaleString()}`;
100+
prevPage.disabled = currentPage === 0;
101+
nextPage.disabled = currentPage >= totalPages - 1;
102+
}
96103
pageSizeSelect.value = pageSize;
97104
}
98105

bigframes/ml/llm.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
_GEMINI_2_FLASH_001_ENDPOINT = "gemini-2.0-flash-001"
5555
_GEMINI_2_FLASH_LITE_001_ENDPOINT = "gemini-2.0-flash-lite-001"
5656
_GEMINI_2P5_PRO_PREVIEW_ENDPOINT = "gemini-2.5-pro-preview-05-06"
57+
_GEMINI_2P5_PRO_ENDPOINT = "gemini-2.5-pro"
58+
_GEMINI_2P5_FLASH_ENDPOINT = "gemini-2.5-flash"
59+
_GEMINI_2P5_FLASH_LITE_ENDPOINT = "gemini-2.5-flash-lite"
60+
5761
_GEMINI_ENDPOINTS = (
5862
_GEMINI_1P5_PRO_PREVIEW_ENDPOINT,
5963
_GEMINI_1P5_PRO_FLASH_PREVIEW_ENDPOINT,
@@ -64,6 +68,9 @@
6468
_GEMINI_2_FLASH_EXP_ENDPOINT,
6569
_GEMINI_2_FLASH_001_ENDPOINT,
6670
_GEMINI_2_FLASH_LITE_001_ENDPOINT,
71+
_GEMINI_2P5_PRO_ENDPOINT,
72+
_GEMINI_2P5_FLASH_ENDPOINT,
73+
_GEMINI_2P5_FLASH_LITE_ENDPOINT,
6774
)
6875
_GEMINI_PREVIEW_ENDPOINTS = (
6976
_GEMINI_1P5_PRO_PREVIEW_ENDPOINT,
@@ -84,6 +91,9 @@
8491
_GEMINI_2_FLASH_EXP_ENDPOINT,
8592
_GEMINI_2_FLASH_001_ENDPOINT,
8693
_GEMINI_2_FLASH_LITE_001_ENDPOINT,
94+
_GEMINI_2P5_PRO_ENDPOINT,
95+
_GEMINI_2P5_FLASH_ENDPOINT,
96+
_GEMINI_2P5_FLASH_LITE_ENDPOINT,
8797
)
8898

8999
_CLAUDE_3_SONNET_ENDPOINT = "claude-3-sonnet"
@@ -419,20 +429,21 @@ class GeminiTextGenerator(base.RetriableRemotePredictor):
419429
"""Gemini text generator LLM model.
420430
421431
.. note::
422-
gemini-1.5-X are going to be deprecated. Use gemini-2.0-X (https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator) instead.
432+
gemini-1.5-X are going to be deprecated. Use gemini-2.5-X (https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator) instead.
423433
424434
Args:
425435
model_name (str, Default to "gemini-2.0-flash-001"):
426436
The model for natural language tasks. Accepted values are
427437
"gemini-1.5-pro-preview-0514", "gemini-1.5-flash-preview-0514",
428438
"gemini-1.5-pro-001", "gemini-1.5-pro-002", "gemini-1.5-flash-001",
429439
"gemini-1.5-flash-002", "gemini-2.0-flash-exp",
430-
"gemini-2.0-flash-lite-001", and "gemini-2.0-flash-001".
440+
"gemini-2.0-flash-lite-001", "gemini-2.0-flash-001",
441+
"gemini-2.5-pro", "gemini-2.5-flash" and "gemini-2.5-flash-lite".
431442
If no setting is provided, "gemini-2.0-flash-001" will be used by
432443
default and a warning will be issued.
433444
434445
.. note::
435-
"gemini-1.5-X" is going to be deprecated. Please use gemini-2.0-X instead. For example, "gemini-2.0-flash-001".
446+
"gemini-1.5-X" is going to be deprecated. Please use gemini-2.5-X instead. For example, "gemini-2.5-flash".
436447
"gemini-2.0-flash-exp", "gemini-1.5-pro-preview-0514" and "gemini-1.5-flash-preview-0514" is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the
437448
Service Specific Terms(https://cloud.google.com/terms/service-terms#1). Pre-GA products and features are available "as is"
438449
and might have limited support. For more information, see the launch stage descriptions
@@ -462,6 +473,9 @@ def __init__(
462473
"gemini-2.0-flash-exp",
463474
"gemini-2.0-flash-001",
464475
"gemini-2.0-flash-lite-001",
476+
"gemini-2.5-pro",
477+
"gemini-2.5-flash",
478+
"gemini-2.5-flash-lite",
465479
]
466480
] = None,
467481
session: Optional[bigframes.Session] = None,
@@ -510,7 +524,7 @@ def _create_bqml_model(self):
510524
msg = exceptions.format_message(
511525
_MODEL_DEPRECATE_WARNING.format(
512526
model_name=self.model_name,
513-
new_model_name="gemini-2.0-X",
527+
new_model_name="gemini-2.5-X",
514528
link="https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator",
515529
)
516530
)

bigframes/ml/loader.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
llm._GEMINI_2_FLASH_001_ENDPOINT: llm.GeminiTextGenerator,
6868
llm._GEMINI_2_FLASH_LITE_001_ENDPOINT: llm.GeminiTextGenerator,
6969
llm._GEMINI_2P5_PRO_PREVIEW_ENDPOINT: llm.GeminiTextGenerator,
70+
llm._GEMINI_2P5_FLASH_ENDPOINT: llm.GeminiTextGenerator,
71+
llm._GEMINI_2P5_FLASH_LITE_ENDPOINT: llm.GeminiTextGenerator,
72+
llm._GEMINI_2P5_PRO_ENDPOINT: llm.GeminiTextGenerator,
7073
llm._CLAUDE_3_HAIKU_ENDPOINT: llm.Claude3TextGenerator,
7174
llm._CLAUDE_3_SONNET_ENDPOINT: llm.Claude3TextGenerator,
7275
llm._CLAUDE_3_5_SONNET_ENDPOINT: llm.Claude3TextGenerator,

bigframes/operations/ai_ops.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class AIGenerate(base_ops.NaryOp):
2929
name: ClassVar[str] = "ai_generate"
3030

3131
prompt_context: Tuple[str | None, ...]
32-
connection_id: str
32+
connection_id: str | None
3333
endpoint: str | None
3434
request_type: Literal["dedicated", "shared", "unspecified"]
3535
model_params: str | None
@@ -57,7 +57,7 @@ class AIGenerateBool(base_ops.NaryOp):
5757
name: ClassVar[str] = "ai_generate_bool"
5858

5959
prompt_context: Tuple[str | None, ...]
60-
connection_id: str
60+
connection_id: str | None
6161
endpoint: str | None
6262
request_type: Literal["dedicated", "shared", "unspecified"]
6363
model_params: str | None
@@ -79,7 +79,7 @@ class AIGenerateInt(base_ops.NaryOp):
7979
name: ClassVar[str] = "ai_generate_int"
8080

8181
prompt_context: Tuple[str | None, ...]
82-
connection_id: str
82+
connection_id: str | None
8383
endpoint: str | None
8484
request_type: Literal["dedicated", "shared", "unspecified"]
8585
model_params: str | None
@@ -101,7 +101,7 @@ class AIGenerateDouble(base_ops.NaryOp):
101101
name: ClassVar[str] = "ai_generate_double"
102102

103103
prompt_context: Tuple[str | None, ...]
104-
connection_id: str
104+
connection_id: str | None
105105
endpoint: str | None
106106
request_type: Literal["dedicated", "shared", "unspecified"]
107107
model_params: str | None

0 commit comments

Comments
 (0)