|
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | 14 |
|
| 15 | +import concurrent.futures |
| 16 | +import threading |
| 17 | +import time |
15 | 18 | from unittest import mock |
16 | 19 |
|
17 | 20 | from google.adk.tools import discovery_engine_search_tool |
@@ -489,6 +492,66 @@ def test_auto_detect_falls_back_to_documents(self, mock_search_client): |
489 | 492 | # Mode should be persisted so subsequent calls skip the retry. |
490 | 493 | assert tool._search_result_mode == SearchResultMode.DOCUMENTS |
491 | 494 |
|
| 495 | + @mock.patch.object( |
| 496 | + discoveryengine, |
| 497 | + "SearchServiceClient", |
| 498 | + ) |
| 499 | + def test_auto_detect_singleflights_structured_fallback( |
| 500 | + self, mock_search_client |
| 501 | + ): |
| 502 | + """Concurrent cold calls should share one CHUNKS probe.""" |
| 503 | + spec_cls = discoveryengine.SearchRequest.ContentSearchSpec |
| 504 | + worker_count = 8 |
| 505 | + start_barrier = threading.Barrier(worker_count) |
| 506 | + search_lock = threading.Lock() |
| 507 | + search_modes = [] |
| 508 | + structured_error = exceptions.InvalidArgument( |
| 509 | + "`content_search_spec.search_result_mode` must be set to" |
| 510 | + " SearchRequest.ContentSearchSpec.SearchResultMode.DOCUMENTS" |
| 511 | + " when the engine contains structured data store." |
| 512 | + ) |
| 513 | + mock_doc = discoveryengine.Document( |
| 514 | + name="projects/p/locations/l/doc1", |
| 515 | + id="doc1", |
| 516 | + struct_data={ |
| 517 | + "title": "Jira Issue", |
| 518 | + "uri": "https://jira.example.com/123", |
| 519 | + "summary": "Bug fix", |
| 520 | + }, |
| 521 | + ) |
| 522 | + mock_doc_response = discoveryengine.SearchResponse() |
| 523 | + mock_doc_response.results = [ |
| 524 | + discoveryengine.SearchResponse.SearchResult(document=mock_doc) |
| 525 | + ] |
| 526 | + |
| 527 | + def search(request): |
| 528 | + mode = request.content_search_spec.search_result_mode |
| 529 | + with search_lock: |
| 530 | + search_modes.append(mode) |
| 531 | + if mode == spec_cls.SearchResultMode.CHUNKS: |
| 532 | + time.sleep(0.05) |
| 533 | + raise structured_error |
| 534 | + return mock_doc_response |
| 535 | + |
| 536 | + mock_search_client.return_value.search.side_effect = search |
| 537 | + tool = DiscoveryEngineSearchTool(data_store_id="test_data_store") |
| 538 | + |
| 539 | + def run_search(index): |
| 540 | + start_barrier.wait(timeout=5) |
| 541 | + return tool.discovery_engine_search(f"test query {index}") |
| 542 | + |
| 543 | + with concurrent.futures.ThreadPoolExecutor( |
| 544 | + max_workers=worker_count |
| 545 | + ) as executor: |
| 546 | + results = list(executor.map(run_search, range(worker_count))) |
| 547 | + |
| 548 | + assert all(result["status"] == "success" for result in results) |
| 549 | + assert search_modes.count(spec_cls.SearchResultMode.CHUNKS) == 1 |
| 550 | + assert ( |
| 551 | + search_modes.count(spec_cls.SearchResultMode.DOCUMENTS) == worker_count |
| 552 | + ) |
| 553 | + assert tool._search_result_mode == SearchResultMode.DOCUMENTS |
| 554 | + |
492 | 555 | @mock.patch.object( |
493 | 556 | discoveryengine, |
494 | 557 | "SearchServiceClient", |
|
0 commit comments