main e34e569de465 cached
46 files
239.2 KB
91.3k tokens
62 symbols
1 requests
Download .txt
Showing preview only (253K chars total). Download the full file or copy to clipboard to get everything.
Repository: google-gemini/gemini-fullstack-langgraph-quickstart
Branch: main
Commit: e34e569de465
Files: 46
Total size: 239.2 KB

Directory structure:
gitextract_1hm2p4us/

├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── backend/
│   ├── .gitignore
│   ├── LICENSE
│   ├── Makefile
│   ├── examples/
│   │   └── cli_research.py
│   ├── langgraph.json
│   ├── pyproject.toml
│   ├── src/
│   │   └── agent/
│   │       ├── __init__.py
│   │       ├── app.py
│   │       ├── configuration.py
│   │       ├── graph.py
│   │       ├── prompts.py
│   │       ├── state.py
│   │       ├── tools_and_schemas.py
│   │       └── utils.py
│   └── test-agent.ipynb
├── docker-compose.yml
└── frontend/
    ├── .gitignore
    ├── components.json
    ├── eslint.config.js
    ├── index.html
    ├── package.json
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   ├── ActivityTimeline.tsx
    │   │   ├── ChatMessagesView.tsx
    │   │   ├── InputForm.tsx
    │   │   ├── WelcomeScreen.tsx
    │   │   └── ui/
    │   │       ├── badge.tsx
    │   │       ├── button.tsx
    │   │       ├── card.tsx
    │   │       ├── input.tsx
    │   │       ├── scroll-area.tsx
    │   │       ├── select.tsx
    │   │       ├── tabs.tsx
    │   │       └── textarea.tsx
    │   ├── global.css
    │   ├── lib/
    │   │   └── utils.ts
    │   ├── main.tsx
    │   └── vite-env.d.ts
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Node / Frontend
node_modules/
frontend/dist/
frontend/.vite/
frontend/coverage/
.DS_Store
*.local

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE files
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Optional backend venv (if created in root)
#.venv/ 

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
uv.lock

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

backend/.langgraph_api

================================================
FILE: Dockerfile
================================================
# Stage 1: Build React Frontend
FROM node:20-alpine AS frontend-builder

# Set working directory for frontend
WORKDIR /app/frontend

# Copy frontend package files and install dependencies
COPY frontend/package.json ./
COPY frontend/package-lock.json ./
# If you use yarn or pnpm, adjust accordingly (e.g., copy yarn.lock or pnpm-lock.yaml and use yarn install or pnpm install)
RUN npm install

# Copy the rest of the frontend source code
COPY frontend/ ./

# Build the frontend
RUN npm run build

# Stage 2: Python Backend
FROM docker.io/langchain/langgraph-api:3.11

# -- Install UV --
# First install curl, then install UV using the standalone installer
RUN apt-get update && apt-get install -y curl && \
    curl -LsSf https://astral.sh/uv/install.sh | sh && \
    apt-get clean && rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.local/bin:$PATH"
# -- End of UV installation --

# -- Copy built frontend from builder stage --
# The app.py expects the frontend build to be at ../frontend/dist relative to its own location.
# If app.py is at /deps/backend/src/agent/app.py, then ../frontend/dist resolves to /deps/frontend/dist.
COPY --from=frontend-builder /app/frontend/dist /deps/frontend/dist
# -- End of copying built frontend --

# -- Adding local package . --
ADD backend/ /deps/backend
# -- End of local package . --

# -- Installing all local dependencies using UV --
# First, we need to ensure pip is available for UV to use
RUN uv pip install --system pip setuptools wheel
# Install dependencies with UV, respecting constraints
RUN cd /deps/backend && \
    PYTHONDONTWRITEBYTECODE=1 UV_SYSTEM_PYTHON=1 uv pip install --system -c /api/constraints.txt -e .
# -- End of local dependencies install --
ENV LANGGRAPH_HTTP='{"app": "/deps/backend/src/agent/app.py:app"}'
ENV LANGSERVE_GRAPHS='{"agent": "/deps/backend/src/agent/graph.py:graph"}'

# -- Ensure user deps didn't inadvertently overwrite langgraph-api
# Create all required directories that the langgraph-api package expects
RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license /api/langgraph_storage && \
    touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py /api/langgraph_storage/__init__.py
# Use pip for this specific package as it has poetry-based build requirements
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir --no-deps -e /api
# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api --
# -- Removing pip from the final image (but keeping UV) --
RUN uv pip uninstall --system pip setuptools wheel && \
    rm -rf /usr/local/lib/python*/site-packages/pip* /usr/local/lib/python*/site-packages/setuptools* /usr/local/lib/python*/site-packages/wheel* && \
    find /usr/local/bin -name "pip*" -delete
# -- End of pip removal --

WORKDIR /deps/backend


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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.


================================================
FILE: Makefile
================================================
.PHONY: help dev-frontend dev-backend dev

help:
	@echo "Available commands:"
	@echo "  make dev-frontend    - Starts the frontend development server (Vite)"
	@echo "  make dev-backend     - Starts the backend development server (Uvicorn with reload)"
	@echo "  make dev             - Starts both frontend and backend development servers"

dev-frontend:
	@echo "Starting frontend development server..."
	@cd frontend && npm run dev

dev-backend:
	@echo "Starting backend development server..."
	@cd backend && langgraph dev

# Run frontend and backend concurrently
dev:
	@echo "Starting both frontend and backend development servers..."
	@make dev-frontend & make dev-backend 

================================================
FILE: README.md
================================================
# Gemini Fullstack LangGraph Quickstart

This project demonstrates a fullstack application using a React frontend and a LangGraph-powered backend agent. The agent is designed to perform comprehensive research on a user's query by dynamically generating search terms, querying the web using Google Search, reflecting on the results to identify knowledge gaps, and iteratively refining its search until it can provide a well-supported answer with citations. This application serves as an example of building research-augmented conversational AI using LangGraph and Google's Gemini models.

<img src="./app.png" title="Gemini Fullstack LangGraph" alt="Gemini Fullstack LangGraph" width="90%">

## Features

- 💬 Fullstack application with a React frontend and LangGraph backend.
- 🧠 Powered by a LangGraph agent for advanced research and conversational AI.
- 🔍 Dynamic search query generation using Google Gemini models.
- 🌐 Integrated web research via Google Search API.
- 🤔 Reflective reasoning to identify knowledge gaps and refine searches.
- 📄 Generates answers with citations from gathered sources.
- 🔄 Hot-reloading for both frontend and backend during development.

## Project Structure

The project is divided into two main directories:

-   `frontend/`: Contains the React application built with Vite.
-   `backend/`: Contains the LangGraph/FastAPI application, including the research agent logic.

## Getting Started: Development and Local Testing

Follow these steps to get the application running locally for development and testing.

**1. Prerequisites:**

-   Node.js and npm (or yarn/pnpm)
-   Python 3.11+
-   **`GEMINI_API_KEY`**: The backend agent requires a Google Gemini API key.
    1.  Navigate to the `backend/` directory.
    2.  Create a file named `.env` by copying the `backend/.env.example` file.
    3.  Open the `.env` file and add your Gemini API key: `GEMINI_API_KEY="YOUR_ACTUAL_API_KEY"`

**2. Install Dependencies:**

**Backend:**

```bash
cd backend
pip install .
```

**Frontend:**

```bash
cd frontend
npm install
```

**3. Run Development Servers:**

**Backend & Frontend:**

```bash
make dev
```
This will run the backend and frontend development servers.    Open your browser and navigate to the frontend development server URL (e.g., `http://localhost:5173/app`).

_Alternatively, you can run the backend and frontend development servers separately. For the backend, open a terminal in the `backend/` directory and run `langgraph dev`. The backend API will be available at `http://127.0.0.1:2024`. It will also open a browser window to the LangGraph UI. For the frontend, open a terminal in the `frontend/` directory and run `npm run dev`. The frontend will be available at `http://localhost:5173`._

## How the Backend Agent Works (High-Level)

The core of the backend is a LangGraph agent defined in `backend/src/agent/graph.py`. It follows these steps:

<img src="./agent.png" title="Agent Flow" alt="Agent Flow" width="50%">

1.  **Generate Initial Queries:** Based on your input, it generates a set of initial search queries using a Gemini model.
2.  **Web Research:** For each query, it uses the Gemini model with the Google Search API to find relevant web pages.
3.  **Reflection & Knowledge Gap Analysis:** The agent analyzes the search results to determine if the information is sufficient or if there are knowledge gaps. It uses a Gemini model for this reflection process.
4.  **Iterative Refinement:** If gaps are found or the information is insufficient, it generates follow-up queries and repeats the web research and reflection steps (up to a configured maximum number of loops).
5.  **Finalize Answer:** Once the research is deemed sufficient, the agent synthesizes the gathered information into a coherent answer, including citations from the web sources, using a Gemini model.

## CLI Example

For quick one-off questions you can execute the agent from the command line. The
script `backend/examples/cli_research.py` runs the LangGraph agent and prints the
final answer:

```bash
cd backend
python examples/cli_research.py "What are the latest trends in renewable energy?"
```


## Deployment

In production, the backend server serves the optimized static frontend build. LangGraph requires a Redis instance and a Postgres database. Redis is used as a pub-sub broker to enable streaming real time output from background runs. Postgres is used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. For more details on how to deploy the backend server, take a look at the [LangGraph Documentation](https://langchain-ai.github.io/langgraph/concepts/deployment_options/). Below is an example of how to build a Docker image that includes the optimized frontend build and the backend server and run it via `docker-compose`.

_Note: For the docker-compose.yml example you need a LangSmith API key, you can get one from [LangSmith](https://smith.langchain.com/settings)._

_Note: If you are not running the docker-compose.yml example or exposing the backend server to the public internet, you should update the `apiUrl` in the `frontend/src/App.tsx` file to your host. Currently the `apiUrl` is set to `http://localhost:8123` for docker-compose or `http://localhost:2024` for development._

**1. Build the Docker Image:**

   Run the following command from the **project root directory**:
   ```bash
   docker build -t gemini-fullstack-langgraph -f Dockerfile .
   ```
**2. Run the Production Server:**

   ```bash
   GEMINI_API_KEY=<your_gemini_api_key> LANGSMITH_API_KEY=<your_langsmith_api_key> docker-compose up
   ```

Open your browser and navigate to `http://localhost:8123/app/` to see the application. The API will be available at `http://localhost:8123`.

## Technologies Used

- [React](https://reactjs.org/) (with [Vite](https://vitejs.dev/)) - For the frontend user interface.
- [Tailwind CSS](https://tailwindcss.com/) - For styling.
- [Shadcn UI](https://ui.shadcn.com/) - For components.
- [LangGraph](https://github.com/langchain-ai/langgraph) - For building the backend research agent.
- [Google Gemini](https://ai.google.dev/models/gemini) - LLM for query generation, reflection, and answer synthesis.

## License

This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details. 


================================================
FILE: backend/.gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
uv.lock

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/


================================================
FILE: backend/LICENSE
================================================
MIT License

Copyright (c) 2025 Philipp Schmid

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: backend/Makefile
================================================
.PHONY: all format lint test tests test_watch integration_tests docker_tests help extended_tests

# Default target executed when no arguments are given to make.
all: help

# Define a variable for the test file path.
TEST_FILE ?= tests/unit_tests/

test:
	uv run --with-editable . pytest $(TEST_FILE)

test_watch:
	uv run --with-editable . ptw --snapshot-update --now . -- -vv tests/unit_tests

test_profile:
	uv run --with-editable . pytest -vv tests/unit_tests/ --profile-svg

extended_tests:
	uv run --with-editable . pytest --only-extended $(TEST_FILE)


######################
# LINTING AND FORMATTING
######################

# Define a variable for Python and notebook files.
PYTHON_FILES=src/
MYPY_CACHE=.mypy_cache
lint format: PYTHON_FILES=.
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --diff-filter=d main | grep -E '\.py$$|\.ipynb$$')
lint_package: PYTHON_FILES=src
lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test

lint lint_diff lint_package lint_tests:
	uv run ruff check .
	[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
	[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
	[ "$(PYTHON_FILES)" = "" ] || uv run mypy --strict $(PYTHON_FILES)
	[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) && uv run mypy --strict $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)

format format_diff:
	uv run ruff format $(PYTHON_FILES)
	uv run ruff check --select I --fix $(PYTHON_FILES)

spell_check:
	codespell --toml pyproject.toml

spell_fix:
	codespell --toml pyproject.toml -w

######################
# HELP
######################

help:
	@echo '----'
	@echo 'format                       - run code formatters'
	@echo 'lint                         - run linters'
	@echo 'test                         - run unit tests'
	@echo 'tests                        - run unit tests'
	@echo 'test TEST_FILE=<test_file>   - run all tests in file'
	@echo 'test_watch                   - run unit tests in watch mode'



================================================
FILE: backend/examples/cli_research.py
================================================
import argparse
from langchain_core.messages import HumanMessage
from agent.graph import graph


def main() -> None:
    """Run the research agent from the command line."""
    parser = argparse.ArgumentParser(description="Run the LangGraph research agent")
    parser.add_argument("question", help="Research question")
    parser.add_argument(
        "--initial-queries",
        type=int,
        default=3,
        help="Number of initial search queries",
    )
    parser.add_argument(
        "--max-loops",
        type=int,
        default=2,
        help="Maximum number of research loops",
    )
    parser.add_argument(
        "--reasoning-model",
        default="gemini-2.5-pro-preview-05-06",
        help="Model for the final answer",
    )
    args = parser.parse_args()

    state = {
        "messages": [HumanMessage(content=args.question)],
        "initial_search_query_count": args.initial_queries,
        "max_research_loops": args.max_loops,
        "reasoning_model": args.reasoning_model,
    }

    result = graph.invoke(state)
    messages = result.get("messages", [])
    if messages:
        print(messages[-1].content)


if __name__ == "__main__":
    main()


================================================
FILE: backend/langgraph.json
================================================
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent/graph.py:graph"
  },
  "http": {
    "app": "./src/agent/app.py:app"
  },
  "env": ".env"
}


================================================
FILE: backend/pyproject.toml
================================================
[project]
name = "agent"
version = "0.0.1"
description = "Backend for the LangGraph agent"
authors = [
    { name = "Philipp Schmid", email = "schmidphilipp1995@gmail.com" },
]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.11,<4.0"
dependencies = [
    "langgraph>=0.2.6",
    "langchain>=0.3.19",
    "langchain-google-genai",
    "python-dotenv>=1.0.1",
    "langgraph-sdk>=0.1.57",
    "langgraph-cli",
    "langgraph-api",
    "fastapi",
    "google-genai",
]


[project.optional-dependencies]
dev = ["mypy>=1.11.1", "ruff>=0.6.1"]

[build-system]
requires = ["setuptools>=73.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.ruff]
lint.select = [
    "E",    # pycodestyle
    "F",    # pyflakes
    "I",    # isort
    "D",    # pydocstyle
    "D401", # First line should be in imperative mood
    "T201",
    "UP",
]
lint.ignore = [
    "UP006",
    "UP007",
    # We actually do want to import from typing_extensions
    "UP035",
    # Relax the convention by _not_ requiring documentation for every function parameter.
    "D417",
    "E501",
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]
[tool.ruff.lint.pydocstyle]
convention = "google"

[dependency-groups]
dev = [
    "langgraph-cli[inmem]>=0.1.71",
    "pytest>=8.3.5",
]


================================================
FILE: backend/src/agent/__init__.py
================================================
from agent.graph import graph

__all__ = ["graph"]


================================================
FILE: backend/src/agent/app.py
================================================
# mypy: disable - error - code = "no-untyped-def,misc"
import pathlib
from fastapi import FastAPI, Response
from fastapi.staticfiles import StaticFiles

# Define the FastAPI app
app = FastAPI()


def create_frontend_router(build_dir="../frontend/dist"):
    """Creates a router to serve the React frontend.

    Args:
        build_dir: Path to the React build directory relative to this file.

    Returns:
        A Starlette application serving the frontend.
    """
    build_path = pathlib.Path(__file__).parent.parent.parent / build_dir

    if not build_path.is_dir() or not (build_path / "index.html").is_file():
        print(
            f"WARN: Frontend build directory not found or incomplete at {build_path}. Serving frontend will likely fail."
        )
        # Return a dummy router if build isn't ready
        from starlette.routing import Route

        async def dummy_frontend(request):
            return Response(
                "Frontend not built. Run 'npm run build' in the frontend directory.",
                media_type="text/plain",
                status_code=503,
            )

        return Route("/{path:path}", endpoint=dummy_frontend)

    return StaticFiles(directory=build_path, html=True)


# Mount the frontend under /app to not conflict with the LangGraph API routes
app.mount(
    "/app",
    create_frontend_router(),
    name="frontend",
)


================================================
FILE: backend/src/agent/configuration.py
================================================
import os
from pydantic import BaseModel, Field
from typing import Any, Optional

from langchain_core.runnables import RunnableConfig


class Configuration(BaseModel):
    """The configuration for the agent."""

    query_generator_model: str = Field(
        default="gemini-2.0-flash",
        metadata={
            "description": "The name of the language model to use for the agent's query generation."
        },
    )

    reflection_model: str = Field(
        default="gemini-2.5-flash",
        metadata={
            "description": "The name of the language model to use for the agent's reflection."
        },
    )

    answer_model: str = Field(
        default="gemini-2.5-pro",
        metadata={
            "description": "The name of the language model to use for the agent's answer."
        },
    )

    number_of_initial_queries: int = Field(
        default=3,
        metadata={"description": "The number of initial search queries to generate."},
    )

    max_research_loops: int = Field(
        default=2,
        metadata={"description": "The maximum number of research loops to perform."},
    )

    @classmethod
    def from_runnable_config(
        cls, config: Optional[RunnableConfig] = None
    ) -> "Configuration":
        """Create a Configuration instance from a RunnableConfig."""
        configurable = (
            config["configurable"] if config and "configurable" in config else {}
        )

        # Get raw values from environment or config
        raw_values: dict[str, Any] = {
            name: os.environ.get(name.upper(), configurable.get(name))
            for name in cls.model_fields.keys()
        }

        # Filter out None values
        values = {k: v for k, v in raw_values.items() if v is not None}

        return cls(**values)


================================================
FILE: backend/src/agent/graph.py
================================================
import os

from agent.tools_and_schemas import SearchQueryList, Reflection
from dotenv import load_dotenv
from langchain_core.messages import AIMessage
from langgraph.types import Send
from langgraph.graph import StateGraph
from langgraph.graph import START, END
from langchain_core.runnables import RunnableConfig
from google.genai import Client

from agent.state import (
    OverallState,
    QueryGenerationState,
    ReflectionState,
    WebSearchState,
)
from agent.configuration import Configuration
from agent.prompts import (
    get_current_date,
    query_writer_instructions,
    web_searcher_instructions,
    reflection_instructions,
    answer_instructions,
)
from langchain_google_genai import ChatGoogleGenerativeAI
from agent.utils import (
    get_citations,
    get_research_topic,
    insert_citation_markers,
    resolve_urls,
)

load_dotenv()

if os.getenv("GEMINI_API_KEY") is None:
    raise ValueError("GEMINI_API_KEY is not set")

# Used for Google Search API
genai_client = Client(api_key=os.getenv("GEMINI_API_KEY"))


# Nodes
def generate_query(state: OverallState, config: RunnableConfig) -> QueryGenerationState:
    """LangGraph node that generates search queries based on the User's question.

    Uses Gemini 2.0 Flash to create an optimized search queries for web research based on
    the User's question.

    Args:
        state: Current graph state containing the User's question
        config: Configuration for the runnable, including LLM provider settings

    Returns:
        Dictionary with state update, including search_query key containing the generated queries
    """
    configurable = Configuration.from_runnable_config(config)

    # check for custom initial search query count
    if state.get("initial_search_query_count") is None:
        state["initial_search_query_count"] = configurable.number_of_initial_queries

    # init Gemini 2.0 Flash
    llm = ChatGoogleGenerativeAI(
        model=configurable.query_generator_model,
        temperature=1.0,
        max_retries=2,
        api_key=os.getenv("GEMINI_API_KEY"),
    )
    structured_llm = llm.with_structured_output(SearchQueryList)

    # Format the prompt
    current_date = get_current_date()
    formatted_prompt = query_writer_instructions.format(
        current_date=current_date,
        research_topic=get_research_topic(state["messages"]),
        number_queries=state["initial_search_query_count"],
    )
    # Generate the search queries
    result = structured_llm.invoke(formatted_prompt)
    return {"search_query": result.query}


def continue_to_web_research(state: QueryGenerationState):
    """LangGraph node that sends the search queries to the web research node.

    This is used to spawn n number of web research nodes, one for each search query.
    """
    return [
        Send("web_research", {"search_query": search_query, "id": int(idx)})
        for idx, search_query in enumerate(state["search_query"])
    ]


def web_research(state: WebSearchState, config: RunnableConfig) -> OverallState:
    """LangGraph node that performs web research using the native Google Search API tool.

    Executes a web search using the native Google Search API tool in combination with Gemini 2.0 Flash.

    Args:
        state: Current graph state containing the search query and research loop count
        config: Configuration for the runnable, including search API settings

    Returns:
        Dictionary with state update, including sources_gathered, research_loop_count, and web_research_results
    """
    # Configure
    configurable = Configuration.from_runnable_config(config)
    formatted_prompt = web_searcher_instructions.format(
        current_date=get_current_date(),
        research_topic=state["search_query"],
    )

    # Uses the google genai client as the langchain client doesn't return grounding metadata
    response = genai_client.models.generate_content(
        model=configurable.query_generator_model,
        contents=formatted_prompt,
        config={
            "tools": [{"google_search": {}}],
            "temperature": 0,
        },
    )
    # resolve the urls to short urls for saving tokens and time
    resolved_urls = resolve_urls(
        response.candidates[0].grounding_metadata.grounding_chunks, state["id"]
    )
    # Gets the citations and adds them to the generated text
    citations = get_citations(response, resolved_urls)
    modified_text = insert_citation_markers(response.text, citations)
    sources_gathered = [item for citation in citations for item in citation["segments"]]

    return {
        "sources_gathered": sources_gathered,
        "search_query": [state["search_query"]],
        "web_research_result": [modified_text],
    }


def reflection(state: OverallState, config: RunnableConfig) -> ReflectionState:
    """LangGraph node that identifies knowledge gaps and generates potential follow-up queries.

    Analyzes the current summary to identify areas for further research and generates
    potential follow-up queries. Uses structured output to extract
    the follow-up query in JSON format.

    Args:
        state: Current graph state containing the running summary and research topic
        config: Configuration for the runnable, including LLM provider settings

    Returns:
        Dictionary with state update, including search_query key containing the generated follow-up query
    """
    configurable = Configuration.from_runnable_config(config)
    # Increment the research loop count and get the reasoning model
    state["research_loop_count"] = state.get("research_loop_count", 0) + 1
    reasoning_model = state.get("reasoning_model", configurable.reflection_model)

    # Format the prompt
    current_date = get_current_date()
    formatted_prompt = reflection_instructions.format(
        current_date=current_date,
        research_topic=get_research_topic(state["messages"]),
        summaries="\n\n---\n\n".join(state["web_research_result"]),
    )
    # init Reasoning Model
    llm = ChatGoogleGenerativeAI(
        model=reasoning_model,
        temperature=1.0,
        max_retries=2,
        api_key=os.getenv("GEMINI_API_KEY"),
    )
    result = llm.with_structured_output(Reflection).invoke(formatted_prompt)

    return {
        "is_sufficient": result.is_sufficient,
        "knowledge_gap": result.knowledge_gap,
        "follow_up_queries": result.follow_up_queries,
        "research_loop_count": state["research_loop_count"],
        "number_of_ran_queries": len(state["search_query"]),
    }


def evaluate_research(
    state: ReflectionState,
    config: RunnableConfig,
) -> OverallState:
    """LangGraph routing function that determines the next step in the research flow.

    Controls the research loop by deciding whether to continue gathering information
    or to finalize the summary based on the configured maximum number of research loops.

    Args:
        state: Current graph state containing the research loop count
        config: Configuration for the runnable, including max_research_loops setting

    Returns:
        String literal indicating the next node to visit ("web_research" or "finalize_summary")
    """
    configurable = Configuration.from_runnable_config(config)
    max_research_loops = (
        state.get("max_research_loops")
        if state.get("max_research_loops") is not None
        else configurable.max_research_loops
    )
    if state["is_sufficient"] or state["research_loop_count"] >= max_research_loops:
        return "finalize_answer"
    else:
        return [
            Send(
                "web_research",
                {
                    "search_query": follow_up_query,
                    "id": state["number_of_ran_queries"] + int(idx),
                },
            )
            for idx, follow_up_query in enumerate(state["follow_up_queries"])
        ]


def finalize_answer(state: OverallState, config: RunnableConfig):
    """LangGraph node that finalizes the research summary.

    Prepares the final output by deduplicating and formatting sources, then
    combining them with the running summary to create a well-structured
    research report with proper citations.

    Args:
        state: Current graph state containing the running summary and sources gathered

    Returns:
        Dictionary with state update, including running_summary key containing the formatted final summary with sources
    """
    configurable = Configuration.from_runnable_config(config)
    reasoning_model = state.get("reasoning_model") or configurable.answer_model

    # Format the prompt
    current_date = get_current_date()
    formatted_prompt = answer_instructions.format(
        current_date=current_date,
        research_topic=get_research_topic(state["messages"]),
        summaries="\n---\n\n".join(state["web_research_result"]),
    )

    # init Reasoning Model, default to Gemini 2.5 Flash
    llm = ChatGoogleGenerativeAI(
        model=reasoning_model,
        temperature=0,
        max_retries=2,
        api_key=os.getenv("GEMINI_API_KEY"),
    )
    result = llm.invoke(formatted_prompt)

    # Replace the short urls with the original urls and add all used urls to the sources_gathered
    unique_sources = []
    for source in state["sources_gathered"]:
        if source["short_url"] in result.content:
            result.content = result.content.replace(
                source["short_url"], source["value"]
            )
            unique_sources.append(source)

    return {
        "messages": [AIMessage(content=result.content)],
        "sources_gathered": unique_sources,
    }


# Create our Agent Graph
builder = StateGraph(OverallState, config_schema=Configuration)

# Define the nodes we will cycle between
builder.add_node("generate_query", generate_query)
builder.add_node("web_research", web_research)
builder.add_node("reflection", reflection)
builder.add_node("finalize_answer", finalize_answer)

# Set the entrypoint as `generate_query`
# This means that this node is the first one called
builder.add_edge(START, "generate_query")
# Add conditional edge to continue with search queries in a parallel branch
builder.add_conditional_edges(
    "generate_query", continue_to_web_research, ["web_research"]
)
# Reflect on the web research
builder.add_edge("web_research", "reflection")
# Evaluate the research
builder.add_conditional_edges(
    "reflection", evaluate_research, ["web_research", "finalize_answer"]
)
# Finalize the answer
builder.add_edge("finalize_answer", END)

graph = builder.compile(name="pro-search-agent")


================================================
FILE: backend/src/agent/prompts.py
================================================
from datetime import datetime


# Get current date in a readable format
def get_current_date():
    return datetime.now().strftime("%B %d, %Y")


query_writer_instructions = """Your goal is to generate sophisticated and diverse web search queries. These queries are intended for an advanced automated web research tool capable of analyzing complex results, following links, and synthesizing information.

Instructions:
- Always prefer a single search query, only add another query if the original question requests multiple aspects or elements and one query is not enough.
- Each query should focus on one specific aspect of the original question.
- Don't produce more than {number_queries} queries.
- Queries should be diverse, if the topic is broad, generate more than 1 query.
- Don't generate multiple similar queries, 1 is enough.
- Query should ensure that the most current information is gathered. The current date is {current_date}.

Format: 
- Format your response as a JSON object with ALL two of these exact keys:
   - "rationale": Brief explanation of why these queries are relevant
   - "query": A list of search queries

Example:

Topic: What revenue grew more last year apple stock or the number of people buying an iphone
```json
{{
    "rationale": "To answer this comparative growth question accurately, we need specific data points on Apple's stock performance and iPhone sales metrics. These queries target the precise financial information needed: company revenue trends, product-specific unit sales figures, and stock price movement over the same fiscal period for direct comparison.",
    "query": ["Apple total revenue growth fiscal year 2024", "iPhone unit sales growth fiscal year 2024", "Apple stock price growth fiscal year 2024"],
}}
```

Context: {research_topic}"""


web_searcher_instructions = """Conduct targeted Google Searches to gather the most recent, credible information on "{research_topic}" and synthesize it into a verifiable text artifact.

Instructions:
- Query should ensure that the most current information is gathered. The current date is {current_date}.
- Conduct multiple, diverse searches to gather comprehensive information.
- Consolidate key findings while meticulously tracking the source(s) for each specific piece of information.
- The output should be a well-written summary or report based on your search findings. 
- Only include the information found in the search results, don't make up any information.

Research Topic:
{research_topic}
"""

reflection_instructions = """You are an expert research assistant analyzing summaries about "{research_topic}".

Instructions:
- Identify knowledge gaps or areas that need deeper exploration and generate a follow-up query. (1 or multiple).
- If provided summaries are sufficient to answer the user's question, don't generate a follow-up query.
- If there is a knowledge gap, generate a follow-up query that would help expand your understanding.
- Focus on technical details, implementation specifics, or emerging trends that weren't fully covered.

Requirements:
- Ensure the follow-up query is self-contained and includes necessary context for web search.

Output Format:
- Format your response as a JSON object with these exact keys:
   - "is_sufficient": true or false
   - "knowledge_gap": Describe what information is missing or needs clarification
   - "follow_up_queries": Write a specific question to address this gap

Example:
```json
{{
    "is_sufficient": true, // or false
    "knowledge_gap": "The summary lacks information about performance metrics and benchmarks", // "" if is_sufficient is true
    "follow_up_queries": ["What are typical performance benchmarks and metrics used to evaluate [specific technology]?"] // [] if is_sufficient is true
}}
```

Reflect carefully on the Summaries to identify knowledge gaps and produce a follow-up query. Then, produce your output following this JSON format:

Summaries:
{summaries}
"""

answer_instructions = """Generate a high-quality answer to the user's question based on the provided summaries.

Instructions:
- The current date is {current_date}.
- You are the final step of a multi-step research process, don't mention that you are the final step. 
- You have access to all the information gathered from the previous steps.
- You have access to the user's question.
- Generate a high-quality answer to the user's question based on the provided summaries and the user's question.
- Include the sources you used from the Summaries in the answer correctly, use markdown format (e.g. [apnews](https://vertexaisearch.cloud.google.com/id/1-0)). THIS IS A MUST.

User Context:
- {research_topic}

Summaries:
{summaries}"""


================================================
FILE: backend/src/agent/state.py
================================================
from __future__ import annotations

from dataclasses import dataclass, field
from typing import TypedDict

from langgraph.graph import add_messages
from typing_extensions import Annotated


import operator


class OverallState(TypedDict):
    messages: Annotated[list, add_messages]
    search_query: Annotated[list, operator.add]
    web_research_result: Annotated[list, operator.add]
    sources_gathered: Annotated[list, operator.add]
    initial_search_query_count: int
    max_research_loops: int
    research_loop_count: int
    reasoning_model: str


class ReflectionState(TypedDict):
    is_sufficient: bool
    knowledge_gap: str
    follow_up_queries: Annotated[list, operator.add]
    research_loop_count: int
    number_of_ran_queries: int


class Query(TypedDict):
    query: str
    rationale: str


class QueryGenerationState(TypedDict):
    search_query: list[Query]


class WebSearchState(TypedDict):
    search_query: str
    id: str


@dataclass(kw_only=True)
class SearchStateOutput:
    running_summary: str = field(default=None)  # Final report


================================================
FILE: backend/src/agent/tools_and_schemas.py
================================================
from typing import List
from pydantic import BaseModel, Field


class SearchQueryList(BaseModel):
    query: List[str] = Field(
        description="A list of search queries to be used for web research."
    )
    rationale: str = Field(
        description="A brief explanation of why these queries are relevant to the research topic."
    )


class Reflection(BaseModel):
    is_sufficient: bool = Field(
        description="Whether the provided summaries are sufficient to answer the user's question."
    )
    knowledge_gap: str = Field(
        description="A description of what information is missing or needs clarification."
    )
    follow_up_queries: List[str] = Field(
        description="A list of follow-up queries to address the knowledge gap."
    )


================================================
FILE: backend/src/agent/utils.py
================================================
from typing import Any, Dict, List
from langchain_core.messages import AnyMessage, AIMessage, HumanMessage


def get_research_topic(messages: List[AnyMessage]) -> str:
    """
    Get the research topic from the messages.
    """
    # check if request has a history and combine the messages into a single string
    if len(messages) == 1:
        research_topic = messages[-1].content
    else:
        research_topic = ""
        for message in messages:
            if isinstance(message, HumanMessage):
                research_topic += f"User: {message.content}\n"
            elif isinstance(message, AIMessage):
                research_topic += f"Assistant: {message.content}\n"
    return research_topic


def resolve_urls(urls_to_resolve: List[Any], id: int) -> Dict[str, str]:
    """
    Create a map of the vertex ai search urls (very long) to a short url with a unique id for each url.
    Ensures each original URL gets a consistent shortened form while maintaining uniqueness.
    """
    prefix = f"https://vertexaisearch.cloud.google.com/id/"
    urls = [site.web.uri for site in urls_to_resolve]

    # Create a dictionary that maps each unique URL to its first occurrence index
    resolved_map = {}
    for idx, url in enumerate(urls):
        if url not in resolved_map:
            resolved_map[url] = f"{prefix}{id}-{idx}"

    return resolved_map


def insert_citation_markers(text, citations_list):
    """
    Inserts citation markers into a text string based on start and end indices.

    Args:
        text (str): The original text string.
        citations_list (list): A list of dictionaries, where each dictionary
                               contains 'start_index', 'end_index', and
                               'segment_string' (the marker to insert).
                               Indices are assumed to be for the original text.

    Returns:
        str: The text with citation markers inserted.
    """
    # Sort citations by end_index in descending order.
    # If end_index is the same, secondary sort by start_index descending.
    # This ensures that insertions at the end of the string don't affect
    # the indices of earlier parts of the string that still need to be processed.
    sorted_citations = sorted(
        citations_list, key=lambda c: (c["end_index"], c["start_index"]), reverse=True
    )

    modified_text = text
    for citation_info in sorted_citations:
        # These indices refer to positions in the *original* text,
        # but since we iterate from the end, they remain valid for insertion
        # relative to the parts of the string already processed.
        end_idx = citation_info["end_index"]
        marker_to_insert = ""
        for segment in citation_info["segments"]:
            marker_to_insert += f" [{segment['label']}]({segment['short_url']})"
        # Insert the citation marker at the original end_idx position
        modified_text = (
            modified_text[:end_idx] + marker_to_insert + modified_text[end_idx:]
        )

    return modified_text


def get_citations(response, resolved_urls_map):
    """
    Extracts and formats citation information from a Gemini model's response.

    This function processes the grounding metadata provided in the response to
    construct a list of citation objects. Each citation object includes the
    start and end indices of the text segment it refers to, and a string
    containing formatted markdown links to the supporting web chunks.

    Args:
        response: The response object from the Gemini model, expected to have
                  a structure including `candidates[0].grounding_metadata`.
                  It also relies on a `resolved_map` being available in its
                  scope to map chunk URIs to resolved URLs.

    Returns:
        list: A list of dictionaries, where each dictionary represents a citation
              and has the following keys:
              - "start_index" (int): The starting character index of the cited
                                     segment in the original text. Defaults to 0
                                     if not specified.
              - "end_index" (int): The character index immediately after the
                                   end of the cited segment (exclusive).
              - "segments" (list[str]): A list of individual markdown-formatted
                                        links for each grounding chunk.
              - "segment_string" (str): A concatenated string of all markdown-
                                        formatted links for the citation.
              Returns an empty list if no valid candidates or grounding supports
              are found, or if essential data is missing.
    """
    citations = []

    # Ensure response and necessary nested structures are present
    if not response or not response.candidates:
        return citations

    candidate = response.candidates[0]
    if (
        not hasattr(candidate, "grounding_metadata")
        or not candidate.grounding_metadata
        or not hasattr(candidate.grounding_metadata, "grounding_supports")
    ):
        return citations

    for support in candidate.grounding_metadata.grounding_supports:
        citation = {}

        # Ensure segment information is present
        if not hasattr(support, "segment") or support.segment is None:
            continue  # Skip this support if segment info is missing

        start_index = (
            support.segment.start_index
            if support.segment.start_index is not None
            else 0
        )

        # Ensure end_index is present to form a valid segment
        if support.segment.end_index is None:
            continue  # Skip if end_index is missing, as it's crucial

        # Add 1 to end_index to make it an exclusive end for slicing/range purposes
        # (assuming the API provides an inclusive end_index)
        citation["start_index"] = start_index
        citation["end_index"] = support.segment.end_index

        citation["segments"] = []
        if (
            hasattr(support, "grounding_chunk_indices")
            and support.grounding_chunk_indices
        ):
            for ind in support.grounding_chunk_indices:
                try:
                    chunk = candidate.grounding_metadata.grounding_chunks[ind]
                    resolved_url = resolved_urls_map.get(chunk.web.uri, None)
                    citation["segments"].append(
                        {
                            "label": chunk.web.title.split(".")[:-1][0],
                            "short_url": resolved_url,
                            "value": chunk.web.uri,
                        }
                    )
                except (IndexError, AttributeError, NameError):
                    # Handle cases where chunk, web, uri, or resolved_map might be problematic
                    # For simplicity, we'll just skip adding this particular segment link
                    # In a production system, you might want to log this.
                    pass
        citations.append(citation)
    return citations


================================================
FILE: backend/test-agent.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from agent import graph\n",
    "\n",
    "state = graph.invoke({\"messages\": [{\"role\": \"user\", \"content\": \"Who won the euro 2024\"}], \"max_research_loops\": 3, \"initial_search_query_count\": 3})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'messages': [HumanMessage(content='Who won the euro 2024', additional_kwargs={}, response_metadata={}, id='4b0ccc12-2e74-4a55-a85e-c512e7867c26'),\n",
       "  AIMessage(content=\"Spain won the UEFA Euro 2024 tournament [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q==) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co=) [coachesvoice](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU=) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL).\\n\\nIn the final match held in Berlin, Germany, Spain defeated England 2-1 [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co=) [coachesvoice](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU=) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL). Nico Williams scored the opening goal for Spain, and Mikel Oyarzabal scored the winning goal [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q==) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz). Cole Palmer scored England's only goal [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz).\\n\\nThis victory marked Spain's record fourth European Championship title [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q==) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co=) [coachesvoice](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU=) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL). Spain achieved this by winning all seven of their matches throughout the tournament [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgwKo5lPes5M_GObnkYEzn3QYn1kpTQpx42ANaNqvNMgRsB1Xp2TIXI82SYTSYuLd9ysgKfmlJJy3lcLxrmNBg1R_Z37PCO9vbqIBIbw6DKqMif7pHdtDTS7FUq69c29hkYb_b5w==) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL) [newsbytesapp](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s=).\\n\\nKey individual awards for the tournament went to Spain's players: Rodri was named the Best Player, and Lamine Yamal was named the Best Young Player [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==0) [bet9ja](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q==) [beinsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA==) [thehindu](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEAlCtejIOwzHPUOAXi7oLu469wYzGUJN86oxtrB6YCAHKAocfkxog6XZeXOUjAl9MTY2_jU5igYEOpyy5RZV2jhxGHtahvQGi8Bq0XkJmaFvludGqwpuBn-vFf-MR3As1CXu9GZNh0TW5f3eLPgvDjB6N3IoYaGhGT8BUiqSyZS6k41T-vL9h6fEFMoOFUYhG2S0AfuVZDuyF2nJHJP1WVWZS42csWXEJUDxqhYjyzmx33HaCxKk0Rbe3_Ovc_Kgdagw==).\", additional_kwargs={}, response_metadata={}, id='4c4aa673-391d-48b2-954a-9fcb7053c634')],\n",
       " 'search_query': ['Euro 2024 winner',\n",
       "  \"What were Spain's key team performance statistics throughout Euro 2024?\",\n",
       "  'What specific stats or performances led to Rodri being named Euro 2024 Best Player?',\n",
       "  'What specific stats or performances led to Lamine Yamal being named Euro 2024 Best Young Player?'],\n",
       " 'web_research_result': [\"Spain won the UEFA Euro 2024, securing their record fourth title [youtube](https://vertexaisearch.cloud.google.com/id/0-0) [aljazeera](https://vertexaisearch.cloud.google.com/id/0-1) [foxsports](https://vertexaisearch.cloud.google.com/id/0-2) [wikipedia](https://vertexaisearch.cloud.google.com/id/0-3) [youtube](https://vertexaisearch.cloud.google.com/id/0-4) [uefa](https://vertexaisearch.cloud.google.com/id/0-5). The final match was held in Berlin, Germany, where Spain defeated England 2-1 [olympics](https://vertexaisearch.cloud.google.com/id/0-6) [aljazeera](https://vertexaisearch.cloud.google.com/id/0-1) [foxsports](https://vertexaisearch.cloud.google.com/id/0-2). Spain's Nico Williams scored the opening goal, and Mikel Oyarzabal scored the winning goal [youtube](https://vertexaisearch.cloud.google.com/id/0-0) [aljazeera](https://vertexaisearch.cloud.google.com/id/0-1) [foxsports](https://vertexaisearch.cloud.google.com/id/0-2). England's Cole Palmer scored their lone goal [olympics](https://vertexaisearch.cloud.google.com/id/0-6) [aljazeera](https://vertexaisearch.cloud.google.com/id/0-1) [foxsports](https://vertexaisearch.cloud.google.com/id/0-2).\\n\\nSpain won all seven of their matches in the tournament [youtube](https://vertexaisearch.cloud.google.com/id/0-7) [wikipedia](https://vertexaisearch.cloud.google.com/id/0-3) [youtube](https://vertexaisearch.cloud.google.com/id/0-4) [uefa](https://vertexaisearch.cloud.google.com/id/0-5). In the quarter-finals, Spain defeated Germany 2-1 after extra time [olympics](https://vertexaisearch.cloud.google.com/id/0-6) [wikipedia](https://vertexaisearch.cloud.google.com/id/0-3). In the semi-finals, Spain beat France 2-1 [olympics](https://vertexaisearch.cloud.google.com/id/0-6) [aljazeera](https://vertexaisearch.cloud.google.com/id/0-8) [wikipedia](https://vertexaisearch.cloud.google.com/id/0-3). Lamine Yamal became the youngest player to score in a UEFA European Championship [ndtv](https://vertexaisearch.cloud.google.com/id/0-9) [uefa](https://vertexaisearch.cloud.google.com/id/0-5).\\n\\nThe top scorers of the tournament were Harry Kane, Georges Mikautadze, Jamal Musiala, Cody Gakpo, Ivan Schranz and Dani Olmo, each with 3 goals [wikipedia](https://vertexaisearch.cloud.google.com/id/0-10). Rodri was named best player and Lamine Yamal best young player of the tournament [wikipedia](https://vertexaisearch.cloud.google.com/id/0-10). Luis de la Fuente was the coach who led Spain to victory [transfermarkt](https://vertexaisearch.cloud.google.com/id/0-11).\\n\",\n",
       "  \"Spain won Euro 2024, defeating England 2-1 in the final to secure their record fourth European Championship [aljazeera](https://vertexaisearch.cloud.google.com/id/1-0) [coachesvoice](https://vertexaisearch.cloud.google.com/id/1-1) [aljazeera](https://vertexaisearch.cloud.google.com/id/1-2) [wikipedia](https://vertexaisearch.cloud.google.com/id/1-3). They won all seven of their matches in the competition [wikipedia](https://vertexaisearch.cloud.google.com/id/1-3).\\n\\nHere's a summary of Spain's key team performance statistics throughout Euro 2024:\\n\\n**General Stats:**\\n\\n*   **Goals Scored:** Spain scored 15 goals throughout the tournament, setting a new record for most goals in a single European Championship [wikipedia](https://vertexaisearch.cloud.google.com/id/1-3). They scored 13 goals before the final [thehindu](https://vertexaisearch.cloud.google.com/id/1-4) [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5).\\n*   **Goals Conceded:** Spain conceded only three goals in the tournament [thehindu](https://vertexaisearch.cloud.google.com/id/1-4) [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5).\\n*   **Wins:** Spain had a 100% win record in Euro 2024 [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5). They won all six of their matches leading up to the final [aljazeera](https://vertexaisearch.cloud.google.com/id/1-2) [sportsmole](https://vertexaisearch.cloud.google.com/id/1-6).\\n*   **Clean Sheets:** Spain had three clean sheets in Euro 2024 [thehindu](https://vertexaisearch.cloud.google.com/id/1-4) [thehindu](https://vertexaisearch.cloud.google.com/id/1-7).\\n*   **Possession:** Spain averaged 57.3% possession during the tournament [thehindu](https://vertexaisearch.cloud.google.com/id/1-4). They often maintained possession for over 65% of their matches [spanishprofootball](https://vertexaisearch.cloud.google.com/id/1-8).\\n*   **Passing Accuracy:** Spain had a passing accuracy of 90% [thehindu](https://vertexaisearch.cloud.google.com/id/1-4).\\n*   **Ball Recoveries:** Spain led the tournament in ball recoveries with 255 [thehindu](https://vertexaisearch.cloud.google.com/id/1-4).\\n*   **Shots:** Spain had 80 shots (excluding blocks), with 38 on target [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5). They had the most attempts in Euro 2024, with 108, 37 of which were on target [thehindu](https://vertexaisearch.cloud.google.com/id/1-4).\\n*   **Chances Created:** Spain created 85 chances [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5).\\n*   **Tackles:** Spain made 92 tackles [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5).\\n\\n**Team Composition and Tactics:**\\n\\n*   The squad featured a blend of experienced players and young talents [totalfootballanalysis](https://vertexaisearch.cloud.google.com/id/1-9).\\n*   Luis de la Fuente employed multifaceted tactics, adapting to different opponents [totalfootballanalysis](https://vertexaisearch.cloud.google.com/id/1-10).\\n*   Spain dominated possession and controlled the tempo of matches [spanishprofootball](https://vertexaisearch.cloud.google.com/id/1-8).\\n*   They utilized a high pressing strategy and quick recovery [spanishprofootball](https://vertexaisearch.cloud.google.com/id/1-8).\\n*   Fluid midfield dynamics were powered by players like Pedri, Rodri, and Gavi [spanishprofootball](https://vertexaisearch.cloud.google.com/id/1-8).\\n\\n**Individual Player Stats:**\\n\\n*   **Dani Olmo:** Joint leading goal scorer with three goals [thehindu](https://vertexaisearch.cloud.google.com/id/1-4) [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5). He also provided two assists [newsbytesapp](https://vertexaisearch.cloud.google.com/id/1-5).\\n*   **Lamine Yamal:** Joint assist leader with three assists [thehindu](https://vertexaisearch.cloud.google.com/id/1-4) [thehindu](https://vertexaisearch.cloud.google.com/id/1-7). He also became the youngest-ever Euros scorer [sportsmole](https://vertexaisearch.cloud.google.com/id/1-6) [wikipedia](https://vertexaisearch.cloud.google.com/id/1-3).\\n*   **Rodri:** Completed the most passes for Spain [thehindu](https://vertexaisearch.cloud.google.com/id/1-4) [thehindu](https://vertexaisearch.cloud.google.com/id/1-7).\\n*   **Aymeric Laporte:** Recovered the ball the most number of times for Spain defensively [thehindu](https://vertexaisearch.cloud.google.com/id/1-4).\\n*   **Unai Simon:** Conceded three goals and made 12 saves in five matches [thehindu](https://vertexaisearch.cloud.google.com/id/1-4).\\n*   **Nico Williams:** Named Man of the Match in the final [wikipedia](https://vertexaisearch.cloud.google.com/id/1-3).\\n\\nSpain's coach, Luis de la Fuente, emphasized versatility, pace on the wings, control in the middle, and a solid defense as key to their balance [coachesvoice](https://vertexaisearch.cloud.google.com/id/1-1).\\n\",\n",
       "  'Rodri was named Euro 2024 Best Player due to his consistent and brilliant performances throughout the tournament [bet9ja](https://vertexaisearch.cloud.google.com/id/2-0). He was the centerpiece of Spain\\'s midfield, playing a crucial role in nearly every game [europeanchampionship2024](https://vertexaisearch.cloud.google.com/id/2-1). Here\\'s a breakdown of the specific stats and performances that led to the award:\\n\\n*   **Key Role in Spain\\'s Victories:** Rodri played a crucial role in Spain\\'s victories over Germany and France [bet9ja](https://vertexaisearch.cloud.google.com/id/2-0).\\n*   **Midfield Dominance:** Rodri\\'s consistent presence in midfield was pivotal for Spain [europeanchampionship2024](https://vertexaisearch.cloud.google.com/id/2-1).\\n*   **Only Goal:** He scored a goal in Spain\\'s 4-1 win over Georgia in the Last 16 [indiatimes](https://vertexaisearch.cloud.google.com/id/2-2) [bet9ja](https://vertexaisearch.cloud.google.com/id/2-0).\\n*   **Passing Accuracy:** Rodri had a remarkable passing accuracy of 92.84% [uefa](https://vertexaisearch.cloud.google.com/id/2-3) [mancity](https://vertexaisearch.cloud.google.com/id/2-4) [uefa](https://vertexaisearch.cloud.google.com/id/2-5). Only Aymeric Laporte completed more passes for Spain with 411 passes [mancity](https://vertexaisearch.cloud.google.com/id/2-4).\\n*   **Ball Recoveries:** Rodri was also pivotal when out of possession, with just one other midfielder registering more ball recoveries than the Spaniard\\'s 33 [mancity](https://vertexaisearch.cloud.google.com/id/2-4).\\n*   **Leadership:** He led his team with distinction [europeanchampionship2024](https://vertexaisearch.cloud.google.com/id/2-1). Rodri\\'s leadership on the field helped integrate young talents [bet9ja](https://vertexaisearch.cloud.google.com/id/2-0).\\n*   **Strategic Rest:** He started in six of Spain\\'s seven matches, only sitting out the final group stage game against Slovakia, which Spain won 1-0. This strategic rest allowed Rodri to stay fresh for the knockout stages [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n*   **Calmness Under Pressure:** Rodri\\'s calmness under pressure was a recurring theme throughout the tournament [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n*   **Dictating Tempo:** His ability to dictate the tempo of the game, coupled with his defensive prowess, made Rodri indispensable [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n*   **Orchestration:** Rodri\\'s orchestration was crucial in maintaining possession and preventing Germany from gaining momentum in the quarter-final [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n*   **Midfield Control:** His performance against France in the semi-finals was another masterclass in midfield control [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n*   **Composure and Strategic Thinking:** Rodri\\'s composure and strategic thinking brought a sense of reliability to Spain\\'s gameplay [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n*   **Impact in the Final:** Despite his early exit due to a hamstring injury in the final against England, Rodri\\'s presence in the first half helped Spain establish control and set the tone for the rest of the match [upthrust](https://vertexaisearch.cloud.google.com/id/2-6).\\n\\nLuis de la Fuente, the coach of the Spanish team, described Rodri as a \"perfect computer\" due to his precise passing and exceptional understanding of the game [indiatimes](https://vertexaisearch.cloud.google.com/id/2-2) [bet9ja](https://vertexaisearch.cloud.google.com/id/2-0). UEFA\\'s team of technical observers at EURO 2024 also recognized Rodri\\'s influence in central midfield [uefa](https://vertexaisearch.cloud.google.com/id/2-7).\\n',\n",
       "  \"Lamine Yamal was named Euro 2024 Young Player of the Tournament due to several outstanding achievements [uefa](https://vertexaisearch.cloud.google.com/id/3-0) [beinsports](https://vertexaisearch.cloud.google.com/id/3-1) [thehindu](https://vertexaisearch.cloud.google.com/id/3-2). He played in all seven of Spain's Euro 2024 matches, starting in six of them [uefa](https://vertexaisearch.cloud.google.com/id/3-0). He became the youngest player ever to play in the tournament when he started against Croatia at 16 years, 338 days old [uefa](https://vertexaisearch.cloud.google.com/id/3-0) [uefa](https://vertexaisearch.cloud.google.com/id/3-3). In the semi-final against France, he scored a remarkable goal, making him the youngest goalscorer in Euros history at 16 years, 362 days [wikipedia](https://vertexaisearch.cloud.google.com/id/3-4) [uefa](https://vertexaisearch.cloud.google.com/id/3-0) [uefa](https://vertexaisearch.cloud.google.com/id/3-3) [beinsports](https://vertexaisearch.cloud.google.com/id/3-1) [thehindu](https://vertexaisearch.cloud.google.com/id/3-2). Furthermore, he provided four assists during the tournament [wikipedia](https://vertexaisearch.cloud.google.com/id/3-4) [thehindu](https://vertexaisearch.cloud.google.com/id/3-5) [beinsports](https://vertexaisearch.cloud.google.com/id/3-1). In the final, he set up the opening goal against England [uefa](https://vertexaisearch.cloud.google.com/id/3-0).\\n\\nKey statistics from the tournament include [uefa](https://vertexaisearch.cloud.google.com/id/3-6) [uefa](https://vertexaisearch.cloud.google.com/id/3-7):\\n*   7 Matches played\\n*   507 Minutes played\\n*   1 Goal\\n*   4 Assists\\n\\nThese performances led to Yamal receiving the Euro 2024 Young Player of the Tournament award [uefa](https://vertexaisearch.cloud.google.com/id/3-0) [beinsports](https://vertexaisearch.cloud.google.com/id/3-1) [thehindu](https://vertexaisearch.cloud.google.com/id/3-2).\\n\"],\n",
       " 'sources_gathered': [{'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q=='},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig=='},\n",
       "  {'label': 'foxsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o='},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP'},\n",
       "  {'label': 'olympics',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe'},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig=='},\n",
       "  {'label': 'foxsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz'},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q=='},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig=='},\n",
       "  {'label': 'foxsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz'},\n",
       "  {'label': 'olympics',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe'},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig=='},\n",
       "  {'label': 'foxsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz'},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgwKo5lPes5M_GObnkYEzn3QYn1kpTQpx42ANaNqvNMgRsB1Xp2TIXI82SYTSYuLd9ysgKfmlJJy3lcLxrmNBg1R_Z37PCO9vbqIBIbw6DKqMif7pHdtDTS7FUq69c29hkYb_b5w=='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o='},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP'},\n",
       "  {'label': 'olympics',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o='},\n",
       "  {'label': 'olympics',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe'},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-8',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFdu_dxqteuc9vM3oGH5WgEnFuOA6vlmbqof-iVRg2OviD2jzkp1jlCRsWkLfb64cK8TJ_g5jKKfZgmaMCk4LA-E2zjYGBfmsWiHdwfSg5Zv3VDMngM3HxT-VLjWYdBdpvpcBTj9VNRkqSCAjGVL9ar0VAOF0uRF6Z96LFz7G9KCSL50llqG7XLpbXmQTFIV4FUsffI8aQG9KKmIaZ1eGqeWQl2xaaRu6-Pwzqxizg8'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o='},\n",
       "  {'label': 'ndtv',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-9',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFRRH83ij2MgKWwrGFVWMaFDAT0_GKCFdwVIjaYn7DOoBlxXCGR-Y2RTw9AdKH8dYuhXxSxUTaZNXOBac2nknNZpdmwJiGIj51H6lRWREPUPOiKQkfVPJ0f4ubRSJBLm7_QcAkz4BwzJr3OM06jh-41TbNFZ9t6D7WrbzxmSs7x1O5DCnrPM2OeI6Nc0OhVT0AbeC6f_dTaBR9APlQFDrzIsvDIAn-W5eWuEohDs8w6np0eW65RuhQWrofdY8vFz-bsHgK0J3ew'},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-10',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGbyTk6AGj4XMhW66noNoKqe8eCt9-HZUMs6FXsKVyXcMuoG1WLLhBHa9dITcU3zQFJqCzcxPmnu6rj3ZHmJp-n2xdffBtWYFl2pqxmLrEiZONNYLwleA-T8cnaL7gXWfFlJ2jnvB0='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-10',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGbyTk6AGj4XMhW66noNoKqe8eCt9-HZUMs6FXsKVyXcMuoG1WLLhBHa9dITcU3zQFJqCzcxPmnu6rj3ZHmJp-n2xdffBtWYFl2pqxmLrEiZONNYLwleA-T8cnaL7gXWfFlJ2jnvB0='},\n",
       "  {'label': 'transfermarkt',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-11',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFMeGs_GRmx0zI6E_xQZfylxykYcTT9MnZlM3ICoa41Pogn4H-1tLirtdPBOrumyI8s_C9i9cBukjUKHxlPfPP49aqTep7xFPgfe2uQFyG37Acsn9RtVv5VenCS5kfPLDQB7sGR-Tyj6wGyiptaTP1uhRnGgYg0u92BW5OH-MY='},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co='},\n",
       "  {'label': 'coachesvoice',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv'},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU='},\n",
       "  {'label': 'sportsmole',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEVHkRwlOhx_8CZHVDe9XPE_nCs4XYVbx6aIl19aXGNLZxDpcsK5-hcYvMX_et8vasZtMNzmJNTtVd3Vne666vIkkRFUNJxVSBH9bMoGEFcPMcPoxFMUY5LV1YGZjm3n6xbDrkskawWb9MBS-zIIXiXZk7n6TluCji9k3ur3i5-ZhJcgPtAYU-KyfWRTdN0JY4bJt4tAl87Ba9ZInk9YuRlLlAFJ6flaKI-a4cZSXYDQeERhB742z_heWOhDchdvlPfoJaAuYSKKaABrbZQeZw='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG7_kutwvl9NHZQl-k0Vpvj_1I7o8MCX8jNlw6rYXEOGSC9QcRvzaH9ycR3JQUjJLvUhUSeaR7hmJ-qPTgMSfw9US7uXQzTF3CJ-tXnIVI1UC8VRyJoW6fH2r-MRFd5EI-PS494grt4Xey1x7WsaZ_Q7tRcQgVX_EM0JxQK12s8yYAY3TIUpa1L5fZOmsi6ZKq-jrXYOmIV5OTu2AaleBeQE_Z-B10oU2qin2Q3T8w6LP2ispUlVEh54d5fWLcHlEtskrRHC8psjrarTgqn'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'spanishprofootball',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-8',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFG8gCwweIne3MmZpbUnDq24EeYu1w6OpSNeS2U5DtRYUbqRVtIjCnFAOjlXy8XjD8MvbmoNIsRD9rdadJ7tWoyG3T5fj2QvlMdWjCXwpMs7W3D_49AT_d1vWRuu8i_-nAK0WHpo6Wo5abiRpwUyjtFX1rYGXujmwsodi5hUV9Q4Qd1ltJe2cuLhq2cPRU='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'totalfootballanalysis',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-9',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGQ6VynwIGq-L7FKFu-L5vh_TzCWZHXL9rXmzI0uuR1Qexwi1jRKOzvfthF3hl-KGOQhdsEC67FoNIH5ojbVkEVCxdDkX73E9DZUv8Vz_GRld1NHm0gm0i7n-KaZ5w72dfptRLWKyKnfY6UZawwFX3OtwTfYQzHd32wv1s4sk0PIUNOj-FdhnWxaYO-PJSC_aZcwpuVrmEgOqXy0Xk='},\n",
       "  {'label': 'totalfootballanalysis',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-10',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHYrmUdaz_yH2TIUYp54IQ9PxJYBikelavTVFZ5gy2Up1Kaavkf0zeM14L7mTiuPxGEHjaQjn8mLt3I1HdZH34VrBJn6sZ07KzPCX9Bo7gkM44oroevlhaXZtFG65maD7igABOGLBJjZE0Hg17i3EIGTMVfE-OEn0NN53EhY1pLQObHKWJogrtjbLil0XJOV9Ym5_La7JuWQpKo7IiuPlH-w7N_vJHgTDZOxJMY'},\n",
       "  {'label': 'spanishprofootball',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-8',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFG8gCwweIne3MmZpbUnDq24EeYu1w6OpSNeS2U5DtRYUbqRVtIjCnFAOjlXy8XjD8MvbmoNIsRD9rdadJ7tWoyG3T5fj2QvlMdWjCXwpMs7W3D_49AT_d1vWRuu8i_-nAK0WHpo6Wo5abiRpwUyjtFX1rYGXujmwsodi5hUV9Q4Qd1ltJe2cuLhq2cPRU='},\n",
       "  {'label': 'spanishprofootball',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-8',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFG8gCwweIne3MmZpbUnDq24EeYu1w6OpSNeS2U5DtRYUbqRVtIjCnFAOjlXy8XjD8MvbmoNIsRD9rdadJ7tWoyG3T5fj2QvlMdWjCXwpMs7W3D_49AT_d1vWRuu8i_-nAK0WHpo6Wo5abiRpwUyjtFX1rYGXujmwsodi5hUV9Q4Qd1ltJe2cuLhq2cPRU='},\n",
       "  {'label': 'spanishprofootball',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-8',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFG8gCwweIne3MmZpbUnDq24EeYu1w6OpSNeS2U5DtRYUbqRVtIjCnFAOjlXy8XjD8MvbmoNIsRD9rdadJ7tWoyG3T5fj2QvlMdWjCXwpMs7W3D_49AT_d1vWRuu8i_-nAK0WHpo6Wo5abiRpwUyjtFX1rYGXujmwsodi5hUV9Q4Qd1ltJe2cuLhq2cPRU='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG7_kutwvl9NHZQl-k0Vpvj_1I7o8MCX8jNlw6rYXEOGSC9QcRvzaH9ycR3JQUjJLvUhUSeaR7hmJ-qPTgMSfw9US7uXQzTF3CJ-tXnIVI1UC8VRyJoW6fH2r-MRFd5EI-PS494grt4Xey1x7WsaZ_Q7tRcQgVX_EM0JxQK12s8yYAY3TIUpa1L5fZOmsi6ZKq-jrXYOmIV5OTu2AaleBeQE_Z-B10oU2qin2Q3T8w6LP2ispUlVEh54d5fWLcHlEtskrRHC8psjrarTgqn'},\n",
       "  {'label': 'sportsmole',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEVHkRwlOhx_8CZHVDe9XPE_nCs4XYVbx6aIl19aXGNLZxDpcsK5-hcYvMX_et8vasZtMNzmJNTtVd3Vne666vIkkRFUNJxVSBH9bMoGEFcPMcPoxFMUY5LV1YGZjm3n6xbDrkskawWb9MBS-zIIXiXZk7n6TluCji9k3ur3i5-ZhJcgPtAYU-KyfWRTdN0JY4bJt4tAl87Ba9ZInk9YuRlLlAFJ6flaKI-a4cZSXYDQeERhB742z_heWOhDchdvlPfoJaAuYSKKaABrbZQeZw='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG7_kutwvl9NHZQl-k0Vpvj_1I7o8MCX8jNlw6rYXEOGSC9QcRvzaH9ycR3JQUjJLvUhUSeaR7hmJ-qPTgMSfw9US7uXQzTF3CJ-tXnIVI1UC8VRyJoW6fH2r-MRFd5EI-PS494grt4Xey1x7WsaZ_Q7tRcQgVX_EM0JxQK12s8yYAY3TIUpa1L5fZOmsi6ZKq-jrXYOmIV5OTu2AaleBeQE_Z-B10oU2qin2Q3T8w6LP2ispUlVEh54d5fWLcHlEtskrRHC8psjrarTgqn'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHtzvfIxJ0Lv3W7kqwlmY7CzFQxcbvXZqh4rRp3xBgV1vY01z4BRWA-GFu4INE8yFv9DE-eCib4cYnC-iv_PVgR8yPkBv8uRhI93Yf29MdbDoi_LGu46heOoxRLdMV58jlLI5nr-1sxKdfPutXE_rjuKehCswPGD-9RlbPI8NjyUQ69XAAOjDDhAN-MBxcIt_r3raV86AQfoo1UtYpUoUjhTGVcYBisvHRxv8-XjDjkr65nPm9vdaO7j28yCcokCCeGWv074_AGWeewDQWwczQM'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL'},\n",
       "  {'label': 'coachesvoice',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv'},\n",
       "  {'label': 'bet9ja',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA=='},\n",
       "  {'label': 'europeanchampionship2024',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGLP8ZiV1gSErFyEW_mBaeUabOdyppbZMUHyMPTq_nC68lIlF28o4vXtvlsLYq7C-ANzy6iwTpWA4ri2fUKevBCGLRotUVZjLX6Au_hnO-mbPGp_Z7nyomkjYhu2iLoPXbmTS8KmWJr8ZAul7j0XQA-S621HaOSBDk0-XBGiKgISgeQb7Tuc-OGj_NMlPQkzK2y4qrs_TBcPgfh5w=='},\n",
       "  {'label': 'bet9ja',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA=='},\n",
       "  {'label': 'europeanchampionship2024',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGLP8ZiV1gSErFyEW_mBaeUabOdyppbZMUHyMPTq_nC68lIlF28o4vXtvlsLYq7C-ANzy6iwTpWA4ri2fUKevBCGLRotUVZjLX6Au_hnO-mbPGp_Z7nyomkjYhu2iLoPXbmTS8KmWJr8ZAul7j0XQA-S621HaOSBDk0-XBGiKgISgeQb7Tuc-OGj_NMlPQkzK2y4qrs_TBcPgfh5w=='},\n",
       "  {'label': 'indiatimes',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXESZAkHCLh4VxzVLM3prMQrm-sk5x27L8Z70Q4PKkte0vxhTZVZGCY1s5VfC7u5gECHBavdf1DHRCmh77mAaONSIJ78dcaGelojd2Cd5NuJcyQD8juxOERO1zD147S62xcwKy0GZ9Pb64Yj9cPLEx3fDJvTEm4sn013e_e13dTXUQd4m2yHuO72CfsZSbEq-wVsP47O20GMQXLlZov73MCd1uS1eMq9I5cj1QjiIOjiTC484inoCaShm3LTkXA-Jk5L8GvL'},\n",
       "  {'label': 'bet9ja',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFG1hC1YkRcN1pqrLp05aZRwvU2gEv7qauPowe-Co8wgi3HfVrNby2N2i7C3--nu8eYku9ak1DQeH8zJX6XRVKe8psOQ02y3nY5TYcHp-Uk3aZay-sGe4bQZJxVKeF5NS2vtG-h09y3TD_5Aox3V9Yh0z1MYKTBE3Q='},\n",
       "  {'label': 'mancity',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFs-OAM9wd4bVstgUzRYVeAqGBbUckmq77-BWTs9IkGYZc-WwzHbZ1khSV8T91YQpZkd8c6vZTke-Wgkf4O1SdhMLwYXVj3SsWViVDOT-eeZPBI5v1BuE1Wb0wg9XGzxOl66-faN_8zKvvdm-KEzx5OfL7ytu0i-cG9AzKpZPgi5HNCuLw8PwcbPPxXB_QE5VSuCC5uYGMJ'},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHlzgFESCAkLrjGTw5ZRNnr68tC-GrAg3iL61UJu3ZT8SJX4HYaFE6qOIR8iNpXpJDUMDnTwIpG6IFrT6NPqAbQCSoj_GIPC-eBrrVrUqA8IdzvncYpRAubOVFFkNVBZGbY64I2FiF6wA-biL0bKFD02adziHempLuyjM5YvnOFDR1r0A=='},\n",
       "  {'label': 'mancity',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFs-OAM9wd4bVstgUzRYVeAqGBbUckmq77-BWTs9IkGYZc-WwzHbZ1khSV8T91YQpZkd8c6vZTke-Wgkf4O1SdhMLwYXVj3SsWViVDOT-eeZPBI5v1BuE1Wb0wg9XGzxOl66-faN_8zKvvdm-KEzx5OfL7ytu0i-cG9AzKpZPgi5HNCuLw8PwcbPPxXB_QE5VSuCC5uYGMJ'},\n",
       "  {'label': 'mancity',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFs-OAM9wd4bVstgUzRYVeAqGBbUckmq77-BWTs9IkGYZc-WwzHbZ1khSV8T91YQpZkd8c6vZTke-Wgkf4O1SdhMLwYXVj3SsWViVDOT-eeZPBI5v1BuE1Wb0wg9XGzxOl66-faN_8zKvvdm-KEzx5OfL7ytu0i-cG9AzKpZPgi5HNCuLw8PwcbPPxXB_QE5VSuCC5uYGMJ'},\n",
       "  {'label': 'europeanchampionship2024',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGLP8ZiV1gSErFyEW_mBaeUabOdyppbZMUHyMPTq_nC68lIlF28o4vXtvlsLYq7C-ANzy6iwTpWA4ri2fUKevBCGLRotUVZjLX6Au_hnO-mbPGp_Z7nyomkjYhu2iLoPXbmTS8KmWJr8ZAul7j0XQA-S621HaOSBDk0-XBGiKgISgeQb7Tuc-OGj_NMlPQkzK2y4qrs_TBcPgfh5w=='},\n",
       "  {'label': 'bet9ja',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'upthrust',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGAjXBUZABbe0t7dJcEjK-t1A0Gqoyhqd7tS8SrIRQNiNGFC_prv2xazEc-9Xd7vH1V9PgjWB5k8TBWcxtRc8Z2ZHRS3i6cwhdKxLswfDAFFuamfuITm699F648K4tmBYZABT6neMReI4c4sINJAEKqrn6hNzZZjtTt44X78i2dTIOQe74qvl9ofmwm6Q=='},\n",
       "  {'label': 'indiatimes',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXESZAkHCLh4VxzVLM3prMQrm-sk5x27L8Z70Q4PKkte0vxhTZVZGCY1s5VfC7u5gECHBavdf1DHRCmh77mAaONSIJ78dcaGelojd2Cd5NuJcyQD8juxOERO1zD147S62xcwKy0GZ9Pb64Yj9cPLEx3fDJvTEm4sn013e_e13dTXUQd4m2yHuO72CfsZSbEq-wVsP47O20GMQXLlZov73MCd1uS1eMq9I5cj1QjiIOjiTC484inoCaShm3LTkXA-Jk5L8GvL'},\n",
       "  {'label': 'bet9ja',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHcZMzOblU6pNw1gc0QlnRNhCK5VfMY4bW1wPz51w6-AyhvvZnyXcfFxJd4JPdnEfEPD0GB5vHPql6jFppUeKKRysRvwpwaTwaDyFAkvRGab-UAPOOUuK72HsYGrlGVEUHLO6mkzthFd_p8HUaj_JJqlhIaQOuosrZ2y7vf9ouEvd10Uh-rBOqmPRclpkcg3o3WpHhgBY5xNUPEw22V45KhXrqiQhUn5ZSKw3TcsGjla-vA'},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'beinsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA=='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEAlCtejIOwzHPUOAXi7oLu469wYzGUJN86oxtrB6YCAHKAocfkxog6XZeXOUjAl9MTY2_jU5igYEOpyy5RZV2jhxGHtahvQGi8Bq0XkJmaFvludGqwpuBn-vFf-MR3As1CXu9GZNh0TW5f3eLPgvDjB6N3IoYaGhGT8BUiqSyZS6k41T-vL9h6fEFMoOFUYhG2S0AfuVZDuyF2nJHJP1WVWZS42csWXEJUDxqhYjyzmx33HaCxKk0Rbe3_Ovc_Kgdagw=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGIh15GjQTH5sloOsTsyL7xu4UxiL1iUhCOmOYuQn2I3oTzOmC8I6vpqG7puUq20dPwFWNyGzUT1m4eiDf3XTrvfO-BRbRz80it26jo1H0Wq6Dr8jI2xYQbW8suUGcHTE6aT6FUa57v2oHiBP2yTBe4FyTP3w-us4RhdAgxy32VvGJhczpHTp36FWBtxK-ESh5KTcPHflNroQkKP0rE17DLYrMfoQVNGf41jeTM2YCvoSeymtFHc-wvySulmtIFlQ=='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGEIQE8ljNdOpLHYgNFDjPekNZfDVP_W7aAbZgzTSgSraVNbalzctN2llZ3do9v9r7sRqxOXioKpebZrVCBnux58qbMLK8wpc4MmOKDRG3bAD8hwE7xMl_InBIfHuIMbuZ_twEC'},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGIh15GjQTH5sloOsTsyL7xu4UxiL1iUhCOmOYuQn2I3oTzOmC8I6vpqG7puUq20dPwFWNyGzUT1m4eiDf3XTrvfO-BRbRz80it26jo1H0Wq6Dr8jI2xYQbW8suUGcHTE6aT6FUa57v2oHiBP2yTBe4FyTP3w-us4RhdAgxy32VvGJhczpHTp36FWBtxK-ESh5KTcPHflNroQkKP0rE17DLYrMfoQVNGf41jeTM2YCvoSeymtFHc-wvySulmtIFlQ=='},\n",
       "  {'label': 'beinsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA=='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEAlCtejIOwzHPUOAXi7oLu469wYzGUJN86oxtrB6YCAHKAocfkxog6XZeXOUjAl9MTY2_jU5igYEOpyy5RZV2jhxGHtahvQGi8Bq0XkJmaFvludGqwpuBn-vFf-MR3As1CXu9GZNh0TW5f3eLPgvDjB6N3IoYaGhGT8BUiqSyZS6k41T-vL9h6fEFMoOFUYhG2S0AfuVZDuyF2nJHJP1WVWZS42csWXEJUDxqhYjyzmx33HaCxKk0Rbe3_Ovc_Kgdagw=='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGEIQE8ljNdOpLHYgNFDjPekNZfDVP_W7aAbZgzTSgSraVNbalzctN2llZ3do9v9r7sRqxOXioKpebZrVCBnux58qbMLK8wpc4MmOKDRG3bAD8hwE7xMl_InBIfHuIMbuZ_twEC'},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEMtKj653gIrpD8CpAaGVkViYwCyEhmCj8w_zAO27Y874XFgkkvvuoVtNU8EXpJiVPDKnShMChgFWK7PnBV3QvbRcOYN268OM1yuPY9DK17q4-9-oGuqw_TYIaEECQxe5JpzVXGBtNidMqlMxM902_iqlm5wQnzMjMO8Vuqj5V3MdMdYj9O_rde6dkewGJFWGMZvPHAySCCMoZPoERD5ErPcaRjpyFQp7VjKoUWvG-mBQkBEn7NP93nxb49ZKVqpt_JhQPlk2HTq-yVyXxh_loL1JE6'},\n",
       "  {'label': 'beinsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXH4pOTdyyQxNadaQjyKh8oQuOvZAoJv3h8lUaUpf_DBGcg11x3NZ3be0osuI4NKZmKmtGvI4IXelQLdf0gHIZB2h6x13iHVuz5kCoohIkFmaL2HaKjkQlzZw3KDIAr8j3KoVbWNXnx34wDW2qtFTmECR6UBkLFiy0VEjcYwowJ_8ex10JM14KzcvA=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXE1pV-r8eRyTFjoRu12FcAetb5Lb1qPrl-GdPkb649C5b4zo99jtjYGI4y5B2EiF6SE413Ct9omXh3NwD0-r8rGqOMROSYEsfwUaFafM10vFtJGs_eWVcMMLVqgqNELj9BrG4JeBEHbYjDRSlCmVMQcWbIHC28goFDBa-dqi3Q='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'beinsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA=='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEAlCtejIOwzHPUOAXi7oLu469wYzGUJN86oxtrB6YCAHKAocfkxog6XZeXOUjAl9MTY2_jU5igYEOpyy5RZV2jhxGHtahvQGi8Bq0XkJmaFvludGqwpuBn-vFf-MR3As1CXu9GZNh0TW5f3eLPgvDjB6N3IoYaGhGT8BUiqSyZS6k41T-vL9h6fEFMoOFUYhG2S0AfuVZDuyF2nJHJP1WVWZS42csWXEJUDxqhYjyzmx33HaCxKk0Rbe3_Ovc_Kgdagw=='},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q=='},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig=='},\n",
       "  {'label': 'foxsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz'},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o='},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-4',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP'},\n",
       "  {'label': 'olympics',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-6',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe'},\n",
       "  {'label': 'youtube',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/0-7',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgwKo5lPes5M_GObnkYEzn3QYn1kpTQpx42ANaNqvNMgRsB1Xp2TIXI82SYTSYuLd9ysgKfmlJJy3lcLxrmNBg1R_Z37PCO9vbqIBIbw6DKqMif7pHdtDTS7FUq69c29hkYb_b5w=='},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co='},\n",
       "  {'label': 'coachesvoice',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv'},\n",
       "  {'label': 'aljazeera',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU='},\n",
       "  {'label': 'wikipedia',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-3',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL'},\n",
       "  {'label': 'newsbytesapp',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/1-5',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s='},\n",
       "  {'label': 'bet9ja',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/2-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA=='},\n",
       "  {'label': 'uefa',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-0',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q=='},\n",
       "  {'label': 'beinsports',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-1',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA=='},\n",
       "  {'label': 'thehindu',\n",
       "   'short_url': 'https://vertexaisearch.cloud.google.com/id/3-2',\n",
       "   'value': 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEAlCtejIOwzHPUOAXi7oLu469wYzGUJN86oxtrB6YCAHKAocfkxog6XZeXOUjAl9MTY2_jU5igYEOpyy5RZV2jhxGHtahvQGi8Bq0XkJmaFvludGqwpuBn-vFf-MR3As1CXu9GZNh0TW5f3eLPgvDjB6N3IoYaGhGT8BUiqSyZS6k41T-vL9h6fEFMoOFUYhG2S0AfuVZDuyF2nJHJP1WVWZS42csWXEJUDxqhYjyzmx33HaCxKk0Rbe3_Ovc_Kgdagw=='}],\n",
       " 'initial_search_query_count': 3,\n",
       " 'max_research_loops': 3,\n",
       " 'research_loop_count': 2}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Spain won the UEFA Euro 2024 tournament [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q==) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co=) [coachesvoice](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU=) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL).\n",
       "\n",
       "In the final match held in Berlin, Germany, Spain defeated England 2-1 [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co=) [coachesvoice](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU=) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL). Nico Williams scored the opening goal for Spain, and Mikel Oyarzabal scored the winning goal [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q==) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz). Cole Palmer scored England's only goal [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFARil0pwjYQuFrDObawlDzu-eVtUPC4_nINjcXT-mlTL3MDgVPI83UB8gWS1rzGZkaMEmAUIeAzo2ihpMXUsWibzVzeAdQ7nUyqAOq0En87kpfuISduBuWI3__7yJw-vmdApD56-_G2ZhhZC4d_ll2iyNBaZHxxdNqXbb76mUiq99xV0hdoPEkp9RLk7T-uYYfTYXa8oYCXy2ysa9SZDa9hffEHrVe) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz).\n",
       "\n",
       "This victory marked Spain's record fourth European Championship title [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGFcidniPKtBR-_QjSR1P1Oathq_0T9FTwfpCAWZxbXsroItHQU8zRcyOPDgMcvsWoD2fEnwYFKwanV18ep2_cyS5BlHF6-OFNsijWb-peAgsgLAVRiubekRnzMugsYtiWrhZyO3Q==) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHh_4hBL0Giyuw_cyfT8m7tUSnMqBqH4Lis1CtJICPJNGGLhT6PADTIoUtrj3Rl5qcKNE9T6rzOmedAER_gxJOBDrCF8pnr9lUvhYvmDJxYCJzELkE5rTap4dx6FzOIKZKm1QBp5aHXzd_LCkSTV9ag7Q1A6_t8Vjdbskch6ZG3BoIfjYDQSPgRKDNFAAwt5J07cVFV5pDQzggmM7pxwsUz4drz) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFY5CRvcfjdkBz3h8Md_PscguyZ7LtYrxeHHP3eagcmIOnjaMyZbOHFqUAsa2cgkwvb26FZTvGiRgLKNLfiAsH1oP-5kGwnL6Ejhm4ZXhWGg0R3yE_8zkIKde4RgjIXlBvQW4kZ-LI5yhag-ESoh771z6hob8AigAVXT7WeWABMlQNfcbyG_UZIkqAs18U5e6to44ruNbSyDIyd5gobsVpEmdU256oVxa9d7co=) [coachesvoice](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHxgpkZWF64tZ8-iypkI2fiFi2cpsj4AFjZXkcYUzf5hSOWYb5etIbCoZd_L6zDJi6mWWisxAO6T5V4T8H7XiRow6dmVqXpSEIKhPSdG0HAQbQK74lwxeV_uXx9fSPllIKPOs2tFNRqTuHdJBNcwpcJp6MJbVLEskyhYnWlyOd9ouQv) [aljazeera](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEV-g6Hxxcan5Xre1yYGM3BtP3fo9uF2zHQ9sVeK_4poD-aBN5CRvhz471beYCC26wdrjhtbiCvDT9dAnPI-ruyqJZhwB3vbKS5HCFb9tPn7Dkj99LpjLXqYyuzbFGsHCbr5SCHoMEhNg--dMU7xB5TiH8HeqKH8B4lk_h00dqhEVQFb05w5TuLtbX1UdXN6NDzHlFN_xyXzOU=) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL). Spain achieved this by winning all seven of their matches throughout the tournament [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgwKo5lPes5M_GObnkYEzn3QYn1kpTQpx42ANaNqvNMgRsB1Xp2TIXI82SYTSYuLd9ysgKfmlJJy3lcLxrmNBg1R_Z37PCO9vbqIBIbw6DKqMif7pHdtDTS7FUq69c29hkYb_b5w==) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGd9ZQky3X7RQLbTs6mY1i4Pg7ppcI5H_vtxpvQPiEyD8Qw0f7hjvn3QeoOeAVcCG_pEt5Aeu8ofWCgjwQy4_u6qU-NOOJsYPWOW94XcvtkmKiv46vbNkJF-Mb4OpvBztrDa28BfIdCGHdfF9o=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGZc-qDhRx_v3mPelXEfAVmWCpNTa_rzUKundc0pRc7PlTgppymao-_wO7O1oPaAhJYLcZkazIg8T5jA6t9OGgOxUd_Vl88BjouHsot0OK8TlM5hmPf4ECMWGeJthqVwndE3h4wdQ==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1Lj9FnmuckfU0k1NC_ThQBZVxFCppp4tPl4FCcM3JZGF9aPvn9ZNFUo0fLfqw4Adt63Cdv8thcFSbsBRcf3rj1sz4LALJvrGfh6OayGo0KJ-UEKmKoOz8cxj5nIILCzKjFh2_0ZgTwrf1pkhhYbnWqj2E8hrVN4S5_sxvlCpLXPxjTsE4R0gYKXH_utqqm1NBkpl3p-C9v6kz-zm6V-JJoePAppIXFICF0DMYjOIBA9Mj0z4yO9Y9Tdgx2oaP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFNtaBQTFVnSbEW5Bbo8LUIs0h5cv4Pc4aS6Q8qG7jIMCsJPKy5_o6R8x7Z_xQ7AuDEAFlj2JY_AVV1YpwLqtXZxiAyvpfboH_VuMpo6MVbQAu2ZASSSD2slWaIqsUGkTEaPa2z2809z7UhEWUL) [newsbytesapp](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIl5Xc3f44I1nYw_YrJqkByrRl20SiAopZqjfJIK6U62o27CrxLvxaJ4v1M7L5eOfTMMlBCHHYCUooPoG0aObaeRG3YxrcoFT7Xtd4KIrvCS6AWWRpOZasCW-sGtFA56DEDf-qbJ8lsXEJ4GQ386iGTdRkyK9EtJWw1mRpDu7dfPQ6Qy1hNIqTgTdo-3yq1WNmWEl8Xtnag0s=).\n",
       "\n",
       "Key individual awards for the tournament went to Spain's players: Rodri was named the Best Player, and Lamine Yamal was named the Best Young Player [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEk7ApC7Y41UOrTWJ40wP2rsT0VDxqhqF-WJEI-FNKW7SNpR7LoA22sRQecS8hZNeZ_-62Vh7X75RmcmZUtnAOuQunrLAsETkkSx5l75dt9ESgTRkIURwtu4Pew7hn8yFz_LY_FJXUpmRfoWP7MWrDfPHcKrOpfmKqONj6mJcASNvAfCZ0p6qK3K4PvKWye6NyBMyYxWCuJig==0) [bet9ja](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFgj0MP_IEmC842xTfmMPnbybBGYTUb_wEpwJ58keX5x_qPfUmC7Zz0o6IQeQ8TEqoRpv-Uq6oOqfbazu_aP0fMhP7UrSln6rB4SRvCRC327tM1LNaXpiXN-h6xlg0TN_-AWQORV4PSH7G5u2qD_NaNEWkz_oaEHxj22-qOam52fwRvqISOdoFDNTptlM6t0BbhcA==) [uefa](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGKGygrv0aVjWa7JUdwqtuttcPxVIiVFb2_Mxv32q-4AyOVwd8oMKLXq6sl2kw4A37lHLmUUQYqVfDMkX3DLXr4or1Xpx1lnOpIUanPjOtrr2Hk6tPPc0308hdE0xJ5CClC220Tz30xD6538_DOvrVWqfA7pV7x651519Zz37wgqYhN00Ah3LX4QZnW981_-SM8tjVSLDXutPphZBXXmMehNgUynvNd2IiGB9UtkLyGeWINIqR2F7lejStuXJ8U2Q==) [beinsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXExRli0zGmQZlemPPItRH3qShabB-QVHrgUAECeXIs3GUKgd2oIHd45-ULY--TosnkRkiM-XHqZlPxeQlOV6Ktgxb-L5r9Hhf8M-nQS_T0N7NK0BeynreRZtFivuKzwwOByq6uALzoVtombjsREMmsPG7s07CMlMrQjyJCVX8McNdnGC7-mdlHEjdfXN4sgi-YGxdxCdAxaHUaMQxPL0GUUmqDzMMpzVC_lRnrYfuk17UhXI9QhsEi3TMeuUgHu3kl16g1mHA==) [thehindu](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEAlCtejIOwzHPUOAXi7oLu469wYzGUJN86oxtrB6YCAHKAocfkxog6XZeXOUjAl9MTY2_jU5igYEOpyy5RZV2jhxGHtahvQGi8Bq0XkJmaFvludGqwpuBn-vFf-MR3As1CXu9GZNh0TW5f3eLPgvDjB6N3IoYaGhGT8BUiqSyZS6k41T-vL9h6fEFMoOFUYhG2S0AfuVZDuyF2nJHJP1WVWZS42csWXEJUDxqhYjyzmx33HaCxKk0Rbe3_Ovc_Kgdagw==)."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.display import Markdown\n",
    "\n",
    "Markdown(state[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = graph.invoke({\"messages\": state[\"messages\"] + [{\"role\": \"user\", \"content\": \"How has the most titles? List the top 5\"}]})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Based on the number of UEFA European Championship titles won, Spain holds the record [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [yahoo](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEN_wi1zong77ArtYg3CR5q-wmd_1KK_G72jrRoZv8_QlUFZUvjqyDXL5Co8RzGzWRzP9To5pq0kSBorMh3qgNVcOLGuCbF3giTuegc9yb0k9JqiOTFNYINKJQNAAmMo2ZziC5Iu_F1S5cpLJkKgCXLKk3VLkhqY-hSV1h_ryZRIMjENlsw7Q0=) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXF2tzG9OJk8Y6nPflMmiUEr826naRNP0ncayg9rczFwi4d_IOq9k99b_7K4ISJPMpAOTzV_VCw8H33rEC6z2N99GWxlB7evrGw__IwY8ZILaE4kYzojFvnmrvRwEdAQsRU2xkUH2AM_VDc6bXduEQBjjkHRi1XFwuY5OvVbGImLznn0dEidu45aQ0Kq) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGCVmrxZzE4ELt_Nl2fd2hYXp7QBGE7bqB8y4I0CRyiGrIB4dMu7krUBEuukEN1Go8KCJme8GuKl3dNqiW-UD7oR6MNWR47NKy1wqoNBxzrgZ2q1nnmjCAqxSeNejx_KTeAkiRVjUBXOQApJL_gUDI4Lwl3BCGcmlNVZGcuP88YN09iuu_stghqGJ7ulc6rUpHEZy9w0SpxUzexpKo27306oSXBPvvYVX6VnltFWvASMRsDfmD3dc94t715Ig04wFvaSIicHg==) [nbcsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHgdp86b29MgI2_Kvo4j5P0Iz8d3W8YgVkRwx31hZAoC9_dL-zyj_jP1vbDkxBiCA8kYArwSMPRVetYZR9WYYAwm3VwowkVtH7slpfObvQLnlHb2SQ386cBdZeZBZmEhgvVFE07YR4Z83RgohnOi26cW1BsZiRYlm1Adh1pgWtiNeiUl7ZNUMxtMQ5XvBx0GM1FpBd1QPsnhjU-pwNz-7ETG_XhsC7ocHEgWyMozF0cJOsEoR-Uye62Q0M=).\n",
       "\n",
       "Here are the top countries ranked by the number of UEFA European Championship titles:\n",
       "\n",
       "1.  **Spain:** 4 titles (1964, 2008, 2012, 2024) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [yahoo](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEN_wi1zong77ArtYg3CR5q-wmd_1KK_G72jrRoZv8_QlUFZUvjqyDXL5Co8RzGzWRzP9To5pq0kSBorMh3qgNVcOLGuCbF3giTuegc9yb0k9JqiOTFNYINKJQNAAmMo2ZziC5Iu_F1S5cpLJkKgCXLKk3VLkhqY-hSV1h_ryZRIMjENlsw7Q0=) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXF2tzG9OJk8Y6nPflMmiUEr826naRNP0ncayg9rczFwi4d_IOq9k99b_7K4ISJPMpAOTzV_VCw8H33rEC6z2N99GWxlB7evrGw__IwY8ZILaE4kYzojFvnmrvRwEdAQsRU2xkUH2AM_VDc6bXduEQBjjkHRi1XFwuY5OvVbGImLznn0dEidu45aQ0Kq) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXGCVmrxZzE4ELt_Nl2fd2hYXp7QBGE7bqB8y4I0CRyiGrIB4dMu7krUBEuukEN1Go8KCJme8GuKl3dNqiW-UD7oR6MNWR47NKy1wqoNBxzrgZ2q1nnmjCAqxSeNejx_KTeAkiRVjUBXOQApJL_gUDI4Lwl3BCGcmlNVZGcuP88YN09iuu_stghqGJ7ulc6rUpHEZy9w0SpxUzexpKo27306oSXBPvvYVX6VnltFWvASMRsDfmD3dc94t715Ig04wFvaSIicHg==) [nbcsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHgdp86b29MgI2_Kvo4j5P0Iz8d3W8YgVkRwx31hZAoC9_dL-zyj_jP1vbDkxBiCA8kYArwSMPRVetYZR9WYYAwm3VwowkVtH7slpfObvQLnlHb2SQ386cBdZeZBZmEhgvVFE07YR4Z83RgohnOi26cW1BsZiRYlm1Adh1pgWtiNeiUl7ZNUMxtMQ5XvBx0GM1FpBd1QPsnhjU-pwNz-7ETG_XhsC7ocHEgWyMozF0cJOsEoR-Uye62Q0M=). Spain is also the only nation to have won consecutive titles (2008 and 2012) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXF2tzG9OJk8Y6nPflMmiUEr826naRNP0ncayg9rczFwi4d_IOq9k99b_7K4ISJPMpAOTzV_VCw8H33rEC6z2N99GWxlB7evrGw__IwY8ZILaE4kYzojFvnmrvRwEdAQsRU2xkUH2AM_VDc6bXduEQBjjkHRi1XFwuY5OvVbGImLznn0dEidu45aQ0Kq) [nbcsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHgdp86b29MgI2_Kvo4j5P0Iz8d3W8YgVkRwx31hZAoC9_dL-zyj_jP1vbDkxBiCA8kYArwSMPRVetYZR9WYYAwm3VwowkVtH7slpfObvQLnlHb2SQ386cBdZeZBZmEhgvVFE07YR4Z83RgohnOi26cW1BsZiRYlm1Adh1pgWtiNeiUl7ZNUMxtMQ5XvBx0GM1FpBd1QPsnhjU-pwNz-7ETG_XhsC7ocHEgWyMozF0cJOsEoR-Uye62Q0M=).\n",
       "2.  **Germany:** 3 titles (1972, 1980, 1996) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [yahoo](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEN_wi1zong77ArtYg3CR5q-wmd_1KK_G72jrRoZv8_QlUFZUvjqyDXL5Co8RzGzWRzP9To5pq0kSBorMh3qgNVcOLGuCbF3giTuegc9yb0k9JqiOTFNYINKJQNAAmMo2ZziC5Iu_F1S5cpLJkKgCXLKk3VLkhqY-hSV1h_ryZRIMjENlsw7Q0=) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXF2tzG9OJk8Y6nPflMmiUEr826naRNP0ncayg9rczFwi4d_IOq9k99b_7K4ISJPMpAOTzV_VCw8H33rEC6z2N99GWxlB7evrGw__IwY8ZILaE4kYzojFvnmrvRwEdAQsRU2xkUH2AM_VDc6bXduEQBjjkHRi1XFwuY5OvVbGImLznn0dEidu45aQ0Kq) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXETMGJq-qIccM3rUu2XPme69mRXj51ItevakpVZcxWa26F74sDgeP3slSuSCccKFyv9Xx5P1r4-3kY4ckWQclfnA3leE1ctTGdnIn-5GBRQrjxIwNSlKADP46pBTqgg_LhybRo2at4=). (Note: The first two titles were won as West Germany) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [byfarthegreatestteam](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHbs0yJwq86sGLUvo-8IgZfAus8ZnQcTe_dDlaZhfp7awMYMvU3mq8EL4VYyYWhahCYeRlACi-5eOq4maub0x3GsfKHvhgLBe8KOJG7f-CaPLRKjmRFPRNS2sig2ZCw6VXQeyLq_JWgmbIof5uLvNFqs_qOwFwqZvSmqWKbEnrw2dXMdEmsLKprFbsvAwd9GdMzFr9HojBPpSSk95SzVt2a849gqAGFyIq89tv-mirQaMRqwVUL).\n",
       "3.  **Italy:** 2 titles (1968, 2021) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [foxsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXF2tzG9OJk8Y6nPflMmiUEr826naRNP0ncayg9rczFwi4d_IOq9k99b_7K4ISJPMpAOTzV_VCw8H33rEC6z2N99GWxlB7evrGw__IwY8ZILaE4kYzojFvnmrvRwEdAQsRU2xkUH2AM_VDc6bXduEQBjjkHRi1XFwuY5OvVbGImLznn0dEidu45aQ0Kq) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=).\n",
       "4.  **France:** 2 titles (1984, 2000) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [youtube](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXETMGJq-qIccM3rUu2XPme69mRXj51ItevakpVZcxWa26F74sDgeP3slSuSCccKFyv9Xx5P1r4-3kY4ckWQclfnA3leE1ctTGdnIn-5GBRQrjxIwNSlKADP46pBTqgg_LhybRo2at4=).\n",
       "5.  **Tied with 1 title each:**\n",
       "    *   Soviet Union (1960) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=)\n",
       "    *   Czechoslovakia (1976) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=)\n",
       "    *   Netherlands (1988) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=)\n",
       "    *   Denmark (1992) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=)\n",
       "    *   Greece (2004) [olympics](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHoAXOG7_3DUjYvRr_buN8IAL3xE5yQoPetZCb1KlcaMOgJEE5BeBoqQEVVkDZLDpwgTmFkPYeWS7i_D23Vd5bKzUTfc0HSLI481VbXjMD9ECeZRFZ17g3xAYLg5I0QU34RWLCRcV_zgphUsJZ0L5gXjpYz5gl8syuYAX3VkHCwh0x6Wqau4er_cZ56CoiA-3S_r2I=) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=)\n",
       "    *   Portugal (2016) [sportsadda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXEYq2a0benYn0vF2WrfvmqEsgwriQ08aVcDdpS1MUjBxlzaV_scV0ldVeUpwqcgVLCfxgX3oVmbUxbkFPzeHbknsAbxLFk4Iyvtxgacx54AZBnL1szGQ9cQQGOOT8f-zGZhzKWEhAIOYTsz89uAr55R546MlC31OFXiU7AGhMgLi0Ekk6wQvPJVTWs_TiaG4MHoHo0obaRhJK1iPYaAxqHKD2Zf5rTr2jmdPBPd9w==) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFvGr1KOv5qWkUO63kL5-QFEKn41IArXdrcLcMuaCc69bmwu_VsGzE7QI4scHdLjQxYxoFD3eg4ZflqzFcnNk7UJKM5cT8IR13LrrWodcNzotVidnczmVCFCd1-w10ixHS2rgykLdSr8UqFNJ88T2hZL-HL6YCLUUAXJjFP) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG_0rXuWu0vsaIzCzaUG9Rw0L65I3o3RWhCp4gzXiHDZW3GaJXLntEQNi-O88mGf5LlE0tAkMNd_5VBNOkzIxAkbVsdkpPjwtzuY1sjv2gjtHLnvbIa8Y9jFbdS8kE3xqj95_TayzxNWpyr-XkwY9qLW4NI) [wikipedia](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFXDSskdzRBS66rtxun9egUAz7spzUcDjq30yCoOdFr_SzKkrXBLgAPbzZKVQhx-Z28pOO3phMnr-qIhLuS9zJrp0MTCyIohI6EYxlJ3DpFXTNxneDn9OzNs7sZX_LwKKYA2E-7Mjr46dqZuprKzRn9amiPHusHo3dRWKpOzMSfhXpO) [topendsports](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXHD7IG-bFCCNoc-2iRxXvo468klJLjiQmPdQCkRubtvT83i-Xbpg5XKxyLQB9Yc7qVwRuLjHIB37ywnZ8fdT3fM2ydpLggvdTGxAUVL1M0havCvEQpxiqcmS9LaBnOqWnMOWyy_ztdfTrVihPRb0chKtGeDHA-2EMQlW9ge) [sportingnews](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFAkEqJsPiiUSSUWhHm2Qtc30qed2JljSZ9Nu4JrcJ--CkX_Rif1AO5L0Kxl09j6yo8n5MS9NWdFsXi7KRIg5EJL0d0jm8YA-E4sllbJojNNQDwII8cb1A3b9b5RP3JoFTp2xEYQu914rrEFmRmjsFb44LU8bgGFJijrBG237B67YqLXiQThCPZjP-Gq3BKv3cTxZKseIXSRjaxosiM4LDpxDxZPAdpeIIpb3aiH7w_IyC8dWXpCyoHZYZfYe6EGNXfkmE=) [sportskeeda](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXFIIs54t2O4RL3Y8cvfgbTs_CjcMeedsitpXc8PsaUZXOA7sohYdYTdTiN1-kLAhtXi2UZT4A-iIPZ6ufIpvuD_53Qtr4zqZnqZ6ox74EgyOPyjxs9k1qS_Gq1kR57IdjnMG4JbC7y9nVq2xZRNevSC-PJLSfCoLc36ahu6Xp6Fssl1Yw8LjgX2ranBbG72OvyijpJj1UygG-SVqr7h0y-DECQ=)\n",
       "\n",
       "Therefore, while Spain has the most titles, the top 5 ranking positions are held by Spain (1st), Germany (2nd), Italy and France (tied 3rd), and the six nations tied for 5th place."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Markdown(state[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}


================================================
FILE: docker-compose.yml
================================================
volumes:
  langgraph-data:
    driver: local
services:
  langgraph-redis:
    image: docker.io/redis:6
    container_name: langgraph-redis
    healthcheck:
      test: redis-cli ping
      interval: 5s
      timeout: 1s
      retries: 5
  langgraph-postgres:
    image: docker.io/postgres:16
    container_name: langgraph-postgres
    ports:
      - "5433:5432"
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - langgraph-data:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready -U postgres
      start_period: 10s
      timeout: 1s
      retries: 5
      interval: 5s
  langgraph-api:
    image: gemini-fullstack-langgraph
    container_name: langgraph-api
    ports:
      - "8123:8000"
    depends_on:
      langgraph-redis:
        condition: service_healthy
      langgraph-postgres:
        condition: service_healthy
    environment:
      GEMINI_API_KEY: ${GEMINI_API_KEY}
      LANGSMITH_API_KEY: ${LANGSMITH_API_KEY}
      REDIS_URI: redis://langgraph-redis:6379
      POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable


================================================
FILE: frontend/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: frontend/components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/app.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}

================================================
FILE: frontend/eslint.config.js
================================================
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
  { ignores: ['dist'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
)


================================================
FILE: frontend/index.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: frontend/package.json
================================================
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@langchain/core": "^0.3.55",
    "@langchain/langgraph-sdk": "^0.0.74",
    "@radix-ui/react-scroll-area": "^1.2.8",
    "@radix-ui/react-select": "^2.2.4",
    "@radix-ui/react-slot": "^1.2.2",
    "@radix-ui/react-tabs": "^1.1.11",
    "@radix-ui/react-tooltip": "^1.2.6",
    "@tailwindcss/vite": "^4.1.5",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.508.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-markdown": "^9.0.3",
    "react-router-dom": "^7.5.3",
    "tailwind-merge": "^3.2.0",
    "tailwindcss": "^4.1.5"
  },
  "devDependencies": {
    "@eslint/js": "^9.22.0",
    "@types/node": "^22.15.17",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.3",
    "@vitejs/plugin-react-swc": "^3.9.0",
    "eslint": "^9.22.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "tw-animate-css": "^1.2.9",
    "typescript": "~5.7.2",
    "typescript-eslint": "^8.26.1",
    "vite": "^6.3.4"
  }
}


================================================
FILE: frontend/src/App.tsx
================================================
import { useStream } from "@langchain/langgraph-sdk/react";
import type { Message } from "@langchain/langgraph-sdk";
import { useState, useEffect, useRef, useCallback } from "react";
import { ProcessedEvent } from "@/components/ActivityTimeline";
import { WelcomeScreen } from "@/components/WelcomeScreen";
import { ChatMessagesView } from "@/components/ChatMessagesView";
import { Button } from "@/components/ui/button";

export default function App() {
  const [processedEventsTimeline, setProcessedEventsTimeline] = useState<
    ProcessedEvent[]
  >([]);
  const [historicalActivities, setHistoricalActivities] = useState<
    Record<string, ProcessedEvent[]>
  >({});
  const scrollAreaRef = useRef<HTMLDivElement>(null);
  const hasFinalizeEventOccurredRef = useRef(false);
  const [error, setError] = useState<string | null>(null);
  const thread = useStream<{
    messages: Message[];
    initial_search_query_count: number;
    max_research_loops: number;
    reasoning_model: string;
  }>({
    apiUrl: import.meta.env.DEV
      ? "http://localhost:2024"
      : "http://localhost:8123",
    assistantId: "agent",
    messagesKey: "messages",
    onUpdateEvent: (event: any) => {
      let processedEvent: ProcessedEvent | null = null;
      if (event.generate_query) {
        processedEvent = {
          title: "Generating Search Queries",
          data: event.generate_query?.search_query?.join(", ") || "",
        };
      } else if (event.web_research) {
        const sources = event.web_research.sources_gathered || [];
        const numSources = sources.length;
        const uniqueLabels = [
          ...new Set(sources.map((s: any) => s.label).filter(Boolean)),
        ];
        const exampleLabels = uniqueLabels.slice(0, 3).join(", ");
        processedEvent = {
          title: "Web Research",
          data: `Gathered ${numSources} sources. Related to: ${
            exampleLabels || "N/A"
          }.`,
        };
      } else if (event.reflection) {
        processedEvent = {
          title: "Reflection",
          data: "Analysing Web Research Results",
        };
      } else if (event.finalize_answer) {
        processedEvent = {
          title: "Finalizing Answer",
          data: "Composing and presenting the final answer.",
        };
        hasFinalizeEventOccurredRef.current = true;
      }
      if (processedEvent) {
        setProcessedEventsTimeline((prevEvents) => [
          ...prevEvents,
          processedEvent!,
        ]);
      }
    },
    onError: (error: any) => {
      setError(error.message);
    },
  });

  useEffect(() => {
    if (scrollAreaRef.current) {
      const scrollViewport = scrollAreaRef.current.querySelector(
        "[data-radix-scroll-area-viewport]"
      );
      if (scrollViewport) {
        scrollViewport.scrollTop = scrollViewport.scrollHeight;
      }
    }
  }, [thread.messages]);

  useEffect(() => {
    if (
      hasFinalizeEventOccurredRef.current &&
      !thread.isLoading &&
      thread.messages.length > 0
    ) {
      const lastMessage = thread.messages[thread.messages.length - 1];
      if (lastMessage && lastMessage.type === "ai" && lastMessage.id) {
        setHistoricalActivities((prev) => ({
          ...prev,
          [lastMessage.id!]: [...processedEventsTimeline],
        }));
      }
      hasFinalizeEventOccurredRef.current = false;
    }
  }, [thread.messages, thread.isLoading, processedEventsTimeline]);

  const handleSubmit = useCallback(
    (submittedInputValue: string, effort: string, model: string) => {
      if (!submittedInputValue.trim()) return;
      setProcessedEventsTimeline([]);
      hasFinalizeEventOccurredRef.current = false;

      // convert effort to, initial_search_query_count and max_research_loops
      // low means max 1 loop and 1 query
      // medium means max 3 loops and 3 queries
      // high means max 10 loops and 5 queries
      let initial_search_query_count = 0;
      let max_research_loops = 0;
      switch (effort) {
        case "low":
          initial_search_query_count = 1;
          max_research_loops = 1;
          break;
        case "medium":
          initial_search_query_count = 3;
          max_research_loops = 3;
          break;
        case "high":
          initial_search_query_count = 5;
          max_research_loops = 10;
          break;
      }

      const newMessages: Message[] = [
        ...(thread.messages || []),
        {
          type: "human",
          content: submittedInputValue,
          id: Date.now().toString(),
        },
      ];
      thread.submit({
        messages: newMessages,
        initial_search_query_count: initial_search_query_count,
        max_research_loops: max_research_loops,
        reasoning_model: model,
      });
    },
    [thread]
  );

  const handleCancel = useCallback(() => {
    thread.stop();
    window.location.reload();
  }, [thread]);

  return (
    <div className="flex h-screen bg-neutral-800 text-neutral-100 font-sans antialiased">
      <main className="h-full w-full max-w-4xl mx-auto">
          {thread.messages.length === 0 ? (
            <WelcomeScreen
              handleSubmit={handleSubmit}
              isLoading={thread.isLoading}
              onCancel={handleCancel}
            />
          ) : error ? (
            <div className="flex flex-col items-center justify-center h-full">
              <div className="flex flex-col items-center justify-center gap-4">
                <h1 className="text-2xl text-red-400 font-bold">Error</h1>
                <p className="text-red-400">{JSON.stringify(error)}</p>

                <Button
                  variant="destructive"
                  onClick={() => window.location.reload()}
                >
                  Retry
                </Button>
              </div>
            </div>
          ) : (
            <ChatMessagesView
              messages={thread.messages}
              isLoading={thread.isLoading}
              scrollAreaRef={scrollAreaRef}
              onSubmit={handleSubmit}
              onCancel={handleCancel}
              liveActivityEvents={processedEventsTimeline}
              historicalActivities={historicalActivities}
            />
          )}
      </main>
    </div>
  );
}


================================================
FILE: frontend/src/components/ActivityTimeline.tsx
================================================
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
} from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
  Loader2,
  Activity,
  Info,
  Search,
  TextSearch,
  Brain,
  Pen,
  ChevronDown,
  ChevronUp,
} from "lucide-react";
import { useEffect, useState } from "react";

export interface ProcessedEvent {
  title: string;
  data: any;
}

interface ActivityTimelineProps {
  processedEvents: ProcessedEvent[];
  isLoading: boolean;
}

export function ActivityTimeline({
  processedEvents,
  isLoading,
}: ActivityTimelineProps) {
  const [isTimelineCollapsed, setIsTimelineCollapsed] =
    useState<boolean>(false);
  const getEventIcon = (title: string, index: number) => {
    if (index === 0 && isLoading && processedEvents.length === 0) {
      return <Loader2 className="h-4 w-4 text-neutral-400 animate-spin" />;
    }
    if (title.toLowerCase().includes("generating")) {
      return <TextSearch className="h-4 w-4 text-neutral-400" />;
    } else if (title.toLowerCase().includes("thinking")) {
      return <Loader2 className="h-4 w-4 text-neutral-400 animate-spin" />;
    } else if (title.toLowerCase(
Download .txt
gitextract_1hm2p4us/

├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── backend/
│   ├── .gitignore
│   ├── LICENSE
│   ├── Makefile
│   ├── examples/
│   │   └── cli_research.py
│   ├── langgraph.json
│   ├── pyproject.toml
│   ├── src/
│   │   └── agent/
│   │       ├── __init__.py
│   │       ├── app.py
│   │       ├── configuration.py
│   │       ├── graph.py
│   │       ├── prompts.py
│   │       ├── state.py
│   │       ├── tools_and_schemas.py
│   │       └── utils.py
│   └── test-agent.ipynb
├── docker-compose.yml
└── frontend/
    ├── .gitignore
    ├── components.json
    ├── eslint.config.js
    ├── index.html
    ├── package.json
    ├── src/
    │   ├── App.tsx
    │   ├── components/
    │   │   ├── ActivityTimeline.tsx
    │   │   ├── ChatMessagesView.tsx
    │   │   ├── InputForm.tsx
    │   │   ├── WelcomeScreen.tsx
    │   │   └── ui/
    │   │       ├── badge.tsx
    │   │       ├── button.tsx
    │   │       ├── card.tsx
    │   │       ├── input.tsx
    │   │       ├── scroll-area.tsx
    │   │       ├── select.tsx
    │   │       ├── tabs.tsx
    │   │       └── textarea.tsx
    │   ├── global.css
    │   ├── lib/
    │   │   └── utils.ts
    │   ├── main.tsx
    │   └── vite-env.d.ts
    ├── tsconfig.json
    ├── tsconfig.node.json
    └── vite.config.ts
Download .txt
SYMBOL INDEX (62 symbols across 22 files)

FILE: backend/examples/cli_research.py
  function main (line 6) | def main() -> None:

FILE: backend/src/agent/app.py
  function create_frontend_router (line 10) | def create_frontend_router(build_dir="../frontend/dist"):

FILE: backend/src/agent/configuration.py
  class Configuration (line 8) | class Configuration(BaseModel):
    method from_runnable_config (line 43) | def from_runnable_config(

FILE: backend/src/agent/graph.py
  function generate_query (line 44) | def generate_query(state: OverallState, config: RunnableConfig) -> Query...
  function continue_to_web_research (line 84) | def continue_to_web_research(state: QueryGenerationState):
  function web_research (line 95) | def web_research(state: WebSearchState, config: RunnableConfig) -> Overa...
  function reflection (line 139) | def reflection(state: OverallState, config: RunnableConfig) -> Reflectio...
  function evaluate_research (line 183) | def evaluate_research(
  function finalize_answer (line 220) | def finalize_answer(state: OverallState, config: RunnableConfig):

FILE: backend/src/agent/prompts.py
  function get_current_date (line 5) | def get_current_date():

FILE: backend/src/agent/state.py
  class OverallState (line 13) | class OverallState(TypedDict):
  class ReflectionState (line 24) | class ReflectionState(TypedDict):
  class Query (line 32) | class Query(TypedDict):
  class QueryGenerationState (line 37) | class QueryGenerationState(TypedDict):
  class WebSearchState (line 41) | class WebSearchState(TypedDict):
  class SearchStateOutput (line 47) | class SearchStateOutput:

FILE: backend/src/agent/tools_and_schemas.py
  class SearchQueryList (line 5) | class SearchQueryList(BaseModel):
  class Reflection (line 14) | class Reflection(BaseModel):

FILE: backend/src/agent/utils.py
  function get_research_topic (line 5) | def get_research_topic(messages: List[AnyMessage]) -> str:
  function resolve_urls (line 22) | def resolve_urls(urls_to_resolve: List[Any], id: int) -> Dict[str, str]:
  function insert_citation_markers (line 39) | def insert_citation_markers(text, citations_list):
  function get_citations (line 78) | def get_citations(response, resolved_urls_map):

FILE: frontend/src/App.tsx
  function App (line 9) | function App() {

FILE: frontend/src/components/ActivityTimeline.tsx
  type ProcessedEvent (line 21) | interface ProcessedEvent {
  type ActivityTimelineProps (line 26) | interface ActivityTimelineProps {
  function ActivityTimeline (line 31) | function ActivityTimeline({

FILE: frontend/src/components/ChatMessagesView.tsx
  type MdComponentProps (line 17) | type MdComponentProps = {
  type HumanMessageBubbleProps (line 138) | interface HumanMessageBubbleProps {
  type AiMessageBubbleProps (line 162) | interface AiMessageBubbleProps {
  type ChatMessagesViewProps (line 225) | interface ChatMessagesViewProps {
  function ChatMessagesView (line 235) | function ChatMessagesView({

FILE: frontend/src/components/InputForm.tsx
  type InputFormProps (line 14) | interface InputFormProps {

FILE: frontend/src/components/WelcomeScreen.tsx
  type WelcomeScreenProps (line 3) | interface WelcomeScreenProps {

FILE: frontend/src/components/ui/badge.tsx
  function Badge (line 28) | function Badge({

FILE: frontend/src/components/ui/button.tsx
  function Button (line 38) | function Button({

FILE: frontend/src/components/ui/card.tsx
  function Card (line 5) | function Card({ className, ...props }: React.ComponentProps<"div">) {
  function CardHeader (line 18) | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  function CardTitle (line 31) | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  function CardDescription (line 41) | function CardDescription({ className, ...props }: React.ComponentProps<"...
  function CardAction (line 51) | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  function CardContent (line 64) | function CardContent({ className, ...props }: React.ComponentProps<"div"...
  function CardFooter (line 74) | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {

FILE: frontend/src/components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: frontend/src/components/ui/scroll-area.tsx
  function ScrollArea (line 6) | function ScrollArea({
  function ScrollBar (line 30) | function ScrollBar({

FILE: frontend/src/components/ui/select.tsx
  function Select (line 7) | function Select({
  function SelectGroup (line 13) | function SelectGroup({
  function SelectValue (line 19) | function SelectValue({
  function SelectTrigger (line 25) | function SelectTrigger({
  function SelectContent (line 51) | function SelectContent({
  function SelectLabel (line 86) | function SelectLabel({
  function SelectItem (line 99) | function SelectItem({
  function SelectSeparator (line 123) | function SelectSeparator({
  function SelectScrollUpButton (line 136) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 154) | function SelectScrollDownButton({

FILE: frontend/src/components/ui/tabs.tsx
  function Tabs (line 6) | function Tabs({
  function TabsList (line 19) | function TabsList({
  function TabsTrigger (line 35) | function TabsTrigger({
  function TabsContent (line 51) | function TabsContent({

FILE: frontend/src/components/ui/textarea.tsx
  function Textarea (line 5) | function Textarea({ className, ...props }: React.ComponentProps<"textare...

FILE: frontend/src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (255K chars).
[
  {
    "path": ".gitignore",
    "chars": 3579,
    "preview": "# Node / Frontend\nnode_modules/\nfrontend/dist/\nfrontend/.vite/\nfrontend/coverage/\n.DS_Store\n*.local\n\n# Logs\nlogs\n*.log\nn"
  },
  {
    "path": "Dockerfile",
    "chars": 2835,
    "preview": "# Stage 1: Build React Frontend\nFROM node:20-alpine AS frontend-builder\n\n# Set working directory for frontend\nWORKDIR /a"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 676,
    "preview": ".PHONY: help dev-frontend dev-backend dev\n\nhelp:\n\t@echo \"Available commands:\"\n\t@echo \"  make dev-frontend    - Starts th"
  },
  {
    "path": "README.md",
    "chars": 6392,
    "preview": "# Gemini Fullstack LangGraph Quickstart\n\nThis project demonstrates a fullstack application using a React frontend and a "
  },
  {
    "path": "backend/.gitignore",
    "chars": 3147,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\nuv.lock\n\n# C extensions\n*.so\n\n# Distribution /"
  },
  {
    "path": "backend/LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2025 Philipp Schmid\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "backend/Makefile",
    "chars": 2000,
    "preview": ".PHONY: all format lint test tests test_watch integration_tests docker_tests help extended_tests\n\n# Default target execu"
  },
  {
    "path": "backend/examples/cli_research.py",
    "chars": 1192,
    "preview": "import argparse\nfrom langchain_core.messages import HumanMessage\nfrom agent.graph import graph\n\n\ndef main() -> None:\n   "
  },
  {
    "path": "backend/langgraph.json",
    "chars": 159,
    "preview": "{\n  \"dependencies\": [\".\"],\n  \"graphs\": {\n    \"agent\": \"./src/agent/graph.py:graph\"\n  },\n  \"http\": {\n    \"app\": \"./src/ag"
  },
  {
    "path": "backend/pyproject.toml",
    "chars": 1287,
    "preview": "[project]\nname = \"agent\"\nversion = \"0.0.1\"\ndescription = \"Backend for the LangGraph agent\"\nauthors = [\n    { name = \"Phi"
  },
  {
    "path": "backend/src/agent/__init__.py",
    "chars": 51,
    "preview": "from agent.graph import graph\n\n__all__ = [\"graph\"]\n"
  },
  {
    "path": "backend/src/agent/app.py",
    "chars": 1388,
    "preview": "# mypy: disable - error - code = \"no-untyped-def,misc\"\nimport pathlib\nfrom fastapi import FastAPI, Response\nfrom fastapi"
  },
  {
    "path": "backend/src/agent/configuration.py",
    "chars": 1797,
    "preview": "import os\nfrom pydantic import BaseModel, Field\nfrom typing import Any, Optional\n\nfrom langchain_core.runnables import R"
  },
  {
    "path": "backend/src/agent/graph.py",
    "chars": 10588,
    "preview": "import os\n\nfrom agent.tools_and_schemas import SearchQueryList, Reflection\nfrom dotenv import load_dotenv\nfrom langchain"
  },
  {
    "path": "backend/src/agent/prompts.py",
    "chars": 4688,
    "preview": "from datetime import datetime\n\n\n# Get current date in a readable format\ndef get_current_date():\n    return datetime.now("
  },
  {
    "path": "backend/src/agent/state.py",
    "chars": 1067,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass, field\nfrom typing import TypedDict\n\nfrom langgrap"
  },
  {
    "path": "backend/src/agent/tools_and_schemas.py",
    "chars": 769,
    "preview": "from typing import List\nfrom pydantic import BaseModel, Field\n\n\nclass SearchQueryList(BaseModel):\n    query: List[str] ="
  },
  {
    "path": "backend/src/agent/utils.py",
    "chars": 7117,
    "preview": "from typing import Any, Dict, List\nfrom langchain_core.messages import AnyMessage, AIMessage, HumanMessage\n\n\ndef get_res"
  },
  {
    "path": "backend/test-agent.ipynb",
    "chars": 123580,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n "
  },
  {
    "path": "docker-compose.yml",
    "chars": 1164,
    "preview": "volumes:\n  langgraph-data:\n    driver: local\nservices:\n  langgraph-redis:\n    image: docker.io/redis:6\n    container_nam"
  },
  {
    "path": "frontend/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "frontend/components.json",
    "chars": 423,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": "
  },
  {
    "path": "frontend/eslint.config.js",
    "chars": 734,
    "preview": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reac"
  },
  {
    "path": "frontend/index.html",
    "chars": 366,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "frontend/package.json",
    "chars": 1284,
    "preview": "{\n  \"name\": \"frontend\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n "
  },
  {
    "path": "frontend/src/App.tsx",
    "chars": 6256,
    "preview": "import { useStream } from \"@langchain/langgraph-sdk/react\";\nimport type { Message } from \"@langchain/langgraph-sdk\";\nimp"
  },
  {
    "path": "frontend/src/components/ActivityTimeline.tsx",
    "chars": 5716,
    "preview": "import {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n} from \"@/components/ui/card\";\nimport { ScrollArea } fr"
  },
  {
    "path": "frontend/src/components/ChatMessagesView.tsx",
    "chars": 10262,
    "preview": "import type React from \"react\";\nimport type { Message } from \"@langchain/langgraph-sdk\";\nimport { ScrollArea } from \"@/c"
  },
  {
    "path": "frontend/src/components/InputForm.tsx",
    "chars": 6950,
    "preview": "import { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { SquarePen, Brain, Send, Stop"
  },
  {
    "path": "frontend/src/components/WelcomeScreen.tsx",
    "chars": 1003,
    "preview": "import { InputForm } from \"./InputForm\";\n\ninterface WelcomeScreenProps {\n  handleSubmit: (\n    submittedInputValue: stri"
  },
  {
    "path": "frontend/src/components/ui/badge.tsx",
    "chars": 1631,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
  },
  {
    "path": "frontend/src/components/ui/button.tsx",
    "chars": 2123,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class"
  },
  {
    "path": "frontend/src/components/ui/card.tsx",
    "chars": 1989,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({ className, ...props }: React.Component"
  },
  {
    "path": "frontend/src/components/ui/input.tsx",
    "chars": 967,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.Co"
  },
  {
    "path": "frontend/src/components/ui/scroll-area.tsx",
    "chars": 1686,
    "preview": "import * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport { cn } from \"@"
  },
  {
    "path": "frontend/src/components/ui/select.tsx",
    "chars": 6239,
    "preview": "import * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { CheckIcon, ChevronDown"
  },
  {
    "path": "frontend/src/components/ui/tabs.tsx",
    "chars": 1955,
    "preview": "import * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \"@/lib/utils\"\n\n"
  },
  {
    "path": "frontend/src/components/ui/textarea.tsx",
    "chars": 759,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Textarea({ className, ...props }: React.Compo"
  },
  {
    "path": "frontend/src/global.css",
    "chars": 5883,
    "preview": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n  --radius-sm: "
  },
  {
    "path": "frontend/src/lib/utils.ts",
    "chars": 169,
    "preview": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
  },
  {
    "path": "frontend/src/main.tsx",
    "chars": 328,
    "preview": "import { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { BrowserRouter } from \"react-"
  },
  {
    "path": "frontend/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "frontend/tsconfig.json",
    "chars": 753,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2020\",\n"
  },
  {
    "path": "frontend/tsconfig.node.json",
    "chars": 593,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2022\","
  },
  {
    "path": "frontend/vite.config.ts",
    "chars": 727,
    "preview": "import path from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport "
  }
]

About this extraction

This page contains the full source code of the google-gemini/gemini-fullstack-langgraph-quickstart GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (239.2 KB), approximately 91.3k tokens, and a symbol index with 62 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!