Skip to content

fix(model_garden): add deepseek example #13444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


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

# Default TEST_CONFIG_OVERRIDE for python repos.

# You can copy this file into your directory, then it will be imported from
# the noxfile.py.

# The source of truth:
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py

TEST_CONFIG_OVERRIDE = {
# You can opt out from the test for specific Python versions.
"ignored_versions": ["2.7", "3.7", "3.8", "3.10", "3.11", "3.12"],
# Old samples are opted out of enforcing Python type hints
# All new samples should feature them
"enforce_type_hints": True,
# An envvar key for determining the project id to use. Change it
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
# build specific Cloud project. You can also use your own string
# to use your own Cloud project.
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
# 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',
# If you need to use a specific version of pip,
# change pip_version_override to the string representation
# of the version number, for example, "20.2.4"
"pip_version_override": None,
# A dictionary you want to inject into your test. Don't put any
# secrets here. These values will override predefined values.
"envs": {},
}
2 changes: 2 additions & 0 deletions model_garden/deepseek/requirements-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
google-api-core==2.24.0
pytest==8.2.0
1 change: 1 addition & 0 deletions model_garden/deepseek/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests==2.32.4
21 changes: 21 additions & 0 deletions model_garden/deepseek/test_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import textgen_with_txt


def test_textgen_with_txt() -> None:
"""Test the example in the README."""
content = textgen_with_txt.generate_content()
assert content
91 changes: 91 additions & 0 deletions model_garden/deepseek/textgen_with_txt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


def get_bearer_token() -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The function get_bearer_token is type-hinted to return str, but it actually returns a tuple (project_name, bearer_token). This mismatch can lead to type errors and incorrect assumptions by callers of this function.

Suggested change
def get_bearer_token() -> str:
def get_bearer_token() -> tuple[str, str]:

"""To get project name and bearer token for Google Cloud ADC authentication."""
import google.auth
from google.auth.transport.requests import Request
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Imports google.auth and google.auth.transport.requests are defined locally within the get_bearer_token function. According to PEP 81, imports should generally be at the top of the file (after module docstrings and comments, before globals and constants) for better readability, maintainability, and discoverability of dependencies. Please move these imports to the module level.

Style Guide References

Suggested change
import google.auth
from google.auth.transport.requests import Request
# These imports should be moved to the top of the file:
# import google.auth
# from google.auth.transport.requests import Request

Footnotes

  1. PEP 8: Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants. This improves clarity and makes it easier to see what modules a script requires.


creds, project_name = google.auth.default(
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
auth_req = Request()
creds.refresh(auth_req)
bearer_token = creds.token
return project_name, bearer_token


def generate_content() -> str:
"""To generate content from DeepSeek model hosted in Vertex AI Model Garden."""
import requests
import json
Comment on lines +32 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Imports requests and json are defined locally within the generate_content function. Similar to the previous comment, PEP 81 recommends placing imports at the top of the file. Please move these to the module level.

Style Guide References

Suggested change
import requests
import json
# These imports should be moved to the top of the file:
# import requests
# import json

Footnotes

  1. PEP 8: Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants. This improves clarity and makes it easier to see what modules a script requires.


project_name, bearer_token = get_bearer_token()

# Read more about the model here:
# https://cloud.google.com/vertex-ai/generative-ai/docs/maas/deepseek#streaming
location = "us-central1"
url = f"https://us-central1-aiplatform.googleapis.com/v1/projects/{project_name}/locations/{location}/endpoints/openapi/chat/completions"

# Set the request header
headers = {
"Authorization": f"Bearer {bearer_token}",
"Content-Type": "application/json",
}
data = {
"model": "deepseek-ai/deepseek-r1-0528-maas",
# "stream": False, # As per the bash script
"messages": [{"role": "user", "content": "Why is the sky blue?"}],
}
# Send the request
response = requests.post(url, headers=headers, data=json.dumps(data))
print(f"Request Response: {response.status_code}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The HTTP response status is printed, but errors (like 4xx or 5xx client/server errors) are not explicitly handled before attempting to process the response. If the API call fails, json.loads(response.content) could raise an exception on unexpected content, or the script might proceed with invalid data. It's robust practice to check for HTTP errors immediately after the request.

Consider using response.raise_for_status(), which will raise an requests.exceptions.HTTPError if the HTTP request returned an unsuccessful status code.

Suggested change
print(f"Request Response: {response.status_code}")
response.raise_for_status() # Will raise an HTTPError for bad responses (4XX or 5XX)
print(f"Request Response: {response.status_code}")

# Example response:
# Request Response: 200

# Load the response data
model_response = json.loads(response.content)
print(model_response["model"])
# Example response:
# 'deepseek-ai/deepseek-r1-0528-maas'
print(model_response["usage"])
# Example response:
# {
# 'completion_tokens': 3294,
# 'prompt_tokens': 10,
# 'total_tokens': 3304
# }

# Print user input & Model response
for each in data["messages"]:
print(f"{each['role']}>>> {each['content']}")
for each in model_response["choices"]:
print(f"{each['message']['role']}>>> {each['message']['content']}")
# Example response:
# user>>> Why is the sky blue?
# Example response:
# assistant>>> <think>
# Okay, the user is asking why the sky is blue.
# ...
# </think>

# The sky appears blue due to a phenomenon called **Rayleigh scattering**,
# which occurs when sunlight passes through Earth's atmosphere and
# ...
return response.content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The function generate_content is type-hinted to return str and its name suggests it returns the generated textual content from the model. However, it currently returns response.content, which is of type bytes (the raw response body).

To align with the type hint and function's intent, it should parse the JSON response and return the actual generated text string, typically found within the choices part of the response. Accessing nested dictionary keys should also be done safely (e.g., using .get()).

Suggested change
return response.content
# Extract and return the assistant's message if available
if model_response.get("choices") and len(model_response["choices"]) > 0:
message = model_response["choices"][0].get("message", {})
content = message.get("content")
if content is not None:
return content
return "" # Return empty string or raise an error if content not found/as appropriate



if __name__ == "__main__":
generate_content()