├── .github ├── release-please.yml ├── release-trigger.yml └── workflows │ ├── import.yml │ ├── mypy.yml │ └── stale.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── .nojekyll ├── _sources │ ├── genai.rst.txt │ ├── index.rst.txt │ └── modules.rst.txt ├── _static │ ├── autodoc_pydantic.css │ ├── basic.css │ ├── css │ │ └── custom.css │ ├── debug.css │ ├── doctools.js │ ├── documentation_options.js │ ├── file.png │ ├── language_data.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── scripts │ │ ├── furo-extensions.js │ │ ├── furo.js │ │ ├── furo.js.LICENSE.txt │ │ └── furo.js.map │ ├── searchtools.js │ ├── skeleton.css │ ├── sphinx_highlight.js │ └── styles │ │ ├── furo-extensions.css │ │ ├── furo-extensions.css.map │ │ ├── furo.css │ │ └── furo.css.map ├── genai.html ├── genindex.html ├── index.html ├── modules.html ├── objects.inv ├── py-modindex.html ├── search.html └── searchindex.js ├── google └── genai │ ├── __init__.py │ ├── _adapters.py │ ├── _api_client.py │ ├── _api_module.py │ ├── _automatic_function_calling_util.py │ ├── _base_url.py │ ├── _common.py │ ├── _extra_utils.py │ ├── _live_converters.py │ ├── _mcp_utils.py │ ├── _replay_api_client.py │ ├── _test_api_client.py │ ├── _tokens_converters.py │ ├── _transformers.py │ ├── batches.py │ ├── caches.py │ ├── chats.py │ ├── client.py │ ├── errors.py │ ├── files.py │ ├── live.py │ ├── live_music.py │ ├── models.py │ ├── mypy.ini │ ├── operations.py │ ├── pagers.py │ ├── py.typed │ ├── tests │ ├── __init__.py │ ├── afc │ │ ├── __init__.py │ │ ├── test_convert_if_exist_pydantic_model.py │ │ ├── test_convert_number_values_for_function_call_args.py │ │ ├── test_generate_content_stream_afc.py │ │ ├── test_get_function_map.py │ │ ├── test_get_function_response_parts.py │ │ ├── test_get_max_remote_calls_for_afc.py │ │ ├── test_invoke_function_from_dict_args.py │ │ ├── test_should_append_afc_history.py │ │ └── test_should_disable_afc.py │ ├── batches │ │ ├── __init__.py │ │ ├── test_cancel.py │ │ ├── test_create.py │ │ ├── test_delete.py │ │ ├── test_get.py │ │ └── test_list.py │ ├── caches │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── test_create.py │ │ ├── test_create_custom_url.py │ │ ├── test_delete.py │ │ ├── test_delete_custom_url.py │ │ ├── test_get.py │ │ ├── test_get_custom_url.py │ │ ├── test_list.py │ │ ├── test_update.py │ │ └── test_update_custom_url.py │ ├── chats │ │ ├── __init__.py │ │ ├── test_get_history.py │ │ ├── test_send_message.py │ │ └── test_validate_response.py │ ├── client │ │ ├── __init__.py │ │ ├── test_client_initialization.py │ │ ├── test_client_requests.py │ │ ├── test_http_options.py │ │ └── test_replay_client_equality.py │ ├── common │ │ ├── __init__.py │ │ └── test_common.py │ ├── conftest.py │ ├── credentials.json │ ├── data │ │ ├── animal.mp4 │ │ ├── bridge1.png │ │ ├── checkerboard.png │ │ ├── google.jpg │ │ ├── google.png │ │ ├── google_small.png │ │ ├── pixel.m4a │ │ └── story.pdf │ ├── errors │ │ ├── __init__.py │ │ └── test_api_error.py │ ├── files │ │ ├── __init__.py │ │ ├── test_delete.py │ │ ├── test_download.py │ │ ├── test_get.py │ │ ├── test_list.py │ │ └── test_upload.py │ ├── imports │ │ └── test_no_optional_imports.py │ ├── live │ │ ├── __init__.py │ │ ├── test_live.py │ │ ├── test_live_music.py │ │ ├── test_live_response.py │ │ ├── test_send_client_content.py │ │ ├── test_send_realtime_input.py │ │ └── test_send_tool_response.py │ ├── mcp │ │ ├── __init__.py │ │ ├── test_has_mcp_tool_usage.py │ │ ├── test_mcp_to_gemini_tools.py │ │ ├── test_parse_config_for_mcp_sessions.py │ │ ├── test_parse_config_for_mcp_usage.py │ │ └── test_set_mcp_usage_header.py │ ├── models │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── test_compute_tokens.py │ │ ├── test_count_tokens.py │ │ ├── test_delete.py │ │ ├── test_edit_image.py │ │ ├── test_embed_content.py │ │ ├── test_generate_content.py │ │ ├── test_generate_content_cached_content.py │ │ ├── test_generate_content_config_zero_value.py │ │ ├── test_generate_content_from_apikey.py │ │ ├── test_generate_content_http_options.py │ │ ├── test_generate_content_mcp.py │ │ ├── test_generate_content_media_resolution.py │ │ ├── test_generate_content_model.py │ │ ├── test_generate_content_part.py │ │ ├── test_generate_content_thought.py │ │ ├── test_generate_content_tools.py │ │ ├── test_generate_images.py │ │ ├── test_generate_videos.py │ │ ├── test_get.py │ │ ├── test_list.py │ │ ├── test_update.py │ │ └── test_upscale_image.py │ ├── public_samples │ │ ├── __init__.py │ │ └── test_gemini_text_only.py │ ├── pytest_helper.py │ ├── tokens │ │ ├── __init__.py │ │ └── test_create.py │ ├── transformers │ │ ├── __init__.py │ │ ├── test_blobs.py │ │ ├── test_bytes.py │ │ ├── test_function_responses.py │ │ ├── test_schema.py │ │ ├── test_t_content.py │ │ ├── test_t_contents.py │ │ ├── test_t_part.py │ │ ├── test_t_parts.py │ │ ├── test_t_tool.py │ │ └── test_t_tools.py │ ├── tunings │ │ ├── __init__.py │ │ ├── test_end_to_end.py │ │ ├── test_get.py │ │ ├── test_list.py │ │ └── test_tune.py │ └── types │ │ ├── __init__.py │ │ ├── test_bytes_internal.py │ │ ├── test_bytes_type.py │ │ ├── test_future.py │ │ ├── test_optional_types.py │ │ ├── test_part_type.py │ │ ├── test_schema_from_json_schema.py │ │ ├── test_schema_json_schema.py │ │ └── test_types.py │ ├── tokens.py │ ├── tunings.py │ ├── types.py │ └── version.py ├── pyproject.toml └── requirements.txt /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | releaseType: python 2 | handleGHRelease: true 3 | bumpMinorPreMajor: false 4 | extraFiles: 5 | - google/genai/version.py 6 | -------------------------------------------------------------------------------- /.github/release-trigger.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | -------------------------------------------------------------------------------- /.github/workflows/import.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Import Test 3 | 4 | on: 5 | push: 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | test-import: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install setuptools wheel 31 | pip install pytest 32 | pip install . 33 | 34 | - name: Run import test 35 | run: | 36 | pytest google/genai/tests/imports/test_no_optional_imports.py -------------------------------------------------------------------------------- /.github/workflows/mypy.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs mypy for static type checking. 2 | # See https://mypy.readthedocs.io/en/stable/index.html for more information. 3 | # 4 | # You can adjust the behavior by modifying this file. 5 | name: Run mypy 6 | 7 | on: 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | mypy: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install mypy 32 | pip install -r requirements.txt 33 | 34 | - name: Run mypy ${{ matrix.python-version }} 35 | run: mypy google/genai/ --strict --config-file=google/genai/mypy.ini -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | # Scheduled to run at 1:30 UTC everyday 11 | - cron: '30 1 * * *' 12 | 13 | jobs: 14 | stale: 15 | 16 | runs-on: ubuntu-latest 17 | permissions: 18 | issues: write 19 | pull-requests: write 20 | 21 | steps: 22 | - uses: actions/stale@v5 23 | with: 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | days-before-issue-stale: 14 26 | days-before-issue-close: 14 27 | stale-issue-label: "status:stale" 28 | close-issue-reason: not_planned 29 | any-of-labels: "status:awaiting user response,status:more data needed" 30 | stale-issue-message: > 31 | Marking this issue as stale since it has been open for 14 days with no activity. 32 | This issue will be closed if no further activity occurs. 33 | close-issue-message: > 34 | This issue was closed because it has been inactive for 28 days. 35 | Please post a new issue if you need further assistance. Thanks! 36 | days-before-pr-stale: 14 37 | days-before-pr-close: 14 38 | stale-pr-label: "status:stale" 39 | stale-pr-message: > 40 | Marking this pull request as stale since it has been open for 14 days with no activity. 41 | This PR will be closed if no further activity occurs. 42 | close-pr-message: > 43 | This pull request was closed because it has been inactive for 28 days. 44 | Please open a new pull request if you need further assistance. Thanks! 45 | # Label that can be assigned to issues to exclude them from being marked as stale 46 | exempt-issue-labels: 'override-stale' 47 | # Label that can be assigned to PRs to exclude them from being marked as stale 48 | exempt-pr-labels: "override-stale" 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | *.py[cod] 3 | google_genai.egg-info/ 4 | build/ 5 | dist/ 6 | 7 | # Test files 8 | .nox/ 9 | 10 | # Coverage files 11 | .coverage 12 | coverage.xml 13 | nosetests.xml 14 | 15 | # IDE files 16 | .idea 17 | .vscode 18 | 19 | # macOS 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The Google Gen AI SDK will accept contributions in the future. 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_sources/genai.rst.txt: -------------------------------------------------------------------------------- 1 | Submodules 2 | ---------- 3 | 4 | genai.client module 5 | ------------------- 6 | 7 | .. automodule:: genai.client 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | genai.batches module 13 | -------------------- 14 | 15 | .. automodule:: genai.batches 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | genai.caches module 21 | ------------------- 22 | 23 | .. automodule:: genai.caches 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | genai.chats module 29 | ------------------ 30 | 31 | .. automodule:: genai.chats 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | genai.files module 37 | ------------------ 38 | 39 | .. automodule:: genai.files 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | genai.live module 45 | ------------------ 46 | 47 | .. automodule:: genai.live 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | genai.models module 53 | ------------------- 54 | 55 | .. automodule:: genai.models 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | genai.tokens module 61 | -------------------- 62 | 63 | .. automodule:: genai.tokens 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | 69 | genai.tunings module 70 | -------------------- 71 | 72 | .. automodule:: genai.tunings 73 | :members: 74 | :undoc-members: 75 | :show-inheritance: 76 | 77 | genai.types module 78 | ------------------ 79 | 80 | .. automodule:: genai.types 81 | :members: 82 | :undoc-members: 83 | :show-inheritance: 84 | -------------------------------------------------------------------------------- /docs/_sources/modules.rst.txt: -------------------------------------------------------------------------------- 1 | google 2 | ====== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | genai 8 | -------------------------------------------------------------------------------- /docs/_static/autodoc_pydantic.css: -------------------------------------------------------------------------------- 1 | .autodoc_pydantic_validator_arrow { 2 | padding-left: 8px; 3 | } 4 | 5 | .autodoc_pydantic_collapsable_json { 6 | cursor: pointer; 7 | } 8 | 9 | .autodoc_pydantic_collapsable_erd { 10 | cursor: pointer; 11 | } -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap'); 2 | 3 | body { 4 | font-weight: 400; 5 | } 6 | 7 | h1 { 8 | font-size: 32px; 9 | font-weight: 400; 10 | } 11 | 12 | h2 { 13 | font-size: 24px; 14 | font-weight: 400; 15 | } 16 | 17 | h3 { 18 | font-size: 20px; 19 | font-weight: 400; 20 | } 21 | 22 | h4 { 23 | font-size: 16px; 24 | font-weight: 400; 25 | } 26 | 27 | .sidebar-brand-text { 28 | font-size: 14px; 29 | } 30 | 31 | .highlight { 32 | box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2); 33 | } -------------------------------------------------------------------------------- /docs/_static/debug.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS file should be overridden by the theme authors. It's 3 | meant for debugging and developing the skeleton that this theme provides. 4 | */ 5 | body { 6 | font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, 7 | "Apple Color Emoji", "Segoe UI Emoji"; 8 | background: lavender; 9 | } 10 | .sb-announcement { 11 | background: rgb(131, 131, 131); 12 | } 13 | .sb-announcement__inner { 14 | background: black; 15 | color: white; 16 | } 17 | .sb-header { 18 | background: lightskyblue; 19 | } 20 | .sb-header__inner { 21 | background: royalblue; 22 | color: white; 23 | } 24 | .sb-header-secondary { 25 | background: lightcyan; 26 | } 27 | .sb-header-secondary__inner { 28 | background: cornflowerblue; 29 | color: white; 30 | } 31 | .sb-sidebar-primary { 32 | background: lightgreen; 33 | } 34 | .sb-main { 35 | background: blanchedalmond; 36 | } 37 | .sb-main__inner { 38 | background: antiquewhite; 39 | } 40 | .sb-header-article { 41 | background: lightsteelblue; 42 | } 43 | .sb-article-container { 44 | background: snow; 45 | } 46 | .sb-article-main { 47 | background: white; 48 | } 49 | .sb-footer-article { 50 | background: lightpink; 51 | } 52 | .sb-sidebar-secondary { 53 | background: lightgoldenrodyellow; 54 | } 55 | .sb-footer-content { 56 | background: plum; 57 | } 58 | .sb-footer-content__inner { 59 | background: palevioletred; 60 | } 61 | .sb-footer { 62 | background: pink; 63 | } 64 | .sb-footer__inner { 65 | background: salmon; 66 | } 67 | .sb-article { 68 | background: white; 69 | } 70 | -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Base JavaScript utilities for all Sphinx HTML documentation. 3 | */ 4 | "use strict"; 5 | 6 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 7 | "TEXTAREA", 8 | "INPUT", 9 | "SELECT", 10 | "BUTTON", 11 | ]); 12 | 13 | const _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * Small JavaScript module for the documentation. 23 | */ 24 | const Documentation = { 25 | init: () => { 26 | Documentation.initDomainIndexTable(); 27 | Documentation.initOnKeyListeners(); 28 | }, 29 | 30 | /** 31 | * i18n support 32 | */ 33 | TRANSLATIONS: {}, 34 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 35 | LOCALE: "unknown", 36 | 37 | // gettext and ngettext don't access this so that the functions 38 | // can safely bound to a different name (_ = Documentation.gettext) 39 | gettext: (string) => { 40 | const translated = Documentation.TRANSLATIONS[string]; 41 | switch (typeof translated) { 42 | case "undefined": 43 | return string; // no translation 44 | case "string": 45 | return translated; // translation exists 46 | default: 47 | return translated[0]; // (singular, plural) translation tuple exists 48 | } 49 | }, 50 | 51 | ngettext: (singular, plural, n) => { 52 | const translated = Documentation.TRANSLATIONS[singular]; 53 | if (typeof translated !== "undefined") 54 | return translated[Documentation.PLURAL_EXPR(n)]; 55 | return n === 1 ? singular : plural; 56 | }, 57 | 58 | addTranslations: (catalog) => { 59 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 60 | Documentation.PLURAL_EXPR = new Function( 61 | "n", 62 | `return (${catalog.plural_expr})` 63 | ); 64 | Documentation.LOCALE = catalog.locale; 65 | }, 66 | 67 | /** 68 | * helper function to focus on search bar 69 | */ 70 | focusSearchBar: () => { 71 | document.querySelectorAll("input[name=q]")[0]?.focus(); 72 | }, 73 | 74 | /** 75 | * Initialise the domain index toggle buttons 76 | */ 77 | initDomainIndexTable: () => { 78 | const toggler = (el) => { 79 | const idNumber = el.id.substr(7); 80 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 81 | if (el.src.substr(-9) === "minus.png") { 82 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 83 | toggledRows.forEach((el) => (el.style.display = "none")); 84 | } else { 85 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 86 | toggledRows.forEach((el) => (el.style.display = "")); 87 | } 88 | }; 89 | 90 | const togglerElements = document.querySelectorAll("img.toggler"); 91 | togglerElements.forEach((el) => 92 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 93 | ); 94 | togglerElements.forEach((el) => (el.style.display = "")); 95 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 96 | }, 97 | 98 | initOnKeyListeners: () => { 99 | // only install a listener if it is really needed 100 | if ( 101 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 102 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 103 | ) 104 | return; 105 | 106 | document.addEventListener("keydown", (event) => { 107 | // bail for input elements 108 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 109 | // bail with special keys 110 | if (event.altKey || event.ctrlKey || event.metaKey) return; 111 | 112 | if (!event.shiftKey) { 113 | switch (event.key) { 114 | case "ArrowLeft": 115 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 116 | 117 | const prevLink = document.querySelector('link[rel="prev"]'); 118 | if (prevLink && prevLink.href) { 119 | window.location.href = prevLink.href; 120 | event.preventDefault(); 121 | } 122 | break; 123 | case "ArrowRight": 124 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 125 | 126 | const nextLink = document.querySelector('link[rel="next"]'); 127 | if (nextLink && nextLink.href) { 128 | window.location.href = nextLink.href; 129 | event.preventDefault(); 130 | } 131 | break; 132 | } 133 | } 134 | 135 | // some keyboard layouts may need Shift to get / 136 | switch (event.key) { 137 | case "/": 138 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 139 | Documentation.focusSearchBar(); 140 | event.preventDefault(); 141 | } 142 | }); 143 | }, 144 | }; 145 | 146 | // quick alias for translations 147 | const _ = Documentation.gettext; 148 | 149 | _ready(Documentation.init); 150 | -------------------------------------------------------------------------------- /docs/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | const DOCUMENTATION_OPTIONS = { 2 | VERSION: '', 3 | LANGUAGE: 'en', 4 | COLLAPSE_INDEX: false, 5 | BUILDER: 'html', 6 | FILE_SUFFIX: '.html', 7 | LINK_SUFFIX: '.html', 8 | HAS_SOURCE: true, 9 | SOURCELINK_SUFFIX: '.txt', 10 | NAVIGATION_WITH_KEYS: false, 11 | SHOW_SEARCH_SUMMARY: true, 12 | ENABLE_SEARCH_SHORTCUTS: true, 13 | }; -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/scripts/furo-extensions.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/docs/_static/scripts/furo-extensions.js -------------------------------------------------------------------------------- /docs/_static/scripts/furo.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see furo.js.LICENSE.txt */ 2 | (()=>{var t={856:function(t,e,n){var o,r;r=void 0!==n.g?n.g:"undefined"!=typeof window?window:this,o=function(){return function(t){"use strict";var e={navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:0,reflow:!1,events:!0},n=function(t,e,n){if(n.settings.events){var o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(o)}},o=function(t){var e=0;if(t.offsetParent)for(;t;)e+=t.offsetTop,t=t.offsetParent;return e>=0?e:0},r=function(t){t&&t.sort((function(t,e){return o(t.content)=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},l=function(t,e){var n=t[t.length-1];if(function(t,e){return!(!s()||!c(t.content,e,!0))}(n,e))return n;for(var o=t.length-1;o>=0;o--)if(c(t[o].content,e))return t[o]},a=function(t,e){if(e.nested&&t.parentNode){var n=t.parentNode.closest("li");n&&(n.classList.remove(e.nestedClass),a(n,e))}},i=function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.remove(e.navClass),t.content.classList.remove(e.contentClass),a(o,e),n("gumshoeDeactivate",o,{link:t.nav,content:t.content,settings:e}))}},u=function(t,e){if(e.nested){var n=t.parentNode.closest("li");n&&(n.classList.add(e.nestedClass),u(n,e))}};return function(o,c){var s,a,d,f,m,v={setup:function(){s=document.querySelectorAll(o),a=[],Array.prototype.forEach.call(s,(function(t){var e=document.getElementById(decodeURIComponent(t.hash.substr(1)));e&&a.push({nav:t,content:e})})),r(a)},detect:function(){var t=l(a,m);t?d&&t.content===d.content||(i(d,m),function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.add(e.navClass),t.content.classList.add(e.contentClass),u(o,e),n("gumshoeActivate",o,{link:t.nav,content:t.content,settings:e}))}}(t,m),d=t):d&&(i(d,m),d=null)}},h=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(v.detect)},g=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame((function(){r(a),v.detect()}))};return v.destroy=function(){d&&i(d,m),t.removeEventListener("scroll",h,!1),m.reflow&&t.removeEventListener("resize",g,!1),a=null,s=null,d=null,f=null,m=null},m=function(){var t={};return Array.prototype.forEach.call(arguments,(function(e){for(var n in e){if(!e.hasOwnProperty(n))return;t[n]=e[n]}})),t}(e,c||{}),v.setup(),v.detect(),t.addEventListener("scroll",h,!1),m.reflow&&t.addEventListener("resize",g,!1),v}}(r)}.apply(e,[]),void 0===o||(t.exports=o)}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var c=e[o]={exports:{}};return t[o].call(c.exports,c,c.exports,n),c.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{"use strict";var t=n(856),e=n.n(t),o=null,r=null,c=document.documentElement.scrollTop;const s=64;function l(){const t=localStorage.getItem("theme")||"auto";var e;"light"!==(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"auto"===t?"light":"light"==t?"dark":"auto":"auto"===t?"dark":"dark"==t?"light":"auto")&&"dark"!==e&&"auto"!==e&&(console.error(`Got invalid theme mode: ${e}. Resetting to auto.`),e="auto"),document.body.dataset.theme=e,localStorage.setItem("theme",e),console.log(`Changed to ${e} mode.`)}function a(){!function(){const t=document.getElementsByClassName("theme-toggle");Array.from(t).forEach((t=>{t.addEventListener("click",l)}))}(),function(){let t=0,e=!1;window.addEventListener("scroll",(function(n){t=window.scrollY,e||(window.requestAnimationFrame((function(){var n;(function(t){const e=Math.floor(r.getBoundingClientRect().top);console.log(`headerTop: ${e}`),0==e&&t!=e?r.classList.add("scrolled"):r.classList.remove("scrolled")})(n=t),function(t){tc&&document.documentElement.classList.remove("show-back-to-top"),c=t}(n),function(t){null!==o&&(0==t?o.scrollTo(0,0):Math.ceil(t)>=Math.floor(document.documentElement.scrollHeight-window.innerHeight)?o.scrollTo(0,o.scrollHeight):document.querySelector(".scroll-current"))}(n),e=!1})),e=!0)})),window.scroll()}(),null!==o&&new(e())(".toc-tree a",{reflow:!0,recursive:!0,navClass:"scroll-current",offset:()=>{let t=parseFloat(getComputedStyle(document.documentElement).fontSize);return r.getBoundingClientRect().height+2.5*t+1}})}document.addEventListener("DOMContentLoaded",(function(){document.body.parentNode.classList.remove("no-js"),r=document.querySelector("header"),o=document.querySelector(".toc-scroll"),a()}))})()})(); 3 | //# sourceMappingURL=furo.js.map -------------------------------------------------------------------------------- /docs/_static/scripts/furo.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * gumshoejs v5.1.2 (patched by @pradyunsg) 3 | * A simple, framework-agnostic scrollspy script. 4 | * (c) 2019 Chris Ferdinandi 5 | * MIT License 6 | * http://github.com/cferdinandi/gumshoe 7 | */ 8 | -------------------------------------------------------------------------------- /docs/_static/styles/furo-extensions.css: -------------------------------------------------------------------------------- 1 | #furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0ms}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)} 2 | /*# sourceMappingURL=furo-extensions.css.map*/ -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/docs/objects.inv -------------------------------------------------------------------------------- /google/genai/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Google Gen AI SDK""" 17 | 18 | from . import version 19 | from .client import Client 20 | 21 | 22 | __version__ = version.__version__ 23 | 24 | __all__ = ['Client'] 25 | -------------------------------------------------------------------------------- /google/genai/_adapters.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import typing 17 | 18 | from ._mcp_utils import mcp_to_gemini_tools 19 | from .types import FunctionCall, Tool 20 | 21 | if typing.TYPE_CHECKING: 22 | from mcp import types as mcp_types 23 | from mcp import ClientSession 24 | 25 | 26 | class McpToGenAiToolAdapter: 27 | """Adapter for working with MCP tools in a GenAI client.""" 28 | 29 | def __init__( 30 | self, 31 | session: "mcp.ClientSession", # type: ignore # noqa: F821 32 | list_tools_result: "mcp_types.ListToolsResult", # type: ignore 33 | ) -> None: 34 | self._mcp_session = session 35 | self._list_tools_result = list_tools_result 36 | 37 | async def call_tool( 38 | self, function_call: FunctionCall 39 | ) -> "mcp_types.CallToolResult": # type: ignore 40 | """Calls a function on the MCP server.""" 41 | name = function_call.name if function_call.name else "" 42 | arguments = dict(function_call.args) if function_call.args else {} 43 | 44 | return typing.cast( 45 | "mcp_types.CallToolResult", 46 | await self._mcp_session.call_tool( 47 | name=name, 48 | arguments=arguments, 49 | ), 50 | ) 51 | 52 | @property 53 | def tools(self) -> list[Tool]: 54 | """Returns a list of Google GenAI tools.""" 55 | return mcp_to_gemini_tools(self._list_tools_result.tools) 56 | -------------------------------------------------------------------------------- /google/genai/_api_module.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Utilities for the API Modules of the Google Gen AI SDK.""" 17 | 18 | from typing import Optional 19 | from . import _api_client 20 | 21 | 22 | class BaseModule: 23 | 24 | def __init__(self, api_client_: _api_client.BaseApiClient): 25 | self._api_client = api_client_ 26 | 27 | @property 28 | def vertexai(self) -> Optional[bool]: 29 | return self._api_client.vertexai 30 | -------------------------------------------------------------------------------- /google/genai/_base_url.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import os 17 | from typing import Optional 18 | 19 | from .types import HttpOptions 20 | 21 | _default_base_gemini_url = None 22 | _default_base_vertex_url = None 23 | 24 | 25 | def set_default_base_urls( 26 | gemini_url: Optional[str], vertex_url: Optional[str] 27 | ) -> None: 28 | """Overrides the base URLs for the Gemini API and Vertex AI API.""" 29 | global _default_base_gemini_url, _default_base_vertex_url 30 | _default_base_gemini_url = gemini_url 31 | _default_base_vertex_url = vertex_url 32 | 33 | 34 | def get_base_url( 35 | vertexai: bool, 36 | http_options: Optional[HttpOptions] = None, 37 | ) -> Optional[str]: 38 | """Returns the default base URL based on the following priority. 39 | 40 | 1. Base URLs set via HttpOptions. 41 | 2. Base URLs set via the latest call to setDefaultBaseUrls. 42 | 3. Base URLs set via environment variables. 43 | """ 44 | if http_options and http_options.base_url: 45 | return http_options.base_url 46 | 47 | if vertexai: 48 | return _default_base_vertex_url or os.getenv('GOOGLE_VERTEX_BASE_URL') 49 | else: 50 | return _default_base_gemini_url or os.getenv('GOOGLE_GEMINI_BASE_URL') 51 | -------------------------------------------------------------------------------- /google/genai/_mcp_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Utils for working with MCP tools.""" 17 | 18 | from importlib.metadata import PackageNotFoundError, version 19 | import typing 20 | from typing import Any 21 | 22 | from . import types 23 | 24 | if typing.TYPE_CHECKING: 25 | from mcp.types import Tool as McpTool 26 | from mcp import ClientSession as McpClientSession 27 | else: 28 | McpClientSession: typing.Type = Any 29 | McpTool: typing.Type = Any 30 | try: 31 | from mcp.types import Tool as McpTool 32 | from mcp import ClientSession as McpClientSession 33 | except ImportError: 34 | McpTool = None 35 | McpClientSession = None 36 | 37 | 38 | def mcp_to_gemini_tool(tool: McpTool) -> types.Tool: 39 | """Translates an MCP tool to a Google GenAI tool.""" 40 | return types.Tool( 41 | function_declarations=[{ 42 | "name": tool.name, 43 | "description": tool.description, 44 | "parameters": types.Schema.from_json_schema( 45 | json_schema=types.JSONSchema( 46 | **_filter_to_supported_schema(tool.inputSchema) 47 | ) 48 | ), 49 | }] 50 | ) 51 | 52 | 53 | def mcp_to_gemini_tools(tools: list[McpTool]) -> list[types.Tool]: 54 | """Translates a list of MCP tools to a list of Google GenAI tools.""" 55 | return [mcp_to_gemini_tool(tool) for tool in tools] 56 | 57 | 58 | def has_mcp_tool_usage(tools: types.ToolListUnion) -> bool: 59 | """Checks whether the list of tools contains any MCP tools or sessions.""" 60 | if McpClientSession is None: 61 | return False 62 | for tool in tools: 63 | if isinstance(tool, McpTool) or isinstance(tool, McpClientSession): 64 | return True 65 | return False 66 | 67 | 68 | def has_mcp_session_usage(tools: types.ToolListUnion) -> bool: 69 | """Checks whether the list of tools contains any MCP sessions.""" 70 | if McpClientSession is None: 71 | return False 72 | for tool in tools: 73 | if isinstance(tool, McpClientSession): 74 | return True 75 | return False 76 | 77 | 78 | def set_mcp_usage_header(headers: dict[str, str]) -> None: 79 | """Sets the MCP version label in the Google API client header.""" 80 | if McpClientSession is None: 81 | return 82 | try: 83 | version_label = version("mcp") 84 | except PackageNotFoundError: 85 | version_label = "0.0.0" 86 | existing_header = headers.get("x-goog-api-client", "") 87 | headers["x-goog-api-client"] = ( 88 | existing_header + f" mcp_used/{version_label}" 89 | ).lstrip() 90 | 91 | 92 | def _filter_to_supported_schema(schema: dict[str, Any]) -> dict[str, Any]: 93 | """Filters the schema to only include fields that are supported by JSONSchema.""" 94 | supported_fields: set[str] = set(types.JSONSchema.model_fields.keys()) 95 | schema_field_names: tuple[str] = ("items",) # 'additional_properties' to come 96 | list_schema_field_names: tuple[str] = ( 97 | "any_of", # 'one_of', 'all_of', 'not' to come 98 | ) 99 | dict_schema_field_names: tuple[str] = ("properties",) # 'defs' to come 100 | for field_name, field_value in schema.items(): 101 | if field_name in schema_field_names: 102 | schema[field_name] = _filter_to_supported_schema(field_value) 103 | elif field_name in list_schema_field_names: 104 | schema[field_name] = [ 105 | _filter_to_supported_schema(value) for value in field_value 106 | ] 107 | elif field_name in dict_schema_field_names: 108 | schema[field_name] = { 109 | key: _filter_to_supported_schema(value) 110 | for key, value in field_value.items() 111 | } 112 | return { 113 | key: value for key, value in schema.items() if key in supported_fields 114 | } 115 | -------------------------------------------------------------------------------- /google/genai/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | exclude = (tests/|_test_api_client\.py) 3 | plugins = pydantic.mypy 4 | ; we are ignoring 'unused-ignore' because we run mypy on Python 3.9 - 3.13 and 5 | ; some errors in _automatic_function_calling_util.py only apply in 3.10+ 6 | ; 'import-not-found' and 'import-untyped' are environment specific 7 | disable_error_code = import-not-found, import-untyped, unused-ignore 8 | -------------------------------------------------------------------------------- /google/genai/py.typed: -------------------------------------------------------------------------------- 1 | # see: https://peps.python.org/pep-0561/ -------------------------------------------------------------------------------- /google/genai/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK.""" 18 | 19 | 20 | import pytest 21 | pytest.register_assert_rewrite('genai.replay_api_client') -------------------------------------------------------------------------------- /google/genai/tests/afc/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """AFC helper function unit tests. 18 | 19 | For AFC end to end test, please write test cases in 20 | test_generate_content_tools.py module 21 | """ 22 | -------------------------------------------------------------------------------- /google/genai/tests/afc/test_convert_number_values_for_function_call_args.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for convert_number_values_for_function_call_args.""" 18 | 19 | from ..._extra_utils import convert_number_values_for_function_call_args 20 | 21 | 22 | def test_integer_value(): 23 | assert convert_number_values_for_function_call_args(1) == 1 24 | 25 | 26 | def test_float_value(): 27 | assert convert_number_values_for_function_call_args(1.0) == 1 28 | 29 | 30 | def test_string_value(): 31 | assert convert_number_values_for_function_call_args('1.0') == '1.0' 32 | 33 | 34 | def test_boolean_value(): 35 | assert convert_number_values_for_function_call_args(True) is True 36 | 37 | 38 | def test_none_value(): 39 | assert convert_number_values_for_function_call_args(None) is None 40 | 41 | 42 | def test_float_value_with_decimal(): 43 | assert convert_number_values_for_function_call_args(1.1) == 1.1 44 | 45 | 46 | def test_dict_value(): 47 | assert convert_number_values_for_function_call_args( 48 | {'key1': 1.0, 'key2': 1.1} 49 | ) == {'key1': 1, 'key2': 1.1} 50 | 51 | 52 | def test_list_value(): 53 | assert convert_number_values_for_function_call_args([1.0, 1.1, 1.2]) == [ 54 | 1, 55 | 1.1, 56 | 1.2, 57 | ] 58 | 59 | 60 | def test_nested_value(): 61 | assert convert_number_values_for_function_call_args( 62 | {'key1': 1.0, 'key2': {'key3': 1.0, 'key4': [1.2, 2.0]}} 63 | ) == {'key1': 1, 'key2': {'key3': 1, 'key4': [1.2, 2]}} 64 | -------------------------------------------------------------------------------- /google/genai/tests/afc/test_get_max_remote_calls_for_afc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for get_max_remote_calls_for_afc.""" 18 | 19 | from ... import types 20 | from ..._extra_utils import get_max_remote_calls_afc 21 | import pytest 22 | 23 | 24 | def test_config_is_none(): 25 | assert get_max_remote_calls_afc(None) == 10 26 | 27 | 28 | def test_afc_unset_max_unset(): 29 | assert get_max_remote_calls_afc(types.GenerateContentConfig()) == 10 30 | 31 | 32 | def test_afc_unset_max_set(): 33 | assert ( 34 | get_max_remote_calls_afc( 35 | types.GenerateContentConfig( 36 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 37 | maximum_remote_calls=20, 38 | ), 39 | ) 40 | ) 41 | == 20 42 | ) 43 | 44 | 45 | def test_afc_disabled_max_unset(): 46 | with pytest.raises(ValueError): 47 | get_max_remote_calls_afc( 48 | types.GenerateContentConfig( 49 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 50 | disable=True, 51 | ), 52 | ) 53 | ) 54 | 55 | 56 | def test_afc_disabled_max_set(): 57 | with pytest.raises(ValueError): 58 | get_max_remote_calls_afc( 59 | types.GenerateContentConfig( 60 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 61 | disable=True, 62 | maximum_remote_calls=20, 63 | ), 64 | ) 65 | ) 66 | 67 | 68 | def test_afc_d_max_unset(): 69 | assert ( 70 | get_max_remote_calls_afc( 71 | types.GenerateContentConfig( 72 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 73 | disable=False, 74 | ), 75 | ) 76 | ) 77 | == 10 78 | ) 79 | 80 | 81 | def test_afc_d_max_set(): 82 | assert ( 83 | get_max_remote_calls_afc( 84 | types.GenerateContentConfig( 85 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 86 | disable=False, 87 | maximum_remote_calls=5, 88 | ), 89 | ) 90 | ) 91 | == 5 92 | ) 93 | 94 | 95 | def test_afc_enabled_max_set_to_zero(): 96 | with pytest.raises(ValueError): 97 | get_max_remote_calls_afc( 98 | types.GenerateContentConfig( 99 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 100 | disable=False, 101 | maximum_remote_calls=0, 102 | ), 103 | ) 104 | ) 105 | 106 | 107 | def test_afc_enabled_max_set_to_negative(): 108 | with pytest.raises(ValueError): 109 | get_max_remote_calls_afc( 110 | types.GenerateContentConfig( 111 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 112 | disable=False, 113 | maximum_remote_calls=-1, 114 | ), 115 | ) 116 | ) 117 | 118 | 119 | def test_afc_enabled_max_set_to_float(): 120 | assert ( 121 | get_max_remote_calls_afc( 122 | types.GenerateContentConfig( 123 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 124 | disable=False, 125 | maximum_remote_calls=5.0, 126 | ), 127 | ) 128 | ) 129 | == 5 130 | ) 131 | -------------------------------------------------------------------------------- /google/genai/tests/afc/test_should_append_afc_history.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Tests for _extra_utils.should_append_afc_history.""" 17 | 18 | from ... import types 19 | from ..._extra_utils import should_append_afc_history 20 | 21 | 22 | def test_should_append_afc_history_with_default_config(): 23 | config = types.GenerateContentConfig() 24 | 25 | assert should_append_afc_history(config) == True 26 | 27 | 28 | def test_should_append_afc_history_with_empty_afc_config(): 29 | config = types.GenerateContentConfig( 30 | automatic_function_calling=types.AutomaticFunctionCallingConfig() 31 | ) 32 | 33 | assert should_append_afc_history(config) == True 34 | 35 | 36 | def test_should_append_afc_history_with_ignore_call_history_true(): 37 | config = types.GenerateContentConfig( 38 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 39 | ignore_call_history=True 40 | ) 41 | ) 42 | 43 | assert should_append_afc_history(config) == False 44 | 45 | 46 | def test_should_append_afc_history_with_ignore_call_history_false(): 47 | config = types.GenerateContentConfig( 48 | automatic_function_calling=types.AutomaticFunctionCallingConfig( 49 | ignore_call_history=False 50 | ) 51 | ) 52 | 53 | assert should_append_afc_history(config) == True 54 | -------------------------------------------------------------------------------- /google/genai/tests/batches/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's batches module.""" -------------------------------------------------------------------------------- /google/genai/tests/batches/test_cancel.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for batches.cancel().""" 18 | 19 | import pytest 20 | 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | 25 | _BATCH_JOB_NAME = '2803006536245313536' 26 | _BATCH_JOB_FULL_RESOURCE_NAME = ( 27 | 'projects/964831358985/locations/us-central1/' 28 | f'batchPredictionJobs/{_BATCH_JOB_NAME}' 29 | ) 30 | _INVALID_BATCH_JOB_NAME = 'invalid_name' 31 | 32 | 33 | # All tests will be run for both Vertex and MLDev. 34 | test_table: list[pytest_helper.TestTableItem] = [ 35 | pytest_helper.TestTableItem( 36 | name='test_cancel_batch_job', 37 | parameters=types._CancelBatchJobParameters( 38 | name=_BATCH_JOB_NAME, 39 | ), 40 | exception_if_mldev='only supported in the Vertex AI client', 41 | ), 42 | pytest_helper.TestTableItem( 43 | name='test_cancel_batch_job_full_resource_name', 44 | override_replay_id='test_cancel_batch_job', 45 | parameters=types._CancelBatchJobParameters( 46 | name=_BATCH_JOB_FULL_RESOURCE_NAME, 47 | ), 48 | exception_if_mldev='only supported in the Vertex AI client', 49 | ), 50 | pytest_helper.TestTableItem( 51 | name='test_cancel_batch_job_with_invalid_name', 52 | parameters=types._CancelBatchJobParameters( 53 | name=_INVALID_BATCH_JOB_NAME, 54 | ), 55 | exception_if_mldev='only supported in the Vertex AI client', 56 | exception_if_vertex='Invalid batch job name', 57 | ), 58 | ] 59 | 60 | pytestmark = pytest_helper.setup( 61 | file=__file__, 62 | globals_for_file=globals(), 63 | test_method='batches.cancel', 64 | test_table=test_table, 65 | ) 66 | 67 | 68 | @pytest.mark.asyncio 69 | async def test_async_cancel(client): 70 | with pytest_helper.exception_if_mldev(client, ValueError): 71 | await client.aio.batches.cancel(name=_BATCH_JOB_NAME) 72 | -------------------------------------------------------------------------------- /google/genai/tests/batches/test_delete.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for batches.delete().""" 18 | 19 | import pytest 20 | 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | 25 | _BATCH_JOB_NAME = '7085929781874655232' 26 | _BATCH_JOB_FULL_RESOURCE_NAME = ( 27 | 'projects/964831358985/locations/us-central1/' 28 | f'batchPredictionJobs/{_BATCH_JOB_NAME}' 29 | ) 30 | _INVALID_BATCH_JOB_NAME = 'invalid_name' 31 | 32 | 33 | # All tests will be run for both Vertex and MLDev. 34 | test_table: list[pytest_helper.TestTableItem] = [ 35 | pytest_helper.TestTableItem( 36 | name='test_delete_batch_job', 37 | parameters=types._DeleteBatchJobParameters( 38 | name=_BATCH_JOB_NAME, 39 | ), 40 | exception_if_mldev='only supported in the Vertex AI client', 41 | ), 42 | pytest_helper.TestTableItem( 43 | name='test_delete_batch_job_full_resource_name', 44 | override_replay_id='test_delete_batch_job', 45 | parameters=types._DeleteBatchJobParameters( 46 | name=_BATCH_JOB_FULL_RESOURCE_NAME, 47 | ), 48 | exception_if_mldev='only supported in the Vertex AI client', 49 | ), 50 | pytest_helper.TestTableItem( 51 | name='test_delete_batch_job_with_invalid_name', 52 | parameters=types._DeleteBatchJobParameters( 53 | name=_INVALID_BATCH_JOB_NAME, 54 | ), 55 | exception_if_mldev='only supported in the Vertex AI client', 56 | exception_if_vertex='Invalid batch job name', 57 | ), 58 | ] 59 | 60 | pytestmark = pytest_helper.setup( 61 | file=__file__, 62 | globals_for_file=globals(), 63 | test_method='batches.delete', 64 | test_table=test_table, 65 | ) 66 | 67 | 68 | @pytest.mark.asyncio 69 | async def test_async_delete(client): 70 | with pytest_helper.exception_if_mldev(client, ValueError): 71 | delete_job = await client.aio.batches.delete(name=_BATCH_JOB_NAME) 72 | 73 | assert delete_job 74 | -------------------------------------------------------------------------------- /google/genai/tests/batches/test_get.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for batches.get().""" 18 | 19 | import pytest 20 | 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | 25 | _BATCH_JOB_NAME = '5798522612028014592' 26 | _BATCH_JOB_FULL_RESOURCE_NAME = ( 27 | 'projects/964831358985/locations/us-central1/' 28 | f'batchPredictionJobs/{_BATCH_JOB_NAME}' 29 | ) 30 | _INVALID_BATCH_JOB_NAME = 'invalid_name' 31 | 32 | 33 | # All tests will be run for both Vertex and MLDev. 34 | test_table: list[pytest_helper.TestTableItem] = [ 35 | pytest_helper.TestTableItem( 36 | name='test_get_batch_job', 37 | parameters=types._GetBatchJobParameters( 38 | name=_BATCH_JOB_NAME, 39 | ), 40 | exception_if_mldev='only supported in the Vertex AI client', 41 | ), 42 | pytest_helper.TestTableItem( 43 | name='test_get_batch_job_with_full_resource_name', 44 | parameters=types._GetBatchJobParameters( 45 | name=_BATCH_JOB_FULL_RESOURCE_NAME, 46 | ), 47 | exception_if_mldev='only supported in the Vertex AI client', 48 | override_replay_id='test_get_batch_job', 49 | ), 50 | pytest_helper.TestTableItem( 51 | name='test_get_batch_job_with_invalid_name', 52 | parameters=types._GetBatchJobParameters( 53 | name=_INVALID_BATCH_JOB_NAME, 54 | ), 55 | exception_if_mldev='only supported in the Vertex AI client', 56 | exception_if_vertex='Invalid batch job name', 57 | ), 58 | ] 59 | 60 | pytestmark = pytest_helper.setup( 61 | file=__file__, 62 | globals_for_file=globals(), 63 | test_method='batches.get', 64 | test_table=test_table, 65 | ) 66 | 67 | 68 | @pytest.mark.asyncio 69 | async def test_async_get(client): 70 | with pytest_helper.exception_if_mldev(client, ValueError): 71 | batch_job = await client.aio.batches.get(name=_BATCH_JOB_NAME) 72 | 73 | assert batch_job 74 | -------------------------------------------------------------------------------- /google/genai/tests/batches/test_list.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for batches.list().""" 18 | 19 | import pytest 20 | 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | 25 | # All tests will be run for both Vertex and MLDev. 26 | test_table: list[pytest_helper.TestTableItem] = [ 27 | pytest_helper.TestTableItem( 28 | name='test_list_batch_jobs', 29 | parameters=types._ListBatchJobsParameters(), 30 | exception_if_mldev='only supported in the Vertex AI client', 31 | ), 32 | pytest_helper.TestTableItem( 33 | name='test_list_batch_jobs_with_config', 34 | parameters=types._ListBatchJobsParameters( 35 | config=types.ListBatchJobsConfig( 36 | filter='display_name:"genai_*"', 37 | page_size=5, 38 | ), 39 | ), 40 | exception_if_mldev='only supported in the Vertex AI client', 41 | ), 42 | ] 43 | 44 | pytestmark = pytest_helper.setup( 45 | file=__file__, 46 | globals_for_file=globals(), 47 | test_method='batches.list', 48 | test_table=test_table, 49 | ) 50 | 51 | 52 | def test_pager(client): 53 | with pytest_helper.exception_if_mldev(client, ValueError): 54 | batch_jobs = client.batches.list(config={'page_size': 10}) 55 | 56 | assert batch_jobs.name == 'batch_jobs' 57 | assert batch_jobs.page_size == 10 58 | assert len(batch_jobs) <= 10 59 | 60 | # Iterate through all the pages. Then next_page() should raise an exception. 61 | for _ in batch_jobs: 62 | pass 63 | with pytest.raises(IndexError, match='No more pages to fetch.'): 64 | batch_jobs.next_page() 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_async_pager(client): 69 | with pytest_helper.exception_if_mldev(client, ValueError): 70 | batch_jobs = await client.aio.batches.list(config={'page_size': 10}) 71 | 72 | assert batch_jobs.name == 'batch_jobs' 73 | assert batch_jobs.page_size == 10 74 | assert len(batch_jobs) <= 10 75 | 76 | # Iterate through all the pages. Then next_page() should raise an exception. 77 | async for _ in batch_jobs: 78 | pass 79 | with pytest.raises(IndexError, match='No more pages to fetch.'): 80 | await batch_jobs.next_page() 81 | -------------------------------------------------------------------------------- /google/genai/tests/caches/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's caches module.""" 18 | -------------------------------------------------------------------------------- /google/genai/tests/caches/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Constants for caches tests.""" 18 | 19 | CACHED_CONTENT_NAME_MLDEV = 'cachedContents/op47f693jk6b' 20 | CACHED_CONTENT_NAME_VERTEX = 'cachedContents/1899938500610883584' 21 | 22 | VERTEX_HTTP_OPTIONS = { 23 | 'api_version': 'v1beta1', 24 | 'base_url': 'https://us-central1-aiplatform.googleapis.com/', 25 | } 26 | MLDEV_HTTP_OPTIONS = { 27 | 'api_version': 'v1beta', 28 | 'base_url': 'https://generativelanguage.googleapis.com/', 29 | } -------------------------------------------------------------------------------- /google/genai/tests/caches/test_create_custom_url.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import copy 18 | import pytest 19 | from ... import types 20 | from .. import pytest_helper 21 | from . import constants 22 | from ... import _transformers as t 23 | 24 | _CREATE_CACHED_CONTENT_PARAMETERS_GCS_URI = types._CreateCachedContentParameters( 25 | model='gemini-1.5-pro-002', 26 | config={ 27 | 'contents': [ 28 | types.Content( 29 | role='user', 30 | parts=[ 31 | types.Part( 32 | fileData=types.FileData( 33 | fileUri='gs://cloud-samples-data/generative-ai/pdf/2312.11805v3.pdf', 34 | mimeType='application/pdf', 35 | ) 36 | ), 37 | types.Part( 38 | fileData=types.FileData( 39 | fileUri='gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf', 40 | mimeType='application/pdf', 41 | ) 42 | ), 43 | ], 44 | ) 45 | ], 46 | 'system_instruction': t.t_content(None, 'What is the sum of the two pdfs?'), 47 | 'display_name': 'test cache', 48 | 'ttl': '86400s', 49 | 'http_options': constants.VERTEX_HTTP_OPTIONS, 50 | }, 51 | ) 52 | 53 | _CREATE_CACHED_CONTENT_PARAMETERS_GOOGLEAI_FILE = types._CreateCachedContentParameters( 54 | model='gemini-1.5-pro-001', 55 | config={ 56 | 'contents': [ 57 | types.Content( 58 | role='user', 59 | parts=[ 60 | types.Part( 61 | fileData=types.FileData( 62 | mimeType='video/mp4', 63 | fileUri='https://generativelanguage.googleapis.com/v1beta/files/v200dhvn15h7', 64 | ) 65 | ) 66 | ], 67 | ) 68 | ], 69 | 'system_instruction': t.t_content(None, 'What is the sum of the two pdfs?'), 70 | 'display_name': 'test cache', 71 | 'ttl': '86400s', 72 | 'http_options': constants.MLDEV_HTTP_OPTIONS, 73 | }, 74 | ) 75 | 76 | # Replay mode is not supported for caches tests due to the error message 77 | # inconsistency in api and replay mode. 78 | # To run api mode tests, use the following steps: 79 | # 1. First create the resource. 80 | # sh run_tests.sh pytest -s tests/caches/test_create.py --mode=api 81 | # 1.1 If mldev test_create fails, update the uploaded file using this colab 82 | # https://colab.sandbox.google.com/drive/1Fv6KGSs0cg6tlpcUHdsclHussXMEGOXk#scrollTo=RSKmFPx00MVL. 83 | # 2. Find the resource name in debugging print and change the resource name constants.py. 84 | # 3. Run and record get and update tests. 85 | # sh run_tests.sh pytest -s tests/caches/test_get.py --mode=api && sh run_tests.sh pytest -s tests/caches/test_update.py --mode=api && sh run_tests.sh pytest -s tests/caches/test_delete.py --mode=api 86 | test_table: list[pytest_helper.TestTableItem] = [ 87 | pytest_helper.TestTableItem( 88 | name='test_caches_create_with_gcs_uri', 89 | exception_if_mldev='404', 90 | parameters=_CREATE_CACHED_CONTENT_PARAMETERS_GCS_URI, 91 | ), 92 | pytest_helper.TestTableItem( 93 | name='test_caches_create_with_googleai_file', 94 | exception_if_vertex='404', 95 | parameters=_CREATE_CACHED_CONTENT_PARAMETERS_GOOGLEAI_FILE, 96 | skip_in_api_mode='Create is not reproducible in the API mode.', 97 | ), 98 | ] 99 | pytestmark = pytest_helper.setup( 100 | file=__file__, 101 | globals_for_file=globals(), 102 | test_method='caches.create', 103 | test_table=test_table, 104 | ) 105 | pytest_plugins = ('pytest_asyncio',) 106 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_delete.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import types 19 | from .. import pytest_helper 20 | from . import constants 21 | 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | skip_in_api_mode='Delete is not reproducible in the API mode.', 26 | name='test_caches_delete_with_vertex_cache_name', 27 | exception_if_mldev='PERMISSION_DENIED', 28 | parameters=types._DeleteCachedContentParameters( 29 | name=constants.CACHED_CONTENT_NAME_VERTEX, 30 | ), 31 | ), 32 | pytest_helper.TestTableItem( 33 | skip_in_api_mode='Delete is not reproducible in the API mode.', 34 | name='test_caches_delete_with_mldev_cache_name', 35 | exception_if_vertex='NOT_FOUND', 36 | parameters=types._DeleteCachedContentParameters( 37 | name=constants.CACHED_CONTENT_NAME_MLDEV, 38 | ), 39 | ), 40 | ] 41 | pytestmark = pytest_helper.setup( 42 | file=__file__, 43 | globals_for_file=globals(), 44 | test_method='caches.delete', 45 | test_table=test_table, 46 | ) 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_async_delete(client): 51 | if client._api_client.vertexai: 52 | await client.aio.caches.delete(name=constants.CACHED_CONTENT_NAME_VERTEX) 53 | else: 54 | await client.aio.caches.delete(name=constants.CACHED_CONTENT_NAME_MLDEV) 55 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_delete_custom_url.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import types 19 | from .. import pytest_helper 20 | from . import constants 21 | 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | skip_in_api_mode='Delete is not reproducible in the API mode.', 26 | name='test_caches_delete_with_vertex_cache_name', 27 | exception_if_mldev='404', 28 | parameters=types._DeleteCachedContentParameters( 29 | name=constants.CACHED_CONTENT_NAME_VERTEX, 30 | config={ 31 | 'http_options': constants.VERTEX_HTTP_OPTIONS, 32 | }, 33 | ), 34 | ), 35 | pytest_helper.TestTableItem( 36 | skip_in_api_mode='Delete is not reproducible in the API mode.', 37 | name='test_caches_delete_with_mldev_cache_name', 38 | exception_if_vertex='404', 39 | parameters=types._DeleteCachedContentParameters( 40 | name=constants.CACHED_CONTENT_NAME_MLDEV, 41 | config={ 42 | 'http_options': constants.MLDEV_HTTP_OPTIONS, 43 | }, 44 | ), 45 | ), 46 | ] 47 | pytestmark = pytest_helper.setup( 48 | file=__file__, 49 | globals_for_file=globals(), 50 | test_method='caches.delete', 51 | test_table=test_table, 52 | ) 53 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_get.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import types 19 | from .. import pytest_helper 20 | from . import constants 21 | 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | skip_in_api_mode='Get is not reproducible in the API mode.', 26 | name='test_caches_get_with_vertex_cache_name', 27 | exception_if_mldev='PERMISSION_DENIED', 28 | parameters=types._GetCachedContentParameters( 29 | name=constants.CACHED_CONTENT_NAME_VERTEX, 30 | ), 31 | ), 32 | pytest_helper.TestTableItem( 33 | skip_in_api_mode='Get is not reproducible in the API mode.', 34 | name='test_caches_get_with_mldev_cache_name', 35 | exception_if_vertex='NOT_FOUND', 36 | parameters=types._GetCachedContentParameters( 37 | name=constants.CACHED_CONTENT_NAME_MLDEV, 38 | ), 39 | ), 40 | pytest_helper.TestTableItem( 41 | skip_in_api_mode='Get is not reproducible in the API mode.', 42 | name='test_caches_get_with_mldev_cache_name_partial_1', 43 | exception_if_vertex='NOT_FOUND', 44 | parameters=types._GetCachedContentParameters( 45 | name='cachedContents/op47f693jk6b' 46 | ), 47 | ), 48 | ] 49 | pytestmark = pytest_helper.setup( 50 | file=__file__, 51 | globals_for_file=globals(), 52 | test_method='caches.get', 53 | test_table=test_table, 54 | ) 55 | 56 | 57 | @pytest.mark.asyncio 58 | async def test_async_get(client): 59 | if client._api_client.vertexai: 60 | response = await client.aio.caches.get( 61 | name=constants.CACHED_CONTENT_NAME_VERTEX 62 | ) 63 | assert response 64 | else: 65 | await client.aio.caches.get(name=constants.CACHED_CONTENT_NAME_MLDEV) 66 | 67 | 68 | def test_different_cache_name_formats(client): 69 | if client._api_client.vertexai: 70 | response1 = client.caches.get( 71 | name='projects/801452371447/locations/us-central1/cachedContents/1739497763885809664' 72 | ) 73 | assert response1 74 | response2 = client.caches.get( 75 | name='locations/us-central1/cachedContents/1739497763885809664' 76 | ) 77 | assert response2 78 | response3 = client.caches.get( 79 | name='cachedContents/1739497763885809664' 80 | ) 81 | assert response3 82 | response4 = client.caches.get( 83 | name='1739497763885809664' 84 | ) 85 | assert response4 86 | else: 87 | response1 = client.caches.get( 88 | name='cachedContents/op47f693jk6b' 89 | ) 90 | assert response1 91 | response2 = client.caches.get( 92 | name='op47f693jk6b' 93 | ) 94 | assert response2 95 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_get_custom_url.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import types 19 | from .. import pytest_helper 20 | from . import constants 21 | 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | skip_in_api_mode='Get is not reproducible in the API mode.', 26 | name='test_caches_get_with_vertex_cache_name', 27 | exception_if_mldev='404', 28 | parameters=types._GetCachedContentParameters( 29 | name=constants.CACHED_CONTENT_NAME_VERTEX, 30 | config={ 31 | 'http_options': constants.VERTEX_HTTP_OPTIONS, 32 | }, 33 | ), 34 | ), 35 | pytest_helper.TestTableItem( 36 | skip_in_api_mode='Get is not reproducible in the API mode.', 37 | name='test_caches_get_with_mldev_cache_name', 38 | exception_if_vertex='404', 39 | parameters=types._GetCachedContentParameters( 40 | name=constants.CACHED_CONTENT_NAME_MLDEV, 41 | config={ 42 | 'http_options': constants.MLDEV_HTTP_OPTIONS, 43 | }, 44 | ), 45 | ), 46 | ] 47 | pytestmark = pytest_helper.setup( 48 | file=__file__, 49 | globals_for_file=globals(), 50 | test_method='caches.get', 51 | test_table=test_table, 52 | ) 53 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_list.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Test caches list method.""" 18 | 19 | import pytest 20 | from ... import types 21 | from .. import pytest_helper 22 | 23 | 24 | test_table: list[pytest_helper.TestTableItem] = [ 25 | pytest_helper.TestTableItem( 26 | skip_in_api_mode='List is not reproducible in the API mode.', 27 | name='test_caches_list', 28 | parameters=types._ListCachedContentsParameters(config={'page_size': 3}), 29 | ), 30 | ] 31 | pytestmark = [ 32 | pytest_helper.setup( 33 | file=__file__, 34 | globals_for_file=globals(), 35 | test_method='caches.list', 36 | test_table=test_table, 37 | ) 38 | ] 39 | 40 | 41 | def test_pager(client): 42 | cached_contents = client.caches.list(config={'page_size': 2}) 43 | 44 | assert cached_contents.name == 'cached_contents' 45 | assert cached_contents.page_size == 2 46 | assert len(cached_contents) <= 2 47 | 48 | # Iterate through all the pages. Then next_page() should raise an exception. 49 | for _ in cached_contents: 50 | pass 51 | with pytest.raises(IndexError, match='No more pages to fetch.'): 52 | cached_contents.next_page() 53 | 54 | 55 | @pytest.mark.asyncio 56 | async def test_async_pager(client): 57 | cached_contents = await client.aio.caches.list(config={'page_size': 2}) 58 | 59 | assert cached_contents.name == 'cached_contents' 60 | assert cached_contents.page_size == 2 61 | assert len(cached_contents) <= 2 62 | 63 | # Iterate through all the pages. Then next_page() should raise an exception. 64 | async for _ in cached_contents: 65 | pass 66 | with pytest.raises(IndexError, match='No more pages to fetch.'): 67 | await cached_contents.next_page() 68 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_update.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import types 19 | from .. import pytest_helper 20 | from . import constants 21 | 22 | _VERTEX_UPDATE_PARAMETERS = types._UpdateCachedContentParameters( 23 | name=constants.CACHED_CONTENT_NAME_VERTEX, 24 | config={ 25 | 'ttl': '7600s', 26 | }, 27 | ) 28 | _MLDEV_UPDATE_PARAMETERS = types._UpdateCachedContentParameters( 29 | name=constants.CACHED_CONTENT_NAME_MLDEV, 30 | config={ 31 | 'ttl': '7600s', 32 | }, 33 | ) 34 | 35 | 36 | test_table: list[pytest_helper.TestTableItem] = [ 37 | pytest_helper.TestTableItem( 38 | skip_in_api_mode='Update has permission issues in the API mode.', 39 | name='test_caches_update_with_vertex_cache_name', 40 | exception_if_mldev='PERMISSION_DENIED', 41 | parameters=_VERTEX_UPDATE_PARAMETERS, 42 | ), 43 | pytest_helper.TestTableItem( 44 | skip_in_api_mode='Update has permission issues in the API mode.', 45 | name='test_caches_update_with_mldev_cache_name', 46 | exception_if_vertex='NOT_FOUND', 47 | parameters=_MLDEV_UPDATE_PARAMETERS, 48 | ), 49 | ] 50 | pytestmark = pytest_helper.setup( 51 | file=__file__, 52 | globals_for_file=globals(), 53 | test_method='caches.update', 54 | test_table=test_table, 55 | ) 56 | 57 | 58 | @pytest.mark.asyncio 59 | async def test_async_update(client): 60 | if client._api_client.vertexai: 61 | response = await client.aio.caches.update( 62 | name=_VERTEX_UPDATE_PARAMETERS.name, 63 | config=_VERTEX_UPDATE_PARAMETERS.config, 64 | ) 65 | assert response 66 | else: 67 | await client.aio.caches.update( 68 | name=_MLDEV_UPDATE_PARAMETERS.name, 69 | config=_MLDEV_UPDATE_PARAMETERS.config, 70 | ) 71 | -------------------------------------------------------------------------------- /google/genai/tests/caches/test_update_custom_url.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import types 19 | from .. import pytest_helper 20 | from . import constants 21 | 22 | _VERTEX_UPDATE_PARAMETERS = types._UpdateCachedContentParameters( 23 | name=constants.CACHED_CONTENT_NAME_VERTEX, 24 | config={ 25 | 'ttl': '7600s', 26 | 'http_options': constants.VERTEX_HTTP_OPTIONS, 27 | }, 28 | ) 29 | _MLDEV_UPDATE_PARAMETERS = types._UpdateCachedContentParameters( 30 | name=constants.CACHED_CONTENT_NAME_MLDEV, 31 | config={ 32 | 'ttl': '7600s', 33 | 'http_options': constants.MLDEV_HTTP_OPTIONS, 34 | }, 35 | ) 36 | 37 | 38 | test_table: list[pytest_helper.TestTableItem] = [ 39 | pytest_helper.TestTableItem( 40 | skip_in_api_mode='Update has permission issues in the API mode.', 41 | name='test_caches_update_with_vertex_cache_name', 42 | exception_if_mldev='404', 43 | parameters=_VERTEX_UPDATE_PARAMETERS, 44 | ), 45 | pytest_helper.TestTableItem( 46 | skip_in_api_mode='Update has permission issues in the API mode.', 47 | name='test_caches_update_with_mldev_cache_name', 48 | exception_if_vertex='404', 49 | parameters=_MLDEV_UPDATE_PARAMETERS, 50 | ), 51 | ] 52 | pytestmark = pytest_helper.setup( 53 | file=__file__, 54 | globals_for_file=globals(), 55 | test_method='caches.update', 56 | test_table=test_table, 57 | ) 58 | 59 | -------------------------------------------------------------------------------- /google/genai/tests/chats/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for the Google GenAI SDK's chats module.""" 2 | -------------------------------------------------------------------------------- /google/genai/tests/chats/test_validate_response.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from ... import types 18 | from ...chats import _validate_response 19 | 20 | 21 | def test_validate_response_default_response(): 22 | response = types.GenerateContentResponse() 23 | 24 | assert not _validate_response(response) 25 | 26 | 27 | def test_validate_response_empty_content(): 28 | response = types.GenerateContentResponse(candidates=[]) 29 | 30 | assert not _validate_response(response) 31 | 32 | 33 | def test_validate_response_empty_parts(): 34 | response = types.GenerateContentResponse( 35 | candidates=[types.Candidate(content=types.Content(parts=[]))] 36 | ) 37 | 38 | assert not _validate_response(response) 39 | 40 | 41 | def test_validate_response_empty_part(): 42 | response = types.GenerateContentResponse( 43 | candidates=[types.Candidate(content=types.Content(parts=[types.Part()]))] 44 | ) 45 | 46 | assert not _validate_response(response) 47 | 48 | 49 | def test_validate_response_part_with_empty_text(): 50 | response = types.GenerateContentResponse( 51 | candidates=[ 52 | types.Candidate(content=types.Content(parts=[types.Part(text='')])) 53 | ] 54 | ) 55 | 56 | assert not _validate_response(response) 57 | 58 | 59 | def test_validate_response_part_with_text(): 60 | response = types.GenerateContentResponse( 61 | candidates=[ 62 | types.Candidate( 63 | content=types.Content( 64 | parts=[types.Part(text='response from model')] 65 | ) 66 | ) 67 | ] 68 | ) 69 | 70 | assert _validate_response(response) 71 | 72 | 73 | def test_validate_response_part_with_function_call(): 74 | response = types.GenerateContentResponse( 75 | candidates=[ 76 | types.Candidate( 77 | content=types.Content( 78 | parts=[ 79 | types.Part( 80 | function_call=types.FunctionCall( 81 | name='foo', args={'bar': 'baz'} 82 | ) 83 | ) 84 | ] 85 | ) 86 | ) 87 | ] 88 | ) 89 | 90 | assert _validate_response(response) 91 | -------------------------------------------------------------------------------- /google/genai/tests/client/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK.""" 18 | -------------------------------------------------------------------------------- /google/genai/tests/client/test_http_options.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for client behavior when issuing requests.""" 18 | 19 | 20 | from ... import _api_client 21 | from ... import types 22 | 23 | 24 | def test_patch_http_options_with_copies_all_fields(): 25 | patch_options = types.HttpOptions( 26 | base_url='https://fake-url.com/', 27 | api_version='v1', 28 | headers={'X-Custom-Header': 'custom_value'}, 29 | timeout=10000, 30 | client_args={'http2': True}, 31 | async_client_args={'http1': True}, 32 | ) 33 | options = types.HttpOptions() 34 | patched = _api_client._patch_http_options(options, patch_options) 35 | http_options_keys = types.HttpOptions.model_fields.keys() 36 | 37 | for key in http_options_keys: 38 | assert hasattr(patched, key) and getattr(patched, key) is not None 39 | assert patched.base_url == 'https://fake-url.com/' 40 | assert patched.api_version == 'v1' 41 | assert patched.headers['X-Custom-Header'] == 'custom_value' 42 | assert patched.timeout == 10000 43 | 44 | 45 | def test_patch_http_options_merges_headers(): 46 | original_options = types.HttpOptions( 47 | headers={ 48 | 'X-Custom-Header': 'different_value', 49 | 'X-different-header': 'different_value', 50 | } 51 | ) 52 | patch_options = types.HttpOptions( 53 | base_url='https://fake-url.com/', 54 | api_version='v1', 55 | headers={'X-Custom-Header': 'custom_value'}, 56 | timeout=10000, 57 | ) 58 | patched = _api_client._patch_http_options(original_options, patch_options) 59 | # If the header is present in both the original and patch options, the patch 60 | # options value should be used 61 | assert patched.headers['X-Custom-Header'] == 'custom_value' 62 | 63 | assert patched.headers['X-different-header'] == 'different_value' 64 | 65 | 66 | def test_patch_http_options_appends_version_headers(): 67 | original_options = types.HttpOptions( 68 | headers={ 69 | 'X-Custom-Header': 'different_value', 70 | 'X-different-header': 'different_value', 71 | } 72 | ) 73 | patch_options = types.HttpOptions( 74 | base_url='https://fake-url.com/', 75 | api_version='v1', 76 | headers={'X-Custom-Header': 'custom_value'}, 77 | timeout=10000, 78 | ) 79 | patched = _api_client._patch_http_options(original_options, patch_options) 80 | assert 'user-agent' in patched.headers 81 | assert 'x-goog-api-client' in patched.headers 82 | 83 | 84 | def test_setting_timeout_populates_server_timeout_header(): 85 | api_client = _api_client.BaseApiClient( 86 | vertexai=False, 87 | api_key='test_api_key', 88 | http_options=types.HttpOptions(timeout=10000), 89 | ) 90 | request = api_client._build_request( 91 | http_method='POST', 92 | path='sample/path', 93 | request_dict={}, 94 | ) 95 | assert 'X-Server-Timeout' in request.headers 96 | assert request.headers['X-Server-Timeout'] == '10' 97 | 98 | 99 | def test_timeout_rounded_to_nearest_second(): 100 | api_client = _api_client.BaseApiClient( 101 | vertexai=False, 102 | api_key='test_api_key', 103 | ) 104 | http_options = types.HttpOptions(timeout=7300) 105 | request = api_client._build_request( 106 | http_method='POST', 107 | path='sample/path', 108 | request_dict={}, 109 | http_options=http_options, 110 | ) 111 | assert request.headers['X-Server-Timeout'] == '8' 112 | 113 | 114 | def test_server_timeout_not_overwritten(): 115 | api_client = _api_client.BaseApiClient( 116 | vertexai=False, 117 | api_key='test_api_key', 118 | ) 119 | http_options = types.HttpOptions( 120 | headers={'X-Server-Timeout': '3'}, 121 | timeout=11000) 122 | request = api_client._build_request( 123 | http_method='POST', 124 | path='sample/path', 125 | request_dict={}, 126 | http_options=http_options, 127 | ) 128 | assert request.headers['X-Server-Timeout'] == '3' 129 | 130 | 131 | def test_server_timeout_not_set_by_default(): 132 | api_client = _api_client.BaseApiClient( 133 | vertexai=False, 134 | api_key='test_api_key', 135 | ) 136 | request = api_client._build_request( 137 | http_method='POST', 138 | path='sample/path', 139 | request_dict={}, 140 | ) 141 | assert not 'X-Server-Timeout' in request.headers -------------------------------------------------------------------------------- /google/genai/tests/common/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's _common module.""" -------------------------------------------------------------------------------- /google/genai/tests/common/test_common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests tools in the _common module.""" 18 | 19 | import warnings 20 | import inspect 21 | import typing 22 | 23 | import pytest 24 | 25 | from ... import _common 26 | from ... import errors 27 | 28 | 29 | def test_warn_once(): 30 | @_common.experimental_warning('Warning!') 31 | def func(): 32 | pass 33 | 34 | with warnings.catch_warnings(record=True) as w: 35 | func() 36 | func() 37 | 38 | assert len(w) == 1 39 | assert w[0].category == errors.ExperimentalWarning 40 | 41 | def test_warn_at_call_line(): 42 | @_common.experimental_warning('Warning!') 43 | def func(): 44 | pass 45 | 46 | with warnings.catch_warnings(record=True) as captured_warnings: 47 | call_line = inspect.currentframe().f_lineno + 1 48 | func() 49 | 50 | assert captured_warnings[0].lineno == call_line 51 | 52 | 53 | def test_is_struct_type(): 54 | assert _common._is_struct_type(list[dict[str, typing.Any]]) 55 | assert _common._is_struct_type(typing.List[typing.Dict[str, typing.Any]]) 56 | assert not _common._is_struct_type(list[dict[str, int]]) 57 | assert not _common._is_struct_type(list[dict[int, typing.Any]]) 58 | assert not _common._is_struct_type(list[str]) 59 | assert not _common._is_struct_type(dict[str, typing.Any]) 60 | assert not _common._is_struct_type(typing.List[typing.Dict[str, int]]) 61 | assert not _common._is_struct_type(typing.List[typing.Dict[int, typing.Any]]) 62 | assert not _common._is_struct_type(typing.List[str]) 63 | assert not _common._is_struct_type(typing.Dict[str, typing.Any]) -------------------------------------------------------------------------------- /google/genai/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Conftest for google.genai tests.""" 18 | 19 | import datetime 20 | import os 21 | from unittest import mock 22 | import uuid 23 | 24 | import pytest 25 | 26 | from .. import _common 27 | from .. import _replay_api_client 28 | from .. import client as google_genai_client_module 29 | 30 | 31 | def pytest_addoption(parser): 32 | parser.addoption( 33 | '--mode', 34 | action='store', 35 | default='auto', 36 | help="""Replay mode. 37 | One of: 38 | * auto: Replay if replay files exist, otherwise record. 39 | * record: Always call the API and record. 40 | * replay: Always replay, fail if replay files do not exist. 41 | * api: Always call the API and do not record. 42 | * tap: Always replay, fail if replay files do not exist. Also sets default values for the API key and replay directory. 43 | """, 44 | ) 45 | 46 | 47 | # Overridden via parameterized test. 48 | @pytest.fixture 49 | def use_vertex(): 50 | return False 51 | 52 | 53 | # Overridden at the module level for each test file. 54 | @pytest.fixture 55 | def replays_prefix(): 56 | return 'test' 57 | 58 | 59 | def _get_replay_id(use_vertex: bool, replays_prefix: str) -> str: 60 | test_name_ending = os.environ.get('PYTEST_CURRENT_TEST').split('::')[-1] 61 | test_name = ( 62 | test_name_ending.split(' ')[0].split('[')[0] 63 | + '.' 64 | + ('vertex' if use_vertex else 'mldev') 65 | ) 66 | return '/'.join([replays_prefix, test_name]) 67 | 68 | 69 | @pytest.fixture 70 | def client(use_vertex, replays_prefix,http_options, request): 71 | mode = request.config.getoption('--mode') 72 | if mode not in ['auto', 'record', 'replay', 'api', 'tap']: 73 | raise ValueError('Invalid mode: ' + mode) 74 | test_function_name = request.function.__name__ 75 | test_filename = os.path.splitext(os.path.basename(request.path))[0] 76 | if test_function_name.startswith(test_filename): 77 | raise ValueError(f""" 78 | {test_function_name}: 79 | Do not include the test filename in the test function name. 80 | keep the test function name short.""") 81 | for word in ['mldev', 'ml_dev', 'vertex']: 82 | if word in test_function_name: 83 | raise ValueError(f""" 84 | {test_function_name}: 85 | You should never ever include api types in the file name (mldev or vertex). 86 | All tests should run against all apis. 87 | Assert an exception if the test is not supported in an API.""") 88 | replay_id = _get_replay_id(use_vertex, replays_prefix) 89 | 90 | if mode == 'tap': 91 | mode = 'replay' 92 | 93 | # Set various environment variables to ensure that the test runs. 94 | os.environ['GOOGLE_API_KEY'] = 'dummy-api-key' 95 | os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = os.path.join( 96 | os.path.dirname(__file__), 97 | 'credentials.json', 98 | ) 99 | os.environ['GOOGLE_CLOUD_PROJECT'] = 'project-id' 100 | os.environ['GOOGLE_CLOUD_LOCATION'] = 'location' 101 | 102 | # Set the replay directory to the root directory of the replays. 103 | # This is needed to ensure that the replay files are found. 104 | replays_root_directory = os.path.abspath( 105 | os.path.join( 106 | os.path.dirname(__file__), 107 | '../../../../../google/cloud/aiplatform/sdk/genai/replays', 108 | ) 109 | ) 110 | os.environ['GOOGLE_GENAI_REPLAYS_DIRECTORY'] = replays_root_directory 111 | 112 | replay_client = _replay_api_client.ReplayApiClient( 113 | mode=mode, 114 | replay_id=replay_id, 115 | vertexai=use_vertex, 116 | http_options=http_options, 117 | ) 118 | 119 | with mock.patch.object( 120 | google_genai_client_module.Client, '_get_api_client' 121 | ) as patch_method: 122 | patch_method.return_value = replay_client 123 | google_genai_client = google_genai_client_module.Client(vertexai=use_vertex) 124 | 125 | # Yield the client so that cleanup can be completed at the end of the test. 126 | yield google_genai_client 127 | 128 | # Save the replay after the test if we were in recording mode. 129 | google_genai_client._api_client.close() 130 | 131 | 132 | @pytest.fixture 133 | def mock_timestamped_unique_name(): 134 | with mock.patch.object( 135 | _common, 136 | 'timestamped_unique_name', 137 | return_value='20240101000000_bd656', 138 | ) as unique_name_mock: 139 | yield unique_name_mock 140 | -------------------------------------------------------------------------------- /google/genai/tests/credentials.json: -------------------------------------------------------------------------------- 1 | // Dummy value to ensure tests can be run for Vertex AI. 2 | { 3 | "type": "service_account" 4 | } 5 | -------------------------------------------------------------------------------- /google/genai/tests/data/animal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/animal.mp4 -------------------------------------------------------------------------------- /google/genai/tests/data/bridge1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/bridge1.png -------------------------------------------------------------------------------- /google/genai/tests/data/checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/checkerboard.png -------------------------------------------------------------------------------- /google/genai/tests/data/google.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/google.jpg -------------------------------------------------------------------------------- /google/genai/tests/data/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/google.png -------------------------------------------------------------------------------- /google/genai/tests/data/google_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/google_small.png -------------------------------------------------------------------------------- /google/genai/tests/data/pixel.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/pixel.m4a -------------------------------------------------------------------------------- /google/genai/tests/data/story.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/python-genai/7eb5b07bf1ffb16761825d96b4c1a536bc395701/google/genai/tests/data/story.pdf -------------------------------------------------------------------------------- /google/genai/tests/errors/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit Tests for the error modules.""" -------------------------------------------------------------------------------- /google/genai/tests/files/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's files module.""" -------------------------------------------------------------------------------- /google/genai/tests/files/test_delete.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Test files delete method.""" 18 | 19 | import pytest 20 | from ... import types 21 | from .. import pytest_helper 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | name='test_delete', 26 | parameters=types._DeleteFileParameters(name='files/1vx8znuf0yje'), 27 | exception_if_vertex='only supported in the Gemini Developer client', 28 | skip_in_api_mode=( 29 | 'The files have a TTL, they cannot be reliably retrieved for a long' 30 | ' time.' 31 | ), 32 | ), 33 | ] 34 | 35 | pytestmark = pytest_helper.setup( 36 | file=__file__, 37 | globals_for_file=globals(), 38 | test_method='files.delete', 39 | test_table=test_table, 40 | ) 41 | 42 | 43 | @pytest.mark.asyncio 44 | async def test_async(client): 45 | with pytest_helper.exception_if_vertex(client, ValueError): 46 | file = await client.aio.files.get(name='files/vjvu9fwk2qj8') 47 | -------------------------------------------------------------------------------- /google/genai/tests/files/test_download.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Test files upload method.""" 18 | 19 | 20 | import pathlib 21 | import pytest 22 | from ... import _transformers as t 23 | from ... import types 24 | from .. import pytest_helper 25 | 26 | 27 | test_table: list[pytest_helper.TestTableItem] = [] 28 | 29 | pytestmark = pytest_helper.setup( 30 | file=__file__, 31 | globals_for_file=globals(), 32 | test_method='t.t_file_name', 33 | test_table=test_table, 34 | ) 35 | 36 | pytest_plugins = ('pytest_asyncio',) 37 | 38 | 39 | def test_name_transform_name(client): 40 | with pytest_helper.exception_if_vertex(client, ValueError): 41 | for file in client.files.list(): 42 | if file.download_uri is not None: 43 | break 44 | else: 45 | raise ValueError('No files found with a `download_uri`.') 46 | 47 | file_id = file.name.split('/')[-1] 48 | video = types.Video(uri=file.download_uri) 49 | generated_video = types.GeneratedVideo(video=video) 50 | for f in [ 51 | file, 52 | file_id, 53 | file.name, 54 | file.uri, 55 | file.download_uri, 56 | video, 57 | generated_video, 58 | ]: 59 | name = t.t_file_name(client, f) 60 | assert name == file_id 61 | 62 | 63 | def test_basic_download(client): 64 | with pytest_helper.exception_if_vertex(client, ValueError): 65 | for file in client.files.list(): 66 | if file.download_uri is not None: 67 | break 68 | else: 69 | raise ValueError('No files found with a `download_uri`.') 70 | 71 | content = client.files.download(file=file) 72 | assert content[4:8] == b'ftyp' 73 | 74 | 75 | @pytest.mark.asyncio 76 | async def test_basic_download_async(client): 77 | with pytest_helper.exception_if_vertex(client, ValueError): 78 | async for file in await client.aio.files.list(): 79 | if file.download_uri is not None: 80 | break 81 | else: 82 | raise ValueError('No files found with a `download_uri`.') 83 | 84 | content = await client.aio.files.download(file=file) 85 | assert content[4:8] == b'ftyp' 86 | -------------------------------------------------------------------------------- /google/genai/tests/files/test_get.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Test files get method.""" 18 | 19 | import pytest 20 | from ... import types 21 | from .. import pytest_helper 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | name='test_get', 26 | parameters=types._GetFileParameters(name='files/vjvu9fwk2qj8'), 27 | exception_if_vertex='only supported in the Gemini Developer client', 28 | skip_in_api_mode=( 29 | 'The files have a TTL, they cannot be reliably retrieved for a long' 30 | ' time.' 31 | ), 32 | ), 33 | ] 34 | 35 | pytestmark = pytest_helper.setup( 36 | file=__file__, 37 | globals_for_file=globals(), 38 | test_method='files.get', 39 | test_table=test_table, 40 | ) 41 | 42 | 43 | @pytest.mark.asyncio 44 | async def test_async(client): 45 | with pytest_helper.exception_if_vertex(client, ValueError): 46 | file = await client.aio.files.get(name='files/vjvu9fwk2qj8') 47 | -------------------------------------------------------------------------------- /google/genai/tests/files/test_list.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Test files list method.""" 18 | 19 | import pytest 20 | 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | test_table: list[pytest_helper.TestTableItem] = [ 25 | pytest_helper.TestTableItem( 26 | name='test_not_empty', 27 | exception_if_vertex='only supported in the Gemini Developer client', 28 | parameters=types._ListFilesParameters( 29 | config=types.ListFilesConfig( 30 | page_size=2, 31 | ), 32 | ), 33 | ), 34 | ] 35 | pytestmark = pytest_helper.setup( 36 | file=__file__, 37 | globals_for_file=globals(), 38 | test_method='files.list', 39 | test_table=test_table, 40 | ) 41 | 42 | 43 | def test_pager(client): 44 | with pytest_helper.exception_if_vertex(client, ValueError): 45 | files = client.files.list(config={'page_size': 2}) 46 | 47 | assert files.name == 'files' 48 | assert files.page_size == 2 49 | assert len(files) <= 2 50 | 51 | # Iterate through all the pages. Then next_page() should raise an exception. 52 | for _ in files: 53 | pass 54 | with pytest.raises(IndexError, match='No more pages to fetch.'): 55 | files.next_page() 56 | 57 | 58 | @pytest.mark.asyncio 59 | async def test_async_pager(client): 60 | with pytest_helper.exception_if_vertex(client, ValueError): 61 | files = await client.aio.files.list(config={'page_size': 2}) 62 | 63 | assert files.name == 'files' 64 | assert files.page_size == 2 65 | assert len(files) <= 2 66 | 67 | # Iterate through all the pages. Then next_page() should raise an exception. 68 | async for _ in files: 69 | pass 70 | with pytest.raises(IndexError, match='No more pages to fetch.'): 71 | await files.next_page() 72 | -------------------------------------------------------------------------------- /google/genai/tests/imports/test_no_optional_imports.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import os 17 | import pytest 18 | 19 | IS_NOT_GITHUB_ACTIONS = os.getenv('GITHUB_ACTIONS') != 'true' 20 | 21 | 22 | @pytest.mark.skipif(IS_NOT_GITHUB_ACTIONS, 23 | reason='This test is only run on GitHub Actions.') 24 | def test_library_can_be_imported_without_optional_dependencies(): 25 | """Tests that the library can be imported without optional dependencies. 26 | """ 27 | from google import genai 28 | from google.genai import types 29 | -------------------------------------------------------------------------------- /google/genai/tests/live/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | -------------------------------------------------------------------------------- /google/genai/tests/live/test_live_response.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Tests for live response handling.""" 17 | import json 18 | from typing import cast 19 | from unittest import mock 20 | from unittest.mock import AsyncMock 21 | 22 | import pytest 23 | 24 | from ... import _api_client as api_client 25 | from ... import _common 26 | from ... import Client 27 | from ... import client as gl_client 28 | from ... import live 29 | from ... import types 30 | 31 | 32 | def mock_api_client(vertexai=False): 33 | """Creates a mock BaseApiClient.""" 34 | mock_client = mock.MagicMock(spec=gl_client.BaseApiClient) 35 | if not vertexai: 36 | mock_client.api_key = 'TEST_API_KEY' 37 | mock_client.location = None 38 | mock_client.project = None 39 | else: 40 | mock_client.api_key = None 41 | mock_client.location = 'us-central1' 42 | mock_client.project = 'test_project' 43 | 44 | mock_client._host = lambda: 'test_host' 45 | mock_client._http_options = types.HttpOptions.model_validate( 46 | {'headers': {}} 47 | ) 48 | mock_client.vertexai = vertexai 49 | return mock_client 50 | 51 | 52 | @pytest.fixture 53 | def mock_websocket(): 54 | """Provides a mock websocket connection.""" 55 | # Use live.ClientConnection if that's the specific type hint in AsyncSession 56 | websocket = AsyncMock(spec=live.ClientConnection) 57 | websocket.send = AsyncMock() 58 | # Set default recv value, will be overridden in the test 59 | websocket.recv = AsyncMock(return_value='{}') 60 | websocket.close = AsyncMock() 61 | return websocket 62 | 63 | 64 | @pytest.mark.parametrize('vertexai', [True, False]) 65 | @pytest.mark.asyncio 66 | async def test_receive_server_content(mock_websocket, vertexai): 67 | 68 | raw_response_json = json.dumps({ 69 | "usageMetadata": { 70 | "promptTokenCount": 15, 71 | "responseTokenCount": 25, 72 | "candidatesTokenCount": 50, 73 | "totalTokenCount": 200, 74 | "responseTokensDetails": [ 75 | { 76 | "tokenCount": 20, 77 | "modality": "TEXT", 78 | } 79 | ], 80 | "candidatesTokensDetails": [ 81 | { 82 | "tokenCount": 10, 83 | "modality": "TEXT", 84 | } 85 | ], 86 | }, 87 | "serverContent": { 88 | "modelTurn": { 89 | "parts": [{"text": "This is a simple response."}] 90 | }, 91 | "turnComplete": True, 92 | "groundingMetadata": { 93 | "web_search_queries": ["test query"], 94 | "groundingChunks": [{ 95 | "web": { 96 | "domain": "google.com", 97 | "title": "Search results", 98 | } 99 | }] 100 | } 101 | } 102 | }) 103 | mock_websocket.recv.return_value = raw_response_json 104 | 105 | session = live.AsyncSession( 106 | api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket 107 | ) 108 | result = await session._receive() 109 | 110 | # Assert the results 111 | assert isinstance(result, types.LiveServerMessage) 112 | 113 | assert ( 114 | result.server_content.model_turn.parts[0].text 115 | == "This is a simple response." 116 | ) 117 | assert result.server_content.turn_complete 118 | assert result.server_content.grounding_metadata.web_search_queries == ["test query"] 119 | assert result.server_content.grounding_metadata.grounding_chunks[0].web.domain == "google.com" 120 | assert result.server_content.grounding_metadata.grounding_chunks[0].web.title == "Search results" 121 | # Verify usageMetadata was parsed 122 | assert isinstance(result.usage_metadata, types.UsageMetadata) 123 | assert result.usage_metadata.prompt_token_count == 15 124 | assert result.usage_metadata.total_token_count == 200 125 | if not vertexai: 126 | assert result.usage_metadata.response_token_count == 25 127 | assert result.usage_metadata.response_tokens_details[0].token_count == 20 128 | else: 129 | # VertexAI maps candidatesTokenCount to responseTokenCount and maps 130 | # candidatesTokensDetails to responseTokensDetails. 131 | assert result.usage_metadata.response_token_count == 50 132 | assert result.usage_metadata.response_tokens_details[0].token_count == 10 133 | -------------------------------------------------------------------------------- /google/genai/tests/live/test_send_client_content.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for live.py.""" 18 | import json 19 | import os 20 | from unittest import mock 21 | 22 | import PIL.Image 23 | import pytest 24 | from websockets import client 25 | 26 | from ... import client as gl_client 27 | from ... import live 28 | from ... import types 29 | 30 | 31 | IMAGE_FILE_PATH = os.path.abspath( 32 | os.path.join(os.path.dirname(__file__), '../data/google.jpg') 33 | ) 34 | image = PIL.Image.open(IMAGE_FILE_PATH) 35 | 36 | 37 | def mock_api_client(vertexai=False): 38 | api_client = mock.MagicMock(spec=gl_client.BaseApiClient) 39 | api_client.api_key = 'TEST_API_KEY' 40 | api_client._host = lambda: 'test_host' 41 | api_client._http_options = {'headers': {}} # Ensure headers exist 42 | api_client.vertexai = vertexai 43 | return api_client 44 | 45 | 46 | @pytest.fixture 47 | def mock_websocket(): 48 | websocket = mock.AsyncMock(spec=client.ClientConnection) 49 | websocket.send = mock.AsyncMock() 50 | websocket.recv = mock.AsyncMock( 51 | return_value='{"serverContent": {"turnComplete": true}}' 52 | ) # Default response 53 | websocket.close = mock.AsyncMock() 54 | return websocket 55 | 56 | 57 | @pytest.mark.parametrize('vertexai', [True, False]) 58 | @pytest.mark.asyncio 59 | async def test_send_content_dict(mock_websocket, vertexai): 60 | session = live.AsyncSession( 61 | api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket 62 | ) 63 | content = [{'parts': [{'text': 'test'}]}] 64 | 65 | await session.send_client_content(turns=content) 66 | mock_websocket.send.assert_called_once() 67 | sent_data = json.loads(mock_websocket.send.call_args[0][0]) 68 | assert 'client_content' in sent_data 69 | 70 | assert sent_data['client_content']['turns'][0]['parts'][0]['text'] == 'test' 71 | 72 | @pytest.mark.parametrize('vertexai', [True, False]) 73 | @pytest.mark.asyncio 74 | async def test_send_content_dict_list(mock_websocket, vertexai): 75 | session = live.AsyncSession( 76 | api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket 77 | ) 78 | content = [{'parts': [{'text': 'test'}]}] 79 | 80 | await session.send_client_content(turns=content) 81 | mock_websocket.send.assert_called_once() 82 | sent_data = json.loads(mock_websocket.send.call_args[0][0]) 83 | assert 'client_content' in sent_data 84 | 85 | assert sent_data['client_content']['turns'][0]['parts'][0]['text'] == 'test' 86 | 87 | @pytest.mark.parametrize('vertexai', [True, False]) 88 | @pytest.mark.asyncio 89 | async def test_send_content_content(mock_websocket, vertexai): 90 | session = live.AsyncSession( 91 | api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket 92 | ) 93 | content = types.Content.model_validate({'parts': [{'text': 'test'}]}) 94 | 95 | await session.send_client_content(turns=content) 96 | mock_websocket.send.assert_called_once() 97 | sent_data = json.loads(mock_websocket.send.call_args[0][0]) 98 | assert 'client_content' in sent_data 99 | 100 | assert sent_data['client_content']['turns'][0]['parts'][0]['text'] == 'test' 101 | 102 | 103 | @pytest.mark.parametrize('vertexai', [True, False]) 104 | @pytest.mark.asyncio 105 | async def test_send_client_content_turn_complete_false( 106 | mock_websocket, vertexai 107 | ): 108 | session = live.AsyncSession( 109 | api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket 110 | ) 111 | 112 | await session.send_client_content(turn_complete=False) 113 | mock_websocket.send.assert_called_once() 114 | sent_data = json.loads(mock_websocket.send.call_args[0][0]) 115 | assert 'client_content' in sent_data 116 | assert sent_data['client_content']['turnComplete'] == False 117 | 118 | 119 | @pytest.mark.parametrize('vertexai', [True, False]) 120 | @pytest.mark.asyncio 121 | async def test_send_client_content_empty( 122 | mock_websocket, vertexai 123 | ): 124 | session = live.AsyncSession( 125 | api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket 126 | ) 127 | 128 | await session.send_client_content() 129 | mock_websocket.send.assert_called_once() 130 | sent_data = json.loads(mock_websocket.send.call_args[0][0]) 131 | assert 'client_content' in sent_data 132 | assert sent_data['client_content']['turnComplete'] == True 133 | -------------------------------------------------------------------------------- /google/genai/tests/mcp/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's _mcp_utils module.""" -------------------------------------------------------------------------------- /google/genai/tests/mcp/test_has_mcp_tool_usage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import typing 17 | from typing import Any 18 | 19 | from ... import _mcp_utils 20 | from ... import types 21 | 22 | if typing.TYPE_CHECKING: 23 | from mcp.types import Tool as McpTool 24 | from mcp import ClientSession as McpClientSession 25 | else: 26 | McpTool: typing.Type = Any 27 | McpClientSession: typing.Type = Any 28 | try: 29 | from mcp.types import Tool as McpTool 30 | from mcp import ClientSession as McpClientSession 31 | except ImportError: 32 | McpTool = None 33 | McpClientSession = None 34 | 35 | 36 | def test_mcp_tools(): 37 | """Test whether the list of tools contains any MCP tools.""" 38 | if McpTool is None: 39 | return 40 | mcp_tools = [ 41 | McpTool( 42 | name='tool', 43 | description='tool-description', 44 | inputSchema={ 45 | 'type': 'OBJECT', 46 | 'properties': { 47 | 'key1': {'type': 'STRING'}, 48 | 'key2': {'type': 'NUMBER'}, 49 | }, 50 | }, 51 | ), 52 | ] 53 | assert _mcp_utils.has_mcp_tool_usage(mcp_tools) 54 | 55 | 56 | def test_mcp_client_session(): 57 | """Test whether the list of tools contains any MCP tools.""" 58 | 59 | class MockMcpClientSession(McpClientSession): 60 | 61 | def __init__(self): 62 | self._read_stream = None 63 | self._write_stream = None 64 | 65 | mcp_tools = [ 66 | MockMcpClientSession(), 67 | ] 68 | assert _mcp_utils.has_mcp_tool_usage(mcp_tools) 69 | 70 | 71 | def test_no_mcp_tools(): 72 | if McpClientSession is None: 73 | return 74 | """Test whether the list of tools contains any MCP tools.""" 75 | gemini_tools = [ 76 | types.Tool( 77 | function_declarations=[ 78 | types.FunctionDeclaration( 79 | name='tool', 80 | description='tool-description', 81 | parameters=types.Schema( 82 | type='OBJECT', 83 | properties={}, 84 | ), 85 | ), 86 | ], 87 | ), 88 | ] 89 | assert not _mcp_utils.has_mcp_tool_usage(gemini_tools) 90 | -------------------------------------------------------------------------------- /google/genai/tests/mcp/test_parse_config_for_mcp_usage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import re 17 | import pytest 18 | from ... import _extra_utils 19 | from ... import types 20 | from ..._adapters import McpToGenAiToolAdapter 21 | 22 | try: 23 | from mcp import types as mcp_types 24 | from mcp import ClientSession as McpClientSession 25 | except ImportError as e: 26 | import sys 27 | 28 | if sys.version_info < (3, 10): 29 | raise ImportError( 30 | 'MCP Tool requires Python 3.10 or above. Please upgrade your Python' 31 | ' version.' 32 | ) from e 33 | else: 34 | raise e 35 | 36 | 37 | def test_parse_empty_config_dict(): 38 | """Test conversion of empty GenerateContentConfigDict to parsed config.""" 39 | config = {} 40 | parsed_config = _extra_utils.parse_config_for_mcp_usage(config) 41 | assert parsed_config == None 42 | 43 | 44 | def test_parse_empty_config_object(): 45 | """Test conversion of empty GenerateContentConfig to parsed config.""" 46 | config = types.GenerateContentConfig() 47 | parsed_config = _extra_utils.parse_config_for_mcp_usage(config) 48 | assert config is not parsed_config # config is not modified 49 | 50 | 51 | def test_parse_config_object_with_tools(): 52 | """Test conversion of GenerateContentConfig with tools to parsed config.""" 53 | 54 | class MockMcpClientSession(McpClientSession): 55 | 56 | def __init__(self): 57 | self._read_stream = None 58 | self._write_stream = None 59 | 60 | async def list_tools(self): 61 | return mcp_types.ListToolsResult( 62 | tools=[ 63 | mcp_types.Tool( 64 | name='get_weather', 65 | description='Get the weather in a city.', 66 | inputSchema={ 67 | 'type': 'object', 68 | 'properties': {'location': {'type': 'string'}}, 69 | }, 70 | ), 71 | mcp_types.Tool( 72 | name='get_weather_2', 73 | description='Different tool to get the weather.', 74 | inputSchema={ 75 | 'type': 'object', 76 | 'properties': {'location': {'type': 'string'}}, 77 | }, 78 | ), 79 | ] 80 | ) 81 | 82 | mock_session_instance = MockMcpClientSession() 83 | config = types.GenerateContentConfig(tools=[mock_session_instance]) 84 | parsed_config = _extra_utils.parse_config_for_mcp_usage(config) 85 | assert config.http_options is None 86 | assert config is not parsed_config # config is not modified 87 | assert re.match( 88 | r'mcp_used/\d+\.\d+\.\d+', 89 | parsed_config.http_options.headers['x-goog-api-client'], 90 | ) 91 | 92 | 93 | def test_parse_config_object_with_tools_and_existing_headers(): 94 | """Test conversion of GenerateContentConfig with tools and existing headers to parsed config.""" 95 | 96 | config = types.GenerateContentConfig( 97 | tools=[ 98 | mcp_types.Tool( 99 | name='get_weather', 100 | description='Get the weather in a city.', 101 | inputSchema={ 102 | 'type': 'object', 103 | 'properties': {'location': {'type': 'string'}}, 104 | }, 105 | ), 106 | mcp_types.Tool( 107 | name='get_weather_2', 108 | description='Different tool to get the weather.', 109 | inputSchema={ 110 | 'type': 'object', 111 | 'properties': {'location': {'type': 'string'}}, 112 | }, 113 | ), 114 | ], 115 | http_options=types.HttpOptions( 116 | headers={ 117 | 'x-goog-api-client': 'google-genai-sdk/1.0.0 gl-python/1.0.0' 118 | } 119 | ), 120 | ) 121 | parsed_config = _extra_utils.parse_config_for_mcp_usage(config) 122 | assert not re.match( 123 | r'mcp_used/\d+\.\d+\.\d+', 124 | config.http_options.headers['x-goog-api-client'], 125 | ) 126 | assert config is not parsed_config # config is not modified 127 | assert re.match( 128 | r'google-genai-sdk/1.0.0 gl-python/1.0.0 mcp_used/\d+\.\d+\.\d+', 129 | parsed_config.http_options.headers['x-goog-api-client'], 130 | ) 131 | -------------------------------------------------------------------------------- /google/genai/tests/mcp/test_set_mcp_usage_header.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from importlib.metadata import version 17 | import re 18 | import typing 19 | from typing import Any 20 | 21 | from ... import _mcp_utils 22 | from ... import types 23 | 24 | _is_mcp_imported = False 25 | if typing.TYPE_CHECKING: 26 | import mcp 27 | 28 | _is_mcp_imported = True 29 | else: 30 | try: 31 | import mcp 32 | 33 | _is_mcp_imported = True 34 | except ImportError: 35 | _is_mcp_imported = False 36 | 37 | 38 | def test_set_mcp_usage_header_from_empty_dict(): 39 | if not _is_mcp_imported: 40 | return 41 | """Test whether the MCP usage header is set correctly from an empty dict.""" 42 | headers = {} 43 | _mcp_utils.set_mcp_usage_header(headers) 44 | assert re.match(r'mcp_used/\d+\.\d+\.\d+', headers['x-goog-api-client']) 45 | 46 | 47 | def test_set_mcp_usage_header_with_existing_header(): 48 | if not _is_mcp_imported: 49 | return 50 | """Test whether the MCP usage header is set correctly from an existing header.""" 51 | headers = {'x-goog-api-client': 'google-genai-sdk/1.0.0 gl-python/1.0.0'} 52 | _mcp_utils.set_mcp_usage_header(headers) 53 | assert re.match( 54 | r'google-genai-sdk/1.0.0 gl-python/1.0.0 mcp_used/\d+\.\d+\.\d+', 55 | headers['x-goog-api-client'], 56 | ) 57 | 58 | 59 | def test_set_mcp_usage_header_with_existing_mcp_header(): 60 | if not _is_mcp_imported: 61 | return 62 | """Test whether the MCP usage header is set correctly from an existing MCP header.""" 63 | headers = { 64 | 'x-goog-api-client': ( 65 | 'google-genai-sdk/1.0.0 gl-python/1.0.0 mcp_used/1.0.0' 66 | ) 67 | } 68 | _mcp_utils.set_mcp_usage_header(headers) 69 | assert re.match( 70 | r'google-genai-sdk/1.0.0 gl-python/1.0.0 mcp_used/\d+\.\d+\.\d+', 71 | headers['x-goog-api-client'], 72 | ) 73 | -------------------------------------------------------------------------------- /google/genai/tests/models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's models module.""" -------------------------------------------------------------------------------- /google/genai/tests/models/constants.py: -------------------------------------------------------------------------------- 1 | VERTEX_HTTP_OPTIONS = { 2 | 'api_version': 'v1beta1', 3 | 'base_url': 'https://us-central1-aiplatform.googleapis.com/', 4 | } 5 | MLDEV_HTTP_OPTIONS = { 6 | 'api_version': 'v1beta', 7 | 'base_url': 'https://generativelanguage.googleapis.com/', 8 | } 9 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_compute_tokens.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import _transformers as t 19 | from ... import types 20 | from .. import pytest_helper 21 | from . import constants 22 | 23 | _COMPUTE_TOKENS_PARAMS = types._ComputeTokensParameters( 24 | model='gemini-1.5-flash', 25 | contents=[t.t_content(None, 'Tell me a story in 300 words.')], 26 | ) 27 | _COMPUTE_TOKENS_PARAMS_VERTEX_CUSTOM_URL = types._ComputeTokensParameters( 28 | model='gemini-1.5-flash', 29 | contents=[t.t_content(None, 'Tell me a story in 300 words.')], 30 | config={'http_options': constants.VERTEX_HTTP_OPTIONS}, 31 | ) 32 | _COMPUTE_TOKENS_PARAMS_MLDEV_CUSTOM_URL = types._ComputeTokensParameters( 33 | model='gemini-1.5-flash', 34 | contents=[t.t_content(None, 'Tell me a story in 300 words.')], 35 | config={'http_options': constants.MLDEV_HTTP_OPTIONS}, 36 | ) 37 | _UNICODE_STRING = '这是一条unicode测试🤪❤★' 38 | 39 | test_table: list[pytest_helper.TestTableItem] = [ 40 | pytest_helper.TestTableItem( 41 | name='test_compute_tokens', 42 | exception_if_mldev='only supported in', 43 | parameters=types._ComputeTokensParameters( 44 | model='gemini-1.5-flash', 45 | contents=[t.t_content(None, 'Tell me a story in 300 words.')], 46 | ), 47 | ), 48 | pytest_helper.TestTableItem( 49 | name='test_compute_tokens_vertex_custom_url', 50 | parameters=_COMPUTE_TOKENS_PARAMS_VERTEX_CUSTOM_URL, 51 | exception_if_mldev='only supported in', 52 | ), 53 | pytest_helper.TestTableItem( 54 | name='test_compute_tokens_mldev_custom_url', 55 | parameters=_COMPUTE_TOKENS_PARAMS_MLDEV_CUSTOM_URL, 56 | exception_if_vertex='404', 57 | exception_if_mldev='only supported in', 58 | ), 59 | pytest_helper.TestTableItem( 60 | name='test_compute_tokens_unicode', 61 | exception_if_mldev='only supported in', 62 | parameters=types._ComputeTokensParameters( 63 | model='gemini-1.5-flash', contents=[t.t_content(None, _UNICODE_STRING)] 64 | ), 65 | ), 66 | ] 67 | pytestmark = pytest_helper.setup( 68 | file=__file__, 69 | globals_for_file=globals(), 70 | test_method='models.compute_tokens', 71 | test_table=test_table, 72 | ) 73 | 74 | 75 | def test_token_bytes_deserialization(client): 76 | if client._api_client.vertexai: 77 | response = client.models.compute_tokens( 78 | model=_COMPUTE_TOKENS_PARAMS.model, 79 | contents=_UNICODE_STRING, 80 | ) 81 | decoded_tokens = b''.join(response.tokens_info[0].tokens) 82 | assert ( 83 | decoded_tokens 84 | == b'\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe6\x9d\xa1unicode\xe6\xb5\x8b\xe8\xaf\x95\xf0\x9f\xa4\xaa\xe2\x9d\xa4\xe2\x98\x85' 85 | ) 86 | assert decoded_tokens.decode('utf-8') == _UNICODE_STRING 87 | 88 | 89 | @pytest.mark.asyncio 90 | async def test_async(client): 91 | if client._api_client.vertexai: 92 | response = await client.aio.models.compute_tokens( 93 | model=_COMPUTE_TOKENS_PARAMS.model, 94 | contents=_COMPUTE_TOKENS_PARAMS.contents, 95 | ) 96 | assert response 97 | else: 98 | with pytest.raises(Exception): 99 | await client.aio.models.compute_tokens( 100 | model=_COMPUTE_TOKENS_PARAMS.model, 101 | contents=_COMPUTE_TOKENS_PARAMS.contents, 102 | ) 103 | 104 | 105 | def test_different_model_names(client): 106 | if client._api_client.vertexai: 107 | response1 = client.models.compute_tokens( 108 | model='gemini-1.5-flash', contents=_COMPUTE_TOKENS_PARAMS.contents 109 | ) 110 | assert response1 111 | response3 = client.models.compute_tokens( 112 | model='publishers/google/models/gemini-1.5-flash', 113 | contents=_COMPUTE_TOKENS_PARAMS.contents, 114 | ) 115 | assert response3 116 | response4 = client.models.compute_tokens( 117 | model='projects/vertexsdk/locations/us-central1/publishers/google/models/gemini-1.5-flash', 118 | contents=_COMPUTE_TOKENS_PARAMS.contents, 119 | ) 120 | assert response4 121 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_count_tokens.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import copy 18 | import pytest 19 | from ... import _transformers as t 20 | from ... import types 21 | from .. import pytest_helper 22 | from . import constants 23 | 24 | _COUNT_TOKENS_PARAMS = types._CountTokensParameters( 25 | model='gemini-1.5-flash', 26 | contents=[t.t_content(None, 'Tell me a story in 300 words.')], 27 | ) 28 | 29 | _COUNT_TOKENS_PARAMS_WITH_SYSTEM_INSTRUCTION = copy.deepcopy( 30 | _COUNT_TOKENS_PARAMS 31 | ) 32 | _COUNT_TOKENS_PARAMS_WITH_SYSTEM_INSTRUCTION.config = { 33 | 'system_instruction': t.t_content(None, 'you are a chatbot.') 34 | } 35 | 36 | _COUNT_TOKENS_PARAMS_WITH_TOOLS = copy.deepcopy(_COUNT_TOKENS_PARAMS) 37 | _COUNT_TOKENS_PARAMS_WITH_TOOLS.config = { 38 | 'tools': [{'google_search_retrieval': {}}] 39 | } 40 | 41 | _COUNT_TOKENS_PARAMS_WITH_GENERATION_CONFIG = copy.deepcopy( 42 | _COUNT_TOKENS_PARAMS 43 | ) 44 | _COUNT_TOKENS_PARAMS_WITH_GENERATION_CONFIG.config = { 45 | 'generation_config': {'max_output_tokens': 50} 46 | } 47 | 48 | _COUNT_TOKENS_PARAMS_VERTEX_CUSTOM_URL = copy.deepcopy(_COUNT_TOKENS_PARAMS) 49 | _COUNT_TOKENS_PARAMS_VERTEX_CUSTOM_URL.config = { 50 | 'http_options': constants.VERTEX_HTTP_OPTIONS 51 | } 52 | _COUNT_TOKENS_PARAMS_MLDEV_CUSTOM_URL = copy.deepcopy(_COUNT_TOKENS_PARAMS) 53 | _COUNT_TOKENS_PARAMS_MLDEV_CUSTOM_URL.config = { 54 | 'http_options': constants.MLDEV_HTTP_OPTIONS 55 | } 56 | 57 | 58 | # TODO(b/378952792): MLDev count_tokens needs to merge contents and model 59 | # param into generateContentRequest field. 60 | test_table: list[pytest_helper.TestTableItem] = [ 61 | pytest_helper.TestTableItem( 62 | name='test_count_tokens', 63 | parameters=_COUNT_TOKENS_PARAMS, 64 | ), 65 | pytest_helper.TestTableItem( 66 | name='test_count_tokens_vertex_custom_url', 67 | parameters=_COUNT_TOKENS_PARAMS_VERTEX_CUSTOM_URL, 68 | exception_if_mldev='404', 69 | ), 70 | pytest_helper.TestTableItem( 71 | name='test_count_tokens_mldev_custom_url', 72 | parameters=_COUNT_TOKENS_PARAMS_MLDEV_CUSTOM_URL, 73 | exception_if_vertex='404', 74 | ), 75 | pytest_helper.TestTableItem( 76 | name='test_count_tokens_with_system_instruction', 77 | exception_if_mldev='not supported', 78 | parameters=_COUNT_TOKENS_PARAMS_WITH_SYSTEM_INSTRUCTION, 79 | ), 80 | pytest_helper.TestTableItem( 81 | name='test_count_tokens_with_tools', 82 | exception_if_mldev='not supported', 83 | parameters=_COUNT_TOKENS_PARAMS_WITH_TOOLS, 84 | ), 85 | pytest_helper.TestTableItem( 86 | name='test_count_tokens_with_generation_config', 87 | exception_if_mldev='not supported', 88 | parameters=_COUNT_TOKENS_PARAMS_WITH_GENERATION_CONFIG, 89 | ), 90 | ] 91 | pytestmark = pytest_helper.setup( 92 | file=__file__, 93 | globals_for_file=globals(), 94 | test_method='models.count_tokens', 95 | test_table=test_table, 96 | ) 97 | 98 | 99 | @pytest.mark.asyncio 100 | async def test_async(client): 101 | response = await client.aio.models.count_tokens( 102 | model=_COUNT_TOKENS_PARAMS.model, contents=_COUNT_TOKENS_PARAMS.contents 103 | ) 104 | assert response 105 | 106 | 107 | def test_different_model_names(client): 108 | if client._api_client.vertexai: 109 | response1 = client.models.count_tokens( 110 | model='gemini-1.5-flash', contents=_COUNT_TOKENS_PARAMS.contents 111 | ) 112 | assert response1 113 | response3 = client.models.count_tokens( 114 | model='publishers/google/models/gemini-1.5-flash', 115 | contents=_COUNT_TOKENS_PARAMS.contents, 116 | ) 117 | assert response3 118 | response4 = client.models.count_tokens( 119 | model='projects/vertexsdk/locations/us-central1/publishers/google/models/gemini-1.5-flash', 120 | contents=_COUNT_TOKENS_PARAMS.contents, 121 | ) 122 | assert response4 123 | else: 124 | response1 = client.models.count_tokens( 125 | model='gemini-1.5-flash', contents=_COUNT_TOKENS_PARAMS.contents 126 | ) 127 | assert response1 128 | response2 = client.models.count_tokens( 129 | model='models/gemini-1.5-flash', contents=_COUNT_TOKENS_PARAMS.contents 130 | ) 131 | assert response2 132 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_delete.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for models.get.""" 18 | 19 | import pytest 20 | 21 | from ... import errors 22 | from ... import types 23 | from .. import pytest_helper 24 | 25 | TEST_API_VERSION = 'test_api_version' 26 | test_table: list[pytest_helper.TestTableItem] = [ 27 | pytest_helper.TestTableItem( 28 | name='test_delete_model', 29 | parameters=types._DeleteModelParameters( 30 | model='models/8533706666867163136' 31 | ), 32 | exception_if_mldev='404', 33 | skip_in_api_mode='cannot be deleted for multiple times', 34 | ), 35 | pytest_helper.TestTableItem( 36 | name='test_delete_model_with_http_options_in_method', 37 | parameters=types._DeleteModelParameters( 38 | model='tmodels/8533706666867163136', 39 | config={ 40 | 'http_options': { 41 | 'api_version': TEST_API_VERSION, 42 | 'headers': {'test': 'headers'}, 43 | }, 44 | }, 45 | ), 46 | exception_if_vertex=TEST_API_VERSION, 47 | exception_if_mldev='404', 48 | skip_in_api_mode='cannot be deleted for multiple times', 49 | ), 50 | pytest_helper.TestTableItem( 51 | name='test_delete_tuned_model', 52 | parameters=types._DeleteModelParameters( 53 | model='tunedModels/generate-num-9598' 54 | ), 55 | exception_if_vertex='404', 56 | skip_in_api_mode='cannot be deleted for multiple times', 57 | ), 58 | ] 59 | pytestmark = pytest_helper.setup( 60 | file=__file__, 61 | globals_for_file=globals(), 62 | test_method='models.delete', 63 | test_table=test_table, 64 | ) 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_async_delete_model_with_http_options_in_method(client): 69 | 70 | with pytest.raises(errors.ClientError) as e: 71 | await client.aio.models.delete( 72 | model='tunedModels/generate-num-888', 73 | config={ 74 | 'http_options': { 75 | 'api_version': TEST_API_VERSION, 76 | 'headers': {'test': 'headers'}, 77 | }, 78 | }, 79 | ) 80 | if client._api_client.vertexai: 81 | assert TEST_API_VERSION in e.value.args[0] 82 | else: 83 | assert '404' in str(e) 84 | 85 | 86 | @pytest.mark.asyncio 87 | async def test_async_delete_tuned_model(client): 88 | if client._api_client.vertexai: 89 | with pytest.raises(errors.ClientError) as e: 90 | await client.aio.models.delete(model='tunedModels/generate-num-888') 91 | assert '404' in str(e) 92 | else: 93 | response = await client.aio.models.delete( 94 | model='tunedModels/generate-num-888' 95 | ) 96 | 97 | 98 | @pytest.mark.asyncio 99 | async def test_async_delete_model(client): 100 | if client._api_client.vertexai: 101 | response = await client.aio.models.delete( 102 | model='models/1071206899942162432' 103 | ) 104 | else: 105 | with pytest.raises(errors.ClientError) as e: 106 | await client.aio.models.delete(model='models/1071206899942162432') 107 | assert '404' in str(e) 108 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_embed_content.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # pylint: disable=protected-access 16 | 17 | 18 | """Tests for models.embedContent().""" 19 | 20 | import pytest 21 | 22 | from ... import _transformers as t 23 | from ... import types 24 | from .. import pytest_helper 25 | 26 | 27 | test_table: list[pytest_helper.TestTableItem] = [ 28 | pytest_helper.TestTableItem( 29 | name='test_single_text', 30 | parameters=types._EmbedContentParameters( 31 | model='text-embedding-004', 32 | contents=t.t_contents(None, 'What is your name?'), 33 | ), 34 | ), 35 | pytest_helper.TestTableItem( 36 | name='test_multi_texts_with_config', 37 | parameters=types._EmbedContentParameters( 38 | model='text-embedding-004', 39 | contents=[ 40 | t.t_content(None, 'What is your name?'), 41 | t.t_content(None, 'I am a model.'), 42 | ], 43 | config={ 44 | 'output_dimensionality': 10, 45 | 'title': 'test_title', 46 | 'task_type': 'RETRIEVAL_DOCUMENT', 47 | 'http_options': { 48 | 'headers': {'test': 'headers'}, 49 | }, 50 | }, 51 | ), 52 | ), 53 | pytest_helper.TestTableItem( 54 | name='test_single_text_with_mime_type_not_supported_in_mldev', 55 | parameters=types._EmbedContentParameters( 56 | model='text-embedding-004', 57 | contents=t.t_contents(None, 'What is your name?'), 58 | config={ 59 | 'output_dimensionality': 10, 60 | 'mime_type': 'text/plain', 61 | }, 62 | ), 63 | exception_if_mldev='parameter is not supported', 64 | ), 65 | pytest_helper.TestTableItem( 66 | name='test_single_text_with_auto_truncate_not_supported_in_mldev', 67 | parameters=types._EmbedContentParameters( 68 | model='text-embedding-004', 69 | contents=t.t_contents(None, 'What is your name?'), 70 | config={ 71 | 'output_dimensionality': 10, 72 | 'auto_truncate': True, 73 | }, 74 | ), 75 | exception_if_mldev='parameter is not supported', 76 | ), 77 | ] 78 | 79 | pytestmark = pytest_helper.setup( 80 | file=__file__, 81 | globals_for_file=globals(), 82 | test_method='models.embed_content', 83 | test_table=test_table, 84 | ) 85 | 86 | 87 | @pytest.mark.asyncio 88 | async def test_async(client): 89 | response = await client.aio.models.embed_content( 90 | model='text-embedding-004', 91 | contents='What is your name?', 92 | config={'output_dimensionality': 10}, 93 | ) 94 | assert response 95 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_generate_content_cached_content.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from ... import _transformers as t 18 | from ... import types 19 | from .. import pytest_helper 20 | 21 | VERTEX_CACHED_CONTENT_NAME = '5648094474862067712' 22 | MLDEV_CACHED_CONTENT_NAME = '5d231lden4nu' 23 | 24 | test_table: list[pytest_helper.TestTableItem] = [ 25 | pytest_helper.TestTableItem( 26 | name='test_cached_content_wrong_name', 27 | exception_if_vertex='INVALID_ARGUMENT', 28 | exception_if_mldev='INVALID_ARGUMENT', 29 | parameters=types._GenerateContentParameters( 30 | model='gemini-1.5-pro-002', 31 | contents=t.t_contents(None, 'What is in these docs?'), 32 | config={ 33 | 'cached_content': 'batchPredictionJobs/123', 34 | }, 35 | ), 36 | ), 37 | pytest_helper.TestTableItem( 38 | name='test_cached_content_partial_vertex_resource_name_0', 39 | exception_if_mldev='INVALID_ARGUMENT', 40 | skip_in_api_mode=( 41 | 'CachedContent API has expiration and permission issues' 42 | ), 43 | parameters=types._GenerateContentParameters( 44 | model='gemini-1.5-pro-002', 45 | contents=t.t_contents(None, 'What is in these docs?'), 46 | config={ 47 | 'cached_content': f'locations/us-central1/cachedContents/{VERTEX_CACHED_CONTENT_NAME}', 48 | }, 49 | ), 50 | ), 51 | pytest_helper.TestTableItem( 52 | name='test_cached_content_partial_vertex_resource_name_1', 53 | exception_if_mldev='PERMISSION_DENIED', 54 | skip_in_api_mode=( 55 | 'CachedContent API has expiration and permission issues' 56 | ), 57 | parameters=types._GenerateContentParameters( 58 | model='gemini-1.5-pro-002', 59 | contents=t.t_contents(None, 'What is in these docs?'), 60 | config={ 61 | 'cached_content': ( 62 | f'cachedContents/{VERTEX_CACHED_CONTENT_NAME}' 63 | ), 64 | }, 65 | ), 66 | ), 67 | pytest_helper.TestTableItem( 68 | name='test_cached_content_partial_vertex_resource_name_2', 69 | exception_if_mldev='PERMISSION_DENIED', 70 | skip_in_api_mode=( 71 | 'CachedContent API has expiration and permission issues' 72 | ), 73 | parameters=types._GenerateContentParameters( 74 | model='gemini-1.5-pro-002', 75 | contents=t.t_contents(None, 'What is in these docs?'), 76 | config={ 77 | 'cached_content': VERTEX_CACHED_CONTENT_NAME, 78 | }, 79 | ), 80 | ), 81 | pytest_helper.TestTableItem( 82 | name='test_cached_content_partial_mldev_resource_name_1', 83 | exception_if_vertex='NOT_FOUND', 84 | skip_in_api_mode=( 85 | 'CachedContent API has expiration and permission issues' 86 | ), 87 | parameters=types._GenerateContentParameters( 88 | model='gemini-1.5-pro-001', 89 | contents=t.t_contents(None, 'What is in these docs?'), 90 | config={ 91 | 'cached_content': f'{MLDEV_CACHED_CONTENT_NAME}', 92 | }, 93 | ), 94 | ), 95 | pytest_helper.TestTableItem( 96 | name='test_cached_content_for_vertex', 97 | exception_if_mldev='INVALID_ARGUMENT', 98 | skip_in_api_mode=( 99 | 'CachedContent API has expiration and permission issues' 100 | ), 101 | parameters=types._GenerateContentParameters( 102 | model='gemini-1.5-pro-002', 103 | contents=t.t_contents(None, 'What is in these docs?'), 104 | config={ 105 | 'cached_content': f'projects/964831358985/locations/us-central1/cachedContents/{VERTEX_CACHED_CONTENT_NAME}', 106 | }, 107 | ), 108 | ), 109 | pytest_helper.TestTableItem( 110 | name='test_cached_content_for_mldev', 111 | exception_if_vertex='NOT_FOUND', 112 | skip_in_api_mode=( 113 | 'CachedContent API has expiration and permission issues' 114 | ), 115 | parameters=types._GenerateContentParameters( 116 | model='gemini-1.5-pro-001', 117 | contents=t.t_contents(None, 'Tell me a story in 300 words.'), 118 | config={ 119 | 'cached_content': f'cachedContents/{MLDEV_CACHED_CONTENT_NAME}', 120 | }, 121 | ), 122 | ), 123 | ] 124 | 125 | 126 | pytestmark = pytest_helper.setup( 127 | file=__file__, 128 | globals_for_file=globals(), 129 | test_method='models.generate_content', 130 | test_table=test_table, 131 | ) 132 | pytest_plugins = ('pytest_asyncio',) 133 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_generate_content_config_zero_value.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from pydantic import BaseModel, ValidationError 18 | import pytest 19 | from ... import _transformers as t 20 | from ... import errors 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | test_table: list[pytest_helper.TestTableItem] = [ 25 | pytest_helper.TestTableItem( 26 | name='test_candidate_count_zero', 27 | parameters=types._GenerateContentParameters( 28 | model='gemini-1.5-flash-002', 29 | contents=t.t_contents(None, 'What is your name?'), 30 | config={ 31 | 'candidate_count': 0, 32 | }, 33 | ), 34 | exception_if_mldev='400' 35 | ), 36 | pytest_helper.TestTableItem( 37 | name='test_max_output_tokens_zero', 38 | parameters=types._GenerateContentParameters( 39 | model='gemini-1.5-flash-002', 40 | contents=t.t_contents(None, 'What is your name?'), 41 | config={ 42 | 'max_output_tokens': 0, 43 | }, 44 | ), 45 | exception_if_vertex='400', 46 | exception_if_mldev='400', 47 | ), 48 | pytest_helper.TestTableItem( 49 | name='test_logprobs_zero', 50 | parameters=types._GenerateContentParameters( 51 | model='gemini-1.5-flash-002', 52 | contents=t.t_contents(None, 'What is your name?'), 53 | config={ 54 | 'logprobs': 0, 55 | }, 56 | ), 57 | exception_if_mldev='response_logprobs is true', 58 | exception_if_vertex='setting response_logprobs to be true', 59 | skip_in_api_mode='it will encounter 400 for api mode', 60 | ), 61 | pytest_helper.TestTableItem( 62 | name='test_logprobs_zero_with_response_logprobs_true', 63 | parameters=types._GenerateContentParameters( 64 | model='gemini-1.5-flash-002', 65 | contents=t.t_contents(None, 'What is your name?'), 66 | config={ 67 | 'response_logprobs': True, 68 | 'logprobs': 0, 69 | }, 70 | ), 71 | # ML DEV discovery doc supports response_logprobs but the backend 72 | # does not. 73 | # TODO: update replay test json files when ML Dev backend is updated. 74 | exception_if_mldev='INVALID_ARGUMENT', 75 | ), 76 | pytest_helper.TestTableItem( 77 | name='test_presence_penalty_zero', 78 | parameters=types._GenerateContentParameters( 79 | model='gemini-1.5-flash-002', 80 | contents=t.t_contents(None, 'What is your name?'), 81 | config={ 82 | 'presence_penalty': 0, 83 | }, 84 | ), 85 | ), 86 | pytest_helper.TestTableItem( 87 | name='test_frequency_penalty_zero', 88 | parameters=types._GenerateContentParameters( 89 | model='gemini-1.5-flash-002', 90 | contents=t.t_contents(None, 'What is your name?'), 91 | config={ 92 | 'frequency_penalty': 0, 93 | }, 94 | ), 95 | ), 96 | ] 97 | 98 | pytestmark = pytest_helper.setup( 99 | file=__file__, 100 | globals_for_file=globals(), 101 | test_method='models.generate_content', 102 | test_table=test_table, 103 | ) 104 | pytest_plugins = ('pytest_asyncio',) 105 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_generate_content_from_apikey.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from .. import pytest_helper 3 | 4 | pytestmark = pytest_helper.setup( 5 | file=__file__, 6 | globals_for_file=globals(), 7 | test_method='models.generate_content', 8 | ) 9 | 10 | 11 | def test_simple_request(client): 12 | # TODO(b/388917450): Add Vertex AI in Express mode test suite 13 | client._api_client.project = None 14 | client._api_client.location = None 15 | 16 | # To record a replay file, replace with api key (from Vertex AI Express). 17 | # API mode will not work if the API key is a ML Dev API key. 18 | # After recording, change the string back to 'key'. 19 | client._api_client._http_options.headers['x-goog-api-key'] = 'key' 20 | if not client._api_client.vertexai: 21 | return 22 | response = client.models.generate_content( 23 | model='gemini-2.0-flash-001', contents='Tell me a joke.' 24 | ) 25 | assert response.text 26 | 27 | 28 | def test_simple_request_stream(client): 29 | # TODO(b/388917450): Add Vertex AI in Express mode test suite 30 | client._api_client.project = None 31 | client._api_client.location = None 32 | 33 | # To record a replay file, replace with api key (from Vertex AI Express). 34 | # API mode will not work if the API key is a ML Dev API key. 35 | # After recording, change the string back to 'key'. 36 | client._api_client._http_options.headers['x-goog-api-key'] = 'key' 37 | if not client._api_client.vertexai: 38 | return 39 | 40 | response = client.models.generate_content_stream( 41 | model='gemini-2.0-flash-001', contents='Tell me a joke.' 42 | ) 43 | 44 | assert any(chunk.text for chunk in response) 45 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_generate_content_http_options.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from ... import _transformers as t 18 | from ... import types 19 | from .. import pytest_helper 20 | 21 | test_table: list[pytest_helper.TestTableItem] = [ 22 | pytest_helper.TestTableItem( 23 | name='test_generate_content_with_http_options', 24 | parameters=types._GenerateContentParameters( 25 | model='models/gemini-1.5-flash', 26 | contents=t.t_contents(None, 'how are you doing?'), 27 | ), 28 | exception_if_vertex='404', 29 | ), 30 | ] 31 | 32 | pytestmark = pytest_helper.setup( 33 | file=__file__, 34 | globals_for_file=globals(), 35 | test_method='models.generate_content', 36 | test_table=test_table, 37 | http_options={ 38 | 'api_version': 'v1alpha', 39 | }, 40 | ) 41 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_generate_content_media_resolution.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from ... import types 18 | from .. import pytest_helper 19 | 20 | test_table: list[pytest_helper.TestTableItem] = [ 21 | pytest_helper.TestTableItem( 22 | name='test_video_audio_uri_with_media_resolution', 23 | parameters=types._GenerateContentParameters( 24 | model='gemini-2.0-flash', 25 | contents=[ 26 | types.Content( 27 | role='user', 28 | parts=[ 29 | types.Part.from_text( 30 | text=( 31 | 'Is the audio related to the video? ' 32 | 'If so, how? ' 33 | 'What are the common themes? ' 34 | 'What are the different emphases?' 35 | ) 36 | ) 37 | ], 38 | ), 39 | types.Content( 40 | role='user', 41 | parts=[ 42 | types.Part.from_uri( 43 | file_uri='gs://cloud-samples-data/generative-ai/video/pixel8.mp4', 44 | mime_type='video/mp4', 45 | ) 46 | ], 47 | ), 48 | types.Content( 49 | role='user', 50 | parts=[ 51 | types.Part.from_uri( 52 | file_uri='gs://cloud-samples-data/generative-ai/audio/pixel.mp3', 53 | mime_type='audio/mpeg', 54 | ) 55 | ], 56 | ), 57 | ], 58 | config={ 59 | 'system_instruction': types.Content( 60 | role='user', 61 | parts=[ 62 | types.Part.from_text( 63 | text=( 64 | 'you are a helpful assistant for people with ' 65 | 'visual and hearing disabilities.' 66 | ) 67 | ) 68 | ], 69 | ), 70 | 'media_resolution': 'MEDIA_RESOLUTION_LOW', 71 | }, 72 | ), 73 | exception_if_mldev='400', 74 | ) 75 | ] 76 | 77 | 78 | def test_low_media_resolution(client): 79 | with pytest_helper.exception_if_vertex(client, ValueError): 80 | file = client.files.upload(file='tests/data/google.png') 81 | response = client.models.generate_content( 82 | model='gemini-2.0-flash', 83 | contents=[file, 'Describe the image.'], 84 | config=types.GenerateContentConfig( 85 | media_resolution='MEDIA_RESOLUTION_LOW', 86 | http_options= types.HttpOptions(api_version='v1alpha', base_url='https://generativelanguage.googleapis.com') 87 | ), 88 | ) 89 | assert response.text 90 | 91 | 92 | pytestmark = pytest_helper.setup( 93 | file=__file__, 94 | globals_for_file=globals(), 95 | test_method='models.generate_content', 96 | test_table=test_table, 97 | ) 98 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_generate_content_thought.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from ... import _api_client 17 | from ... import _transformers as t 18 | from ... import errors 19 | from ... import types 20 | from .. import pytest_helper 21 | 22 | 23 | test_table: list[pytest_helper.TestTableItem] = [ 24 | pytest_helper.TestTableItem( 25 | name='test_generate_content_thought', 26 | parameters=types._GenerateContentParameters( 27 | model='gemini-2.5-pro-preview-03-25', 28 | contents=t.t_contents(None, 'Explain the monty hall problem.'), 29 | config={ 30 | 'thinking_config': { 31 | 'include_thoughts': True, 32 | 'thinking_budget': 10000}, 33 | }, 34 | ), 35 | exception_if_vertex='400', 36 | ), 37 | pytest_helper.TestTableItem( 38 | name='test_generate_content_thought_v1alpha', 39 | parameters=types._GenerateContentParameters( 40 | model='gemini-2.5-pro-preview-03-25', 41 | contents=t.t_contents( 42 | None, 'What is the sum of natural numbers from 1 to 100?' 43 | ), 44 | config={ 45 | 'thinking_config': { 46 | 'include_thoughts': True, 47 | 'thinking_budget': 10000 48 | }, 49 | 'http_options': { 50 | 'api_version': 'v1alpha' 51 | }, 52 | }, 53 | ), 54 | exception_if_vertex='404', 55 | ), 56 | ] 57 | 58 | 59 | pytestmark = pytest_helper.setup( 60 | file=__file__, 61 | globals_for_file=globals(), 62 | test_method='models.generate_content', 63 | test_table=test_table, 64 | ) 65 | 66 | 67 | def test_thought_signature_with_thinking_budget(client): 68 | with pytest_helper.exception_if_vertex(client, errors.ClientError): 69 | response = client.models.generate_content( 70 | model='gemini-2.5-pro-preview-03-25', 71 | contents='What is the sum of natural numbers from 1 to 100?', 72 | config={ 73 | 'thinking_config': { 74 | 'include_thoughts': True, 75 | 'thinking_budget': 10000, 76 | }, 77 | 'http_options': {'api_version': 'v1alpha'}, 78 | }, 79 | ) 80 | has_thought = False 81 | if response.candidates: 82 | for candidate in response.candidates: 83 | for part in candidate.content.parts: 84 | if part.thought: 85 | has_thought = True 86 | break 87 | assert has_thought 88 | 89 | 90 | def test_thought_with_include_thoughts_v1alpha(client): 91 | # Thoughts have been disabled in the API. 92 | with pytest_helper.exception_if_vertex(client, errors.ClientError): 93 | response = client.models.generate_content( 94 | model='gemini-2.0-flash-thinking-exp', 95 | contents='What is the sum of natural numbers from 1 to 100?', 96 | config={ 97 | 'thinking_config': {'include_thoughts': True}, 98 | 'http_options': {'api_version': 'v1alpha'}, 99 | }, 100 | ) 101 | has_thought = False 102 | if response.candidates: 103 | for candidate in response.candidates: 104 | for part in candidate.content.parts: 105 | if part.thought: 106 | has_thought = True 107 | break 108 | assert has_thought 109 | 110 | 111 | def test_no_thought_with_default_config(client): 112 | with pytest_helper.exception_if_vertex(client, errors.ClientError): 113 | response = client.models.generate_content( 114 | model='gemini-2.0-flash-thinking-exp', 115 | contents='What is the sum of natural numbers from 1 to 100?', 116 | ) 117 | has_thought = False 118 | for candidate in response.candidates: 119 | for part in candidate.content.parts: 120 | if part.thought: 121 | has_thought = True 122 | break 123 | assert not has_thought 124 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_get.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for models.get.""" 18 | 19 | import pytest 20 | from ... import errors 21 | from ... import types 22 | from .. import pytest_helper 23 | 24 | 25 | test_http_options = {'api_version': 'v1', 'headers': {'test': 'headers'}} 26 | 27 | test_table: list[pytest_helper.TestTableItem] = [ 28 | pytest_helper.TestTableItem( 29 | name='test_get_vertex_tuned_model', 30 | parameters=types._GetModelParameters( 31 | model='models/2171259487439028224' 32 | ), 33 | exception_if_mldev='404', 34 | ), 35 | pytest_helper.TestTableItem( 36 | name='test_get_mldev_tuned_model', 37 | parameters=types._GetModelParameters( 38 | model='tunedModels/generatenum5443-ekrw7ie9wis23zbeogbw6jq8' 39 | ), 40 | exception_if_vertex='404', 41 | ), 42 | pytest_helper.TestTableItem( 43 | name='test_get_vertex_tuned_model_with_http_options_in_method', 44 | parameters=types._GetModelParameters( 45 | model='models/2171259487439028224', 46 | config={ 47 | 'http_options': test_http_options, 48 | }, 49 | ), 50 | exception_if_mldev='404', 51 | ), 52 | pytest_helper.TestTableItem( 53 | name='test_get_mldev_base_model_with_http_options_in_method', 54 | parameters=types._GetModelParameters( 55 | model='gemini-1.5-flash', 56 | config={ 57 | 'http_options': test_http_options, 58 | }, 59 | ), 60 | exception_if_vertex='404', 61 | ), 62 | pytest_helper.TestTableItem( 63 | name='test_get_base_model', 64 | parameters=types._GetModelParameters(model='gemini-1.5-flash'), 65 | ), 66 | pytest_helper.TestTableItem( 67 | name='test_get_base_model_with_models_prefix', 68 | parameters=types._GetModelParameters(model='models/gemini-1.5-flash'), 69 | exception_if_vertex='400', 70 | ), 71 | ] 72 | pytestmark = pytest_helper.setup( 73 | file=__file__, 74 | globals_for_file=globals(), 75 | test_method='models.get', 76 | test_table=test_table, 77 | ) 78 | 79 | 80 | @pytest.mark.asyncio 81 | async def test_async_get_tuned_model(client): 82 | if client._api_client.vertexai: 83 | with pytest.raises(errors.ClientError) as e: 84 | await client.aio.models.get(model='tunedModels/generate-num-1896') 85 | assert '404' in str(e) 86 | else: 87 | response = await client.aio.models.get( 88 | model='tunedModels/generate-num-1896' 89 | ) 90 | 91 | 92 | @pytest.mark.asyncio 93 | async def test_async_get_model(client): 94 | if client._api_client.vertexai: 95 | response = await client.aio.models.get( 96 | model='models/7687416965014487040', 97 | config={'http_options': test_http_options}, 98 | ) 99 | else: 100 | with pytest.raises(errors.ClientError) as e: 101 | await client.aio.models.get( 102 | model='models/7687416965014487040', 103 | config={'http_options': test_http_options}, 104 | ) 105 | assert '404' in str(e) 106 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_update.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | import pytest 18 | from ... import errors 19 | from ... import types 20 | from .. import pytest_helper 21 | 22 | test_http_options = {'headers': {'test': 'headers'}} 23 | 24 | test_table: list[pytest_helper.TestTableItem] = [ 25 | pytest_helper.TestTableItem( 26 | name='test_mldev_tuned_models_update', 27 | parameters=types._UpdateModelParameters( 28 | model='tunedModels/generatenum5443-ekrw7ie9wis23zbeogbw6jq8', 29 | config={ 30 | 'display_name': 'My tuned gemini-1.5', 31 | }, 32 | ), 33 | exception_if_vertex='404', 34 | ), 35 | pytest_helper.TestTableItem( 36 | name='test_vertex_tuned_models_update', 37 | parameters=types._UpdateModelParameters( 38 | model='models/2171259487439028224', 39 | config={ 40 | 'description': ( 41 | 'My SupervisedTuningJob' 42 | ), 43 | 'default_checkpoint_id': '8', 44 | }, 45 | ), 46 | exception_if_mldev='404', 47 | ), 48 | pytest_helper.TestTableItem( 49 | name='test_mldev_tuned_models_update_with_http_options_in_method', 50 | parameters=types._UpdateModelParameters( 51 | model='tunedModels/generatenum5443-ekrw7ie9wis23zbeogbw6jq8', 52 | config={ 53 | 'display_name': 'My tuned gemini-1.0', 54 | 'http_options': test_http_options, 55 | }, 56 | ), 57 | exception_if_vertex='404', 58 | ), 59 | pytest_helper.TestTableItem( 60 | name='test_vertex_tuned_models_update_with_http_options_in_method', 61 | parameters=types._UpdateModelParameters( 62 | model='models/2171259487439028224', 63 | config={ 64 | 'description': ( 65 | 'My SupervisedTuningJob' 66 | ), 67 | 'default_checkpoint_id': '8', 68 | 'http_options': test_http_options, 69 | }, 70 | ), 71 | exception_if_mldev='404', 72 | ), 73 | ] 74 | 75 | pytestmark = pytest_helper.setup( 76 | file=__file__, 77 | globals_for_file=globals(), 78 | test_method='models.update', 79 | test_table=test_table, 80 | ) 81 | 82 | 83 | @pytest.mark.asyncio 84 | async def test_async_update_tuned_model(client): 85 | if client._api_client.vertexai: 86 | with pytest.raises(errors.ClientError) as e: 87 | await client.aio.models.update( 88 | model='tunedModels/generatenum5443-ekrw7ie9wis23zbeogbw6jq8', 89 | config={ 90 | 'description': 'My tuned gemini-1.0', 91 | 'http_options': test_http_options, 92 | }, 93 | ) 94 | assert '404' in str(e) 95 | else: 96 | response = await client.aio.models.update( 97 | model='tunedModels/generatenum5443-ekrw7ie9wis23zbeogbw6jq8', 98 | config={ 99 | 'description': 'My tuned gemini-1.5', 100 | 'http_options': test_http_options, 101 | }, 102 | ) 103 | 104 | 105 | @pytest.mark.asyncio 106 | async def test_async_update_model(client): 107 | if client._api_client.vertexai: 108 | response = await client.aio.models.update( 109 | model='models/2171259487439028224', 110 | config={ 111 | 'display_name': 'My tuned gemini-1.5', 112 | 'http_options': test_http_options, 113 | }, 114 | ) 115 | else: 116 | with pytest.raises(errors.ClientError) as e: 117 | await client.aio.models.update( 118 | model='models/2171259487439028224', 119 | config={ 120 | 'display_name': 'My tuned gemini-1.5', 121 | 'http_options': test_http_options, 122 | }, 123 | ) 124 | assert '404' in str(e) 125 | -------------------------------------------------------------------------------- /google/genai/tests/models/test_upscale_image.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for upscale_image.""" 18 | 19 | import os 20 | 21 | from pydantic import ValidationError 22 | import pytest 23 | 24 | from ... import types 25 | from .. import pytest_helper 26 | 27 | 28 | IMAGE_FILE_PATH = os.path.abspath( 29 | os.path.join(os.path.dirname(__file__), '../data/bridge1.png') 30 | ) 31 | 32 | test_table: list[pytest_helper.TestTableItem] = [ 33 | pytest_helper.TestTableItem( 34 | name='test_upscale_no_config', 35 | exception_if_mldev='only supported in the Vertex AI client', 36 | parameters=types.UpscaleImageParameters( 37 | model='imagen-3.0-generate-001', 38 | image=types.Image.from_file(location=IMAGE_FILE_PATH), 39 | upscale_factor='x2', 40 | ), 41 | ), 42 | pytest_helper.TestTableItem( 43 | name='test_upscale', 44 | exception_if_mldev='only supported in the Vertex AI client', 45 | parameters=types.UpscaleImageParameters( 46 | model='imagen-3.0-generate-001', 47 | image=types.Image.from_file(location=IMAGE_FILE_PATH), 48 | upscale_factor='x2', 49 | config={ 50 | 'include_rai_reason': True, 51 | 'output_mime_type': 'image/jpeg', 52 | 'output_compression_quality': 80, 53 | }, 54 | ), 55 | ), 56 | ] 57 | pytestmark = pytest_helper.setup( 58 | file=__file__, 59 | globals_for_file=globals(), 60 | test_method='models.upscale_image', 61 | test_table=test_table, 62 | ) 63 | 64 | 65 | def test_upscale_extra_config_parameters(client): 66 | # MLDev currently does not support upscale_image, but the ValidationError 67 | # occurs before the ValueError. 68 | try: 69 | # User is not allowed to set mode or number_of_images 70 | client.models.upscale_image( 71 | model='imagen-3.0-generate-001', 72 | image=types.Image.from_file(location=IMAGE_FILE_PATH), 73 | upscale_factor='x2', 74 | config={ 75 | 'mode': 'upscale', 76 | 'number_of_images': 1, 77 | } 78 | ) 79 | # Should never reach this. 80 | assert False 81 | except Exception as e: 82 | assert isinstance(e, ValidationError) 83 | assert 'Extra inputs are not permitted' in str(e) 84 | 85 | 86 | @pytest.mark.asyncio 87 | async def test_upscale_async(client): 88 | with pytest_helper.exception_if_mldev(client, ValueError): 89 | response = await client.aio.models.upscale_image( 90 | model='imagen-3.0-generate-001', 91 | image=types.Image.from_file(location=IMAGE_FILE_PATH), 92 | upscale_factor='x2', 93 | config={ 94 | 'include_rai_reason': True, 95 | 'output_mime_type': 'image/jpeg', 96 | 'output_compression_quality': 80, 97 | }, 98 | ) 99 | assert response.generated_images[0].image.image_bytes 100 | 101 | 102 | @pytest.mark.asyncio 103 | async def test_upscale_extra_config_parameters_async(client): 104 | # MLDev currently does not support upscale_image, but the ValidationError 105 | # occurs before the ValueError. 106 | try: 107 | # User is not allowed to set mode or number_of_images 108 | await client.aio.models.upscale_image( 109 | model='imagen-3.0-generate-001', 110 | image=types.Image.from_file(location=IMAGE_FILE_PATH), 111 | upscale_factor='x2', 112 | config={ 113 | 'mode': 'upscale', 114 | 'number_of_images': 1, 115 | }, 116 | ) 117 | # Should never reach this. 118 | assert False 119 | except Exception as e: 120 | assert isinstance(e, ValidationError) 121 | assert 'Extra inputs are not permitted' in str(e) 122 | -------------------------------------------------------------------------------- /google/genai/tests/public_samples/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Public Samples and Tests for the Google GenAI SDK.""" 18 | -------------------------------------------------------------------------------- /google/genai/tests/public_samples/test_gemini_text_only.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Test for the code sample for Gemini text-only request.""" 18 | 19 | from .. import pytest_helper 20 | 21 | pytestmark = pytest_helper.setup(file=__file__) 22 | 23 | 24 | def test_sample(client): 25 | # [START generativeaionvertexai_gemini_text_only] 26 | response = client.models.generate_content( 27 | model="gemini-1.5-flash-002", 28 | contents=( 29 | "What's a good name for a flower shop that specializes in selling" 30 | " bouquets of dried flowers?" 31 | ), 32 | ) 33 | print(response.text) 34 | # [END generativeaionvertexai_gemini_text_only] 35 | -------------------------------------------------------------------------------- /google/genai/tests/tokens/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for the Google GenAI SDK's _transformers module.""" -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_blobs.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests t_bytes methods in the _transformers module.""" 18 | 19 | import os 20 | 21 | import PIL.Image 22 | 23 | from ... import _transformers as t 24 | from ... import types 25 | 26 | IMAGE_FILE_PATH = os.path.abspath( 27 | os.path.join(os.path.dirname(__file__), '../data/google.jpg') 28 | ) 29 | image = PIL.Image.open(IMAGE_FILE_PATH) 30 | 31 | 32 | 33 | def test_blob_dict(): 34 | blob = t.t_blob(None, {'data': bytes([0, 0, 0, 0, 0, 0]), 'mime_type': 'audio/pcm'} 35 | ) 36 | assert blob.data == bytes([0, 0, 0, 0, 0, 0]) 37 | assert blob.mime_type == 'audio/pcm' 38 | 39 | 40 | def test_blob(): 41 | blob = t.t_blob(None, types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm') 42 | ) 43 | assert blob.data == bytes([0, 0, 0, 0, 0, 0]) 44 | assert blob.mime_type == 'audio/pcm' 45 | 46 | 47 | def test_image(): 48 | blob = t.t_blob(None, image) 49 | assert blob.data[6:10] == b'JFIF' 50 | assert blob.mime_type == 'image/jpeg' 51 | 52 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_bytes.py: -------------------------------------------------------------------------------- 1 | """Tests t_bytes methods in the _transformers module.""" 2 | 3 | import base64 4 | 5 | import pytest 6 | 7 | from ... import client as google_genai_client_module 8 | from ... import _transformers as t 9 | 10 | _RAW_BYTES = ( 11 | b'\xfb\xf6\x9bq\xd7\x9f\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2\xdb\xaf\xc3\x1c\xb3\x00\x10\x83\x10Q\x87' 12 | b' \x92\x8b0\xd3\x8fA\x14\x93QU\x97a\x9d5\xdb~9\xeb\xbf=' 13 | ) 14 | 15 | 16 | @pytest.fixture 17 | def client(use_vertex): 18 | if use_vertex: 19 | yield google_genai_client_module.Client( 20 | vertexai=use_vertex, project='test-project', location='test-location' 21 | ) 22 | else: 23 | yield google_genai_client_module.Client( 24 | vertexai=use_vertex, api_key='test-api-key' 25 | ) 26 | 27 | pytestmark = [pytest.mark.parametrize('use_vertex', [True, False])] 28 | 29 | 30 | @pytest.mark.usefixtures('client') 31 | def test_t_bytes(client): 32 | assert t.t_bytes(client._api_client, _RAW_BYTES) == base64.b64encode( 33 | _RAW_BYTES 34 | ).decode('ascii') 35 | assert t.t_bytes(client._api_client, 'string') == 'string' 36 | 37 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_function_responses.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for live.py.""" 18 | from ... import types 19 | from ... import _transformers as t 20 | 21 | def test_function_response_dict(): 22 | input = { 23 | 'name': 'get_current_weather', 24 | 'response': {'temperature': 14.5, 'unit': 'C'}, 25 | 'id': 'some-id', 26 | } 27 | 28 | function_responses = t.t_function_responses(input) 29 | 30 | assert len(function_responses) == 1 31 | assert function_responses[0].name == 'get_current_weather' 32 | assert function_responses[0].response['temperature'] == 14.5 33 | assert function_responses[0].response['unit'] == 'C' 34 | 35 | 36 | def test_send_function_response(): 37 | input = types.FunctionResponse( 38 | name='get_current_weather', 39 | response={'temperature': 14.5, 'unit': 'C'}, 40 | id='some-id', 41 | ) 42 | 43 | function_responses = t.t_function_responses(input) 44 | 45 | assert len(function_responses) == 1 46 | assert function_responses[0].name == 'get_current_weather' 47 | assert function_responses[0].response['temperature'] == 14.5 48 | assert function_responses[0].response['unit'] == 'C' 49 | 50 | 51 | def test_send_function_response_list(): 52 | 53 | input1 = { 54 | 'name': 'get_current_weather', 55 | 'response': {'temperature': 14.5, 'unit': 'C'}, 56 | 'id': '1', 57 | } 58 | input2 = { 59 | 'name': 'get_current_weather', 60 | 'response': {'temperature': 99.9, 'unit': 'C'}, 61 | 'id': '2', 62 | } 63 | 64 | function_responses = t.t_function_responses([input1, input2]) 65 | 66 | assert len(function_responses) == 2 67 | assert function_responses[0].name == 'get_current_weather' 68 | assert function_responses[0].response['temperature'] == 14.5 69 | assert function_responses[0].response['unit'] == 'C' 70 | assert function_responses[1].name == 'get_current_weather' 71 | assert function_responses[1].response['temperature'] == 99.9 72 | assert function_responses[1].response['unit'] == 'C' 73 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_t_content.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Tests for t_content.""" 17 | 18 | import pytest 19 | import pydantic 20 | 21 | from ... import _transformers as t 22 | from ... import types 23 | 24 | 25 | def test_none(): 26 | with pytest.raises(ValueError): 27 | t.t_content(None, None) 28 | 29 | 30 | def test_content(): 31 | assert t.t_content( 32 | None, types.Content(parts=[types.Part(text='test')]) 33 | ) == types.Content(parts=[types.Part(text='test')]) 34 | 35 | 36 | def test_content_dict(): 37 | assert t.t_content( 38 | None, {'role': 'user', 'parts': [{'text': 'test'}]} 39 | ) == types.Content(parts=[types.Part(text='test')], role='user') 40 | 41 | 42 | def test_content_dict_invalid(): 43 | with pytest.raises(pydantic.ValidationError): 44 | t.t_content(None, {'invalid_key': 'test'}) 45 | 46 | 47 | def test_text_part_dict(): 48 | assert t.t_content(None, {'text': 'test'}) == types.UserContent( 49 | parts=[types.Part(text='test')] 50 | ) 51 | 52 | 53 | def test_function_call_part_dict(): 54 | assert t.t_content( 55 | None, {'function_call': {'name': 'test_func', 'args': {'arg1': 'value1'}}} 56 | ) == ( 57 | types.ModelContent( 58 | parts=[ 59 | types.Part( 60 | function_call=types.FunctionCall( 61 | name='test_func', args={'arg1': 'value1'} 62 | ) 63 | ) 64 | ] 65 | ) 66 | ) 67 | 68 | 69 | def test_text_part(): 70 | assert t.t_content(None, types.Part(text='test')) == types.UserContent( 71 | parts=[types.Part(text='test')] 72 | ) 73 | 74 | 75 | def test_function_call_part(): 76 | assert t.t_content( 77 | None, 78 | types.Part( 79 | function_call=types.FunctionCall( 80 | name='test_func', args={'arg1': 'value1'} 81 | ) 82 | ), 83 | ) == ( 84 | types.ModelContent( 85 | parts=[ 86 | types.Part( 87 | function_call=types.FunctionCall( 88 | name='test_func', args={'arg1': 'value1'} 89 | ) 90 | ) 91 | ] 92 | ) 93 | ) 94 | 95 | 96 | def test_string(): 97 | assert t.t_content(None, 'test') == types.UserContent( 98 | parts=[types.Part(text='test')] 99 | ) 100 | 101 | 102 | def test_file(): 103 | assert t.t_content( 104 | None, types.File(uri='gs://test', mime_type='image/png') 105 | ) == types.UserContent( 106 | parts=[ 107 | types.Part( 108 | file_data=types.FileData( 109 | file_uri='gs://test', mime_type='image/png' 110 | ) 111 | ) 112 | ] 113 | ) 114 | 115 | 116 | def test_file_no_uri(): 117 | with pytest.raises(ValueError): 118 | t.t_content(None, types.File(mime_type='image/png')) 119 | 120 | 121 | def test_file_no_mime_type(): 122 | with pytest.raises(ValueError): 123 | t.t_content(None, types.File(uri='gs://test')) 124 | 125 | 126 | def test_int(): 127 | try: 128 | t.t_content(None, 1) 129 | except ValueError as e: 130 | assert 'Unsupported content part type: ' in str(e) 131 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_t_part.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Tests for t_part.""" 17 | 18 | import pytest 19 | import pydantic 20 | 21 | from ... import _transformers as t 22 | from ... import types 23 | 24 | 25 | def test_none(): 26 | with pytest.raises(ValueError): 27 | t.t_part(None) 28 | 29 | 30 | def test_empty_string(): 31 | assert t.t_part('') == types.Part(text='') 32 | 33 | 34 | def test_string(): 35 | assert t.t_part('test') == types.Part(text='test') 36 | 37 | 38 | def test_file(): 39 | assert t.t_part( 40 | types.File(uri='gs://test', mime_type='image/png') 41 | ) == types.Part( 42 | file_data=types.FileData(file_uri='gs://test', mime_type='image/png') 43 | ) 44 | 45 | 46 | def test_file_no_uri(): 47 | with pytest.raises(ValueError): 48 | t.t_part(types.File(mime_type='image/png')) 49 | 50 | 51 | def test_file_no_mime_type(): 52 | with pytest.raises(ValueError): 53 | t.t_part(types.File(uri='gs://test')) 54 | 55 | 56 | def test_empty_dict(): 57 | assert t.t_part({}) == types.Part() 58 | 59 | 60 | def test_dict(): 61 | assert t.t_part({'text': 'test'}) == types.Part(text='test') 62 | 63 | 64 | def test_invalid_dict(): 65 | with pytest.raises(pydantic.ValidationError): 66 | t.t_part({'invalid_key': 'test'}) 67 | 68 | 69 | def test_part(): 70 | assert t.t_part(types.Part(text='test')) == types.Part(text='test') 71 | 72 | 73 | def test_int(): 74 | try: 75 | t.t_part(1) 76 | except ValueError as e: 77 | assert 'Unsupported content part type: ' in str(e) 78 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_t_parts.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Tests for t_parts.""" 17 | 18 | import pytest 19 | import pydantic 20 | 21 | from ... import _transformers as t 22 | from ... import types 23 | 24 | 25 | def test_none(): 26 | with pytest.raises(ValueError): 27 | t.t_parts(None) 28 | 29 | 30 | def test_empty_list(): 31 | with pytest.raises(ValueError): 32 | t.t_parts([]) 33 | 34 | 35 | def test_list(): 36 | assert t.t_parts(['test1', 'test2']) == [ 37 | types.Part(text='test1'), 38 | types.Part(text='test2'), 39 | ] 40 | 41 | 42 | def test_empty_dict(): 43 | assert t.t_parts({}) == [types.Part()] 44 | 45 | 46 | def test_dict(): 47 | assert t.t_parts({'text': 'test'}) == [types.Part(text='test')] 48 | 49 | 50 | def test_invalid_dict(): 51 | with pytest.raises(pydantic.ValidationError): 52 | t.t_parts({'invalid_key': 'test'}) 53 | 54 | 55 | def test_string(): 56 | assert t.t_parts('test') == [types.Part(text='test')] 57 | 58 | 59 | def test_file(): 60 | assert t.t_parts( 61 | types.File(uri='gs://test', mime_type='image/png') 62 | ) == [ 63 | types.Part( 64 | file_data=types.FileData(file_uri='gs://test', mime_type='image/png') 65 | ) 66 | ] 67 | 68 | 69 | def test_file_no_uri(): 70 | with pytest.raises(ValueError): 71 | t.t_parts(types.File(mime_type='image/png')) 72 | 73 | 74 | def test_file_no_mime_type(): 75 | with pytest.raises(ValueError): 76 | t.t_parts(types.File(uri='gs://test')) 77 | 78 | 79 | def test_part(): 80 | assert t.t_parts(types.Part(text='test')) == [types.Part(text='test')] 81 | 82 | 83 | def test_int(): 84 | try: 85 | t.t_parts(1) 86 | except ValueError as e: 87 | assert 'Unsupported content part type: ' in str(e) 88 | -------------------------------------------------------------------------------- /google/genai/tests/transformers/test_t_tool.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | """Tests for t_tool.""" 17 | 18 | import typing 19 | from typing import Any 20 | import pytest 21 | from ... import _transformers as t 22 | from ... import client as google_genai_client_module 23 | from ... import types 24 | 25 | _is_mcp_imported = False 26 | if typing.TYPE_CHECKING: 27 | from mcp import types as mcp_types 28 | 29 | _is_mcp_imported = True 30 | else: 31 | try: 32 | from mcp import types as mcp_types 33 | 34 | _is_mcp_imported = True 35 | except ImportError: 36 | _is_mcp_imported = False 37 | 38 | 39 | @pytest.fixture 40 | def client(use_vertex): 41 | if use_vertex: 42 | yield google_genai_client_module.Client( 43 | vertexai=use_vertex, project='test-project', location='test-location' 44 | ) 45 | else: 46 | yield google_genai_client_module.Client( 47 | vertexai=use_vertex, api_key='test-api-key' 48 | ) 49 | 50 | 51 | @pytest.mark.usefixtures('client') 52 | def test_none(client): 53 | assert t.t_tool(client, None) == None 54 | 55 | 56 | @pytest.mark.usefixtures('client') 57 | def test_function(client): 58 | def test_func(arg1: str, arg2: int): 59 | pass 60 | 61 | assert t.t_tool(client, test_func) == types.Tool( 62 | function_declarations=[ 63 | types.FunctionDeclaration( 64 | name='test_func', 65 | parameters=types.Schema( 66 | type='OBJECT', 67 | properties={ 68 | 'arg1': types.Schema(type='STRING'), 69 | 'arg2': types.Schema(type='INTEGER'), 70 | }, 71 | required=['arg1', 'arg2'], 72 | ), 73 | ) 74 | ] 75 | ) 76 | 77 | 78 | @pytest.mark.usefixtures('client') 79 | def test_dictionary(client): 80 | assert t.t_tool( 81 | client, 82 | { 83 | 'function_declarations': [{ 84 | 'name': 'tool', 85 | 'description': 'tool-description', 86 | 'parameters': {'type': 'OBJECT', 'properties': {}}, 87 | }] 88 | }, 89 | ) == types.Tool( 90 | function_declarations=[ 91 | types.FunctionDeclaration( 92 | name='tool', 93 | description='tool-description', 94 | parameters=types.Schema( 95 | type='OBJECT', 96 | properties={}, 97 | ), 98 | ) 99 | ] 100 | ) 101 | 102 | 103 | @pytest.mark.usefixtures('client') 104 | def test_mcp_tool(client): 105 | if not _is_mcp_imported: 106 | return 107 | 108 | mcp_tool = mcp_types.Tool( 109 | name='tool', 110 | description='tool-description', 111 | inputSchema={ 112 | 'type': 'object', 113 | 'properties': { 114 | 'key1': { 115 | 'type': 'string', 116 | }, 117 | 'key2': { 118 | 'type': 'number', 119 | }, 120 | }, 121 | }, 122 | ) 123 | assert t.t_tool(client, mcp_tool) == types.Tool( 124 | function_declarations=[ 125 | types.FunctionDeclaration( 126 | name='tool', 127 | description='tool-description', 128 | parameters=types.Schema( 129 | type='OBJECT', 130 | properties={ 131 | 'key1': types.Schema(type='STRING'), 132 | 'key2': types.Schema(type='NUMBER'), 133 | }, 134 | ), 135 | ) 136 | ] 137 | ) 138 | 139 | 140 | @pytest.mark.usefixtures('client') 141 | def test_tool(client): 142 | tool = types.Tool( 143 | function_declarations=[ 144 | types.FunctionDeclaration( 145 | name='tool', 146 | description='tool-description', 147 | parameters=types.Schema( 148 | type='OBJECT', 149 | properties={ 150 | 'key1': types.Schema(type='STRING'), 151 | 'key2': types.Schema(type='NUMBER'), 152 | }, 153 | ), 154 | ) 155 | ] 156 | ) 157 | assert t.t_tool(client, tool) == tool 158 | -------------------------------------------------------------------------------- /google/genai/tests/tunings/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | -------------------------------------------------------------------------------- /google/genai/tests/tunings/test_end_to_end.py: -------------------------------------------------------------------------------- 1 | """Tests for create_sft_job.""" 2 | 3 | from ... import types as genai_types 4 | from .. import pytest_helper 5 | from ... import _replay_api_client 6 | 7 | 8 | test_table: list[pytest_helper.TestTableItem] = [] 9 | 10 | pytestmark = pytest_helper.setup( 11 | file=__file__, 12 | globals_for_file=globals(), 13 | test_method="tunings.tune", 14 | test_table=test_table, 15 | ) 16 | 17 | pytest_plugins = ("pytest_asyncio",) 18 | 19 | 20 | def test_tune_until_success(client): 21 | import time 22 | 23 | if client._api_client.vertexai: 24 | job = client.tunings.tune( 25 | base_model="gemini-2.0-flash-001", 26 | training_dataset=genai_types.TuningDataset( 27 | gcs_uri="gs://cloud-samples-data/ai-platform/generative_ai/gemini-2_0/text/sft_train_data.jsonl", 28 | ), 29 | ) 30 | else: 31 | job = client.tunings.tune( 32 | base_model="models/gemini-1.0-pro-001", 33 | training_dataset=genai_types.TuningDataset( 34 | examples=[ 35 | genai_types.TuningExample( 36 | text_input=f"Input text {i}", 37 | output=f"Output text {i}", 38 | ) 39 | for i in range(5) 40 | ], 41 | ), 42 | # Required for MLDev: 43 | # "Either tuned_model_id or display_name must be set." 44 | config=genai_types.CreateTuningJobConfig( 45 | tuned_model_display_name="test_dataset_examples model", 46 | ), 47 | ) 48 | 49 | while not job.has_ended: 50 | # Skipping the sleep for when in replay mode. 51 | if client._api_client._mode not in ("replay", "auto"): 52 | time.sleep(60) 53 | job = client.tunings.get(name=job.name) 54 | 55 | assert job.has_ended 56 | assert job.has_succeeded 57 | -------------------------------------------------------------------------------- /google/genai/tests/tunings/test_get.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from ... import types as genai_types 18 | from .. import pytest_helper 19 | 20 | test_table: list[pytest_helper.TestTableItem] = [ 21 | pytest_helper.TestTableItem( 22 | name="test_vertexai", 23 | parameters=genai_types._GetTuningJobParameters( 24 | name="projects/801452371447/locations/us-central1/tuningJobs/4303478340632707072" 25 | ), 26 | exception_if_mldev="Not Found", 27 | ), 28 | pytest_helper.TestTableItem( 29 | name="test_mldev", 30 | parameters=genai_types._GetTuningJobParameters( 31 | name="tunedModels/generate-num-1896" 32 | ), 33 | exception_if_vertex="Not Found", 34 | ), 35 | ] 36 | 37 | pytestmark = pytest_helper.setup( 38 | file=__file__, 39 | globals_for_file=globals(), 40 | test_method="tunings.get", 41 | test_table=test_table, 42 | ) 43 | 44 | pytest_plugins = ("pytest_asyncio",) 45 | 46 | 47 | def test_helper_properties(client): 48 | if client._api_client.vertexai: 49 | job = client.tunings.get( 50 | name="projects/801452371447/locations/us-central1/tuningJobs/4303478340632707072", 51 | ) 52 | else: 53 | job = client.tunings.get( 54 | name="tunedModels/testdatasetexamples-model-j0fpgpaksvri" 55 | ) 56 | 57 | assert job.has_ended 58 | assert job.has_succeeded 59 | -------------------------------------------------------------------------------- /google/genai/tests/tunings/test_list.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | """Tests for tunings.list().""" 18 | 19 | import pytest 20 | 21 | from ... import types as genai_types 22 | from .. import pytest_helper 23 | 24 | 25 | test_table: list[pytest_helper.TestTableItem] = [ 26 | pytest_helper.TestTableItem( 27 | name='test_default', 28 | parameters=genai_types._ListTuningJobsParameters(), 29 | ), 30 | pytest_helper.TestTableItem( 31 | name='test_with_config', 32 | parameters=genai_types._ListTuningJobsParameters( 33 | config=genai_types.ListTuningJobsConfig(page_size=2) 34 | ), 35 | ), 36 | ] 37 | 38 | pytestmark = pytest_helper.setup( 39 | file=__file__, 40 | globals_for_file=globals(), 41 | test_method='tunings.list', 42 | test_table=test_table, 43 | ) 44 | 45 | pytest_plugins = ('pytest_asyncio',) 46 | 47 | 48 | def test_pager(client): 49 | tuning_jobs = client.tunings.list(config={'page_size': 2}) 50 | 51 | assert tuning_jobs.name == 'tuning_jobs' 52 | assert tuning_jobs.page_size == 2 53 | assert len(tuning_jobs) <= 2 54 | 55 | # Iterate through all the pages. Then next_page() should raise an exception. 56 | for _ in tuning_jobs: 57 | pass 58 | with pytest.raises(IndexError, match='No more pages to fetch.'): 59 | tuning_jobs.next_page() 60 | 61 | 62 | @pytest.mark.asyncio 63 | async def test_async_pager(client): 64 | tuning_jobs = await client.aio.tunings.list(config={'page_size': 2}) 65 | 66 | assert tuning_jobs.name == 'tuning_jobs' 67 | assert tuning_jobs.page_size == 2 68 | assert len(tuning_jobs) <= 2 69 | 70 | # Iterate through all the pages. Then next_page() should raise an exception. 71 | async for _ in tuning_jobs: 72 | pass 73 | with pytest.raises(IndexError, match='No more pages to fetch.'): 74 | await tuning_jobs.next_page() 75 | -------------------------------------------------------------------------------- /google/genai/tests/types/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | -------------------------------------------------------------------------------- /google/genai/tests/types/test_future.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | # !Please DO NOT combine the test in this file with other tests. This file is 17 | # used to test the future annotation, which is the first line of import. 18 | from __future__ import annotations 19 | 20 | import copy 21 | import pydantic 22 | 23 | from ... import types 24 | 25 | 26 | class ComplexType(pydantic.BaseModel): 27 | param_x: str 28 | param_y: int 29 | 30 | 31 | class FakeClient: 32 | 33 | def __init__(self, vertexai=False) -> None: 34 | self.vertexai = vertexai 35 | 36 | 37 | mldev_client = FakeClient() 38 | vertex_client = FakeClient(vertexai=True) 39 | 40 | 41 | def test_future_annotation_simple_type(): 42 | def func_under_test(param_1: str, param_2: int) -> str: 43 | return '123' 44 | 45 | expected_schema_mldev = types.FunctionDeclaration( 46 | name='func_under_test', 47 | parameters=types.Schema( 48 | type='OBJECT', 49 | properties={ 50 | 'param_1': types.Schema(type='STRING'), 51 | 'param_2': types.Schema(type='INTEGER'), 52 | }, 53 | required=['param_1', 'param_2'], 54 | ), 55 | ) 56 | expected_schema_vertex = copy.deepcopy(expected_schema_mldev) 57 | expected_schema_vertex.response = types.Schema(type='STRING') 58 | actual_schema_mldev = types.FunctionDeclaration.from_callable( 59 | client=mldev_client, callable=func_under_test 60 | ) 61 | actual_schema_vertex = types.FunctionDeclaration.from_callable( 62 | client=vertex_client, callable=func_under_test 63 | ) 64 | assert actual_schema_mldev == expected_schema_mldev 65 | assert actual_schema_vertex == expected_schema_vertex 66 | 67 | 68 | def test_future_annotation_complex_type(): 69 | def func_under_test(param_1: ComplexType, param_2: int) -> str: 70 | return '123' 71 | 72 | expected_schema_mldev = types.FunctionDeclaration( 73 | name='func_under_test', 74 | parameters=types.Schema( 75 | type='OBJECT', 76 | properties={ 77 | 'param_1': types.Schema( 78 | type='OBJECT', 79 | properties={ 80 | 'param_x': types.Schema(type='STRING'), 81 | 'param_y': types.Schema(type='INTEGER'), 82 | }, 83 | required=['param_x', 'param_y'], 84 | ), 85 | 'param_2': types.Schema(type='INTEGER'), 86 | }, 87 | required=['param_1', 'param_2'], 88 | ) 89 | ) 90 | expected_schema_vertex = copy.deepcopy(expected_schema_mldev) 91 | expected_schema_vertex.response = types.Schema(type='STRING') 92 | 93 | actual_schema_mldev = types.FunctionDeclaration.from_callable( 94 | client=mldev_client, callable=func_under_test 95 | ) 96 | actual_schema_vertex = types.FunctionDeclaration.from_callable( 97 | client=vertex_client, callable=func_under_test 98 | ) 99 | 100 | assert actual_schema_mldev == expected_schema_mldev 101 | assert actual_schema_vertex == expected_schema_vertex 102 | -------------------------------------------------------------------------------- /google/genai/tests/types/test_optional_types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | 17 | from unittest.mock import patch 18 | 19 | 20 | @patch.dict('sys.modules', {'PIL': None, 'PIL.Image': None}) 21 | def test_without_pil_installed_mocked(): 22 | from ... import types 23 | 24 | type_names_in_union = [ 25 | arg.__name__ if hasattr(arg, '__name__') else str(arg) 26 | for arg in types.PartUnion.__args__] 27 | assert 'Image' not in type_names_in_union 28 | 29 | def test_without_pil_installed_mocked(): 30 | from ... import types 31 | 32 | type_names_in_union = [ 33 | arg.__name__ if hasattr(arg, '__name__') else str(arg) 34 | for arg in types.PartUnion.__args__] 35 | 36 | assert 'Image' in type_names_in_union -------------------------------------------------------------------------------- /google/genai/version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | __version__ = '1.19.0' # x-release-please-version 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "twine>=6.1.0", "packaging>=24.2", "pkginfo>=1.12.0"] 3 | 4 | [project] 5 | name = "google-genai" 6 | version = "1.19.0" 7 | description = "GenAI Python SDK" 8 | readme = "README.md" 9 | license = {text = "Apache-2.0"} 10 | requires-python = ">=3.9" 11 | authors = [ 12 | { name = "Google LLC", email = "googleapis-packages@google.com" }, 13 | ] 14 | classifiers = [ 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: Apache Software License", 17 | "Operating System :: OS Independent", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: 3.13", 25 | "Topic :: Internet", 26 | "Topic :: Software Development :: Libraries :: Python Modules", 27 | ] 28 | dependencies = [ 29 | "anyio>=4.8.0, <5.0.0", 30 | "google-auth>=2.14.1, <3.0.0", 31 | "httpx>=0.28.1, <1.0.0", 32 | "pydantic>=2.0.0, <3.0.0", 33 | "requests>=2.28.1, <3.0.0", 34 | "websockets>=13.0.0, <15.1.0", 35 | "typing-extensions>=4.11.0, <5.0.0", 36 | ] 37 | 38 | [project.urls] 39 | Homepage = "https://github.com/googleapis/python-genai" 40 | 41 | [tool.setuptools] 42 | packages = [ 43 | "google", 44 | "google.genai", 45 | ] 46 | include-package-data = true 47 | 48 | [tools.setuptools.package_data] 49 | "google.genai" = ["py.typed"] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==2.1.0 2 | annotated-types==0.7.0 3 | anyio==4.8.0 4 | cachetools==5.5.0 5 | certifi==2024.8.30 6 | charset-normalizer==3.4.0 7 | coverage==7.6.9 8 | httpx==0.28.1 9 | google-auth==2.37.0 10 | idna==3.10 11 | iniconfig==2.0.0 12 | packaging==24.2 13 | pillow==11.0.0 14 | pluggy==1.5.0 15 | pyasn1==0.6.1 16 | pyasn1_modules==0.4.1 17 | pydantic==2.9.2 18 | pydantic_core==2.23.4 19 | pytest==8.3.4 20 | pytest-asyncio==0.25.0 21 | pytest-cov==6.0.0 22 | requests==2.32.3 23 | rsa==4.9 24 | typing_extensions==4.12.2 25 | urllib3==2.2.3 26 | websockets==15.0.0 27 | mcp==1.8.1; python_version > '3.9' 28 | --------------------------------------------------------------------------------