Hello! Welcome back to our project.
In the last lesson, we built the core of our backend: a Flask endpoint that takes user text, creates a detailed prompt, and successfully calls the Gemini API to generate a summary. We used response.text to quickly grab the result, which was a fantastic first step.
Today, we will dig deeper into that response object. Our goal is to parse the JSON response from the Gemini API to extract the generated text in a more structured and reliable way. While response.text works, it's a shortcut. Understanding the full response structure will make our application more robust and allow us to handle different scenarios, like API errors or safety blocks, much more effectively.
From Simple Text to a Structured Object
When you call the Gemini API, it doesn't just send back a plain string of text. It returns a structured data object. The Python library you're using (google-genai) conveniently wraps this object, giving you helpful shortcuts like .text. However, to build a reliable application, we need to look under the hood at the full structure.
Think of it like getting a package delivered. response.text is like a label on the box that tells you the main content. But inside the box, there's the item itself, plus packaging, an invoice, and maybe a return slip. To handle everything correctly, you need to open the box and see all the contents.
Why response.text Isn't Always Enough
The response.text attribute is great for simple, successful responses. But what happens if the API call doesn't go as planned?
- Safety Blocking: What if the user's input or the model's output triggers Google's safety filters? The response won't contain any text, and simply calling
response.textmight cause an error in your program. The full response object, however, contains information about why the content was blocked. - Multiple Candidates: The API can sometimes generate several alternative responses, called "candidates."
response.textusually just gives you the first one, but the full response gives you access to all of them, if needed. - More Complex Output: Later, you might want the AI to return more than just a paragraph, like a JSON object containing a summary, a list of keywords, and a title. In that case,
.textwill give you a raw string of JSON, which you'll need to parse anyway.
Inspecting the Full Response
Let's see what the full response object actually looks like. A great way to do this is to convert it into a standard Python dictionary. The Stack Overflow resource below shows a user asking this exact question.
python - Google Gemini API response
This Stack Overflow answer is perfect for our task. It shows how to get the full API response as a dictionary and how to navigate it to find the text.
Read the second answer on the page (the one starting with 'If you are after semi-structured responses...'). Pay close attention to two key things: The method response.to_dict() which converts the response object to a dictionary. The example path to access the text: response["candidates"][0]["content"]["parts"][0]["text"].
As the resource explains, you can use the .to_dict() method to see the full structure. Let's modify our summarize_endpoint from the last lesson. For now, just print the dictionary to see what's inside.
# In your app.py, inside the summarize_endpoint function...
try:
# 3. Call the Gemini API
model = genai.GenerativeModel('gemini-pro')
response = model.generate_content(full_prompt)
# NEW: Let's inspect the full response
response_dict = response.to_dict()
print(response_dict) # This will print the full structure to your terminal
# ... we will replace response.text later ...
summary = response.text
return jsonify({'summary': summary})
except Exception as e:
# ...
If you run your Flask app and send a request, you will see something like this printed in your terminal:
{
"candidates": [
{
"content": {
"parts": [
{
"text": "The main points of the article are..."
}
],
"role": "model"
},
"finish_reason": "STOP",
"index": 0,
"safety_ratings": [
// ... safety information ...
]
}
]
}
Navigating the Response Dictionary
Now that we see the structure, we can write code to navigate it. It's a nested dictionary and list, which you're already familiar with from Module 1.

Here is the path to the text:
response_dict['candidates']: The main response contains acandidateskey. Its value is a list of all the potential responses the model generated. In most cases, this list will only have one item....[0]: We access the first item in thecandidateslist....['content']: This dictionary contains the actual content generated by the model....['parts']: The content can be split into multipleparts(e.g., text and images in a multimodal response). For us, it's a list with just one item....[0]: We access the first item in thepartslist....['text']: Finally, we get thetextkey, which holds the generated summary we are looking for.
Putting it all together, the code to extract the text is:
summary = response_dict['candidates'][0]['content']['parts'][0]['text']
This looks more complex than response.text, but it's much safer because you are explicitly accessing the data path. If any of these keys or indices don't exist, your program will raise a KeyError or IndexError, which we can catch and handle gracefully.
Updating Our Flask Endpoint
Let's update our app.py to use this robust method. We will wrap the parsing logic in its own try...except block to catch potential issues if the API returns an unexpected structure (for example, if content is blocked for safety reasons).
# In your app.py, in the /api/summarize endpoint
try:
# 3. Call the Gemini API
model = genai.GenerativeModel('gemini-pro')
response = model.generate_content(full_prompt)
# 4. Parse the response to get the summary text
try:
summary = response.candidates[0].content.parts[0].text
except (KeyError, IndexError, AttributeError):
# This handles cases where the response structure is not as expected.
# It could be that the prompt was blocked.
print(f"Error parsing response. Prompt feedback: {response.prompt_feedback}")
return jsonify({"error": "Failed to generate summary. The content may have been blocked."}), 500
return jsonify({'summary': summary})
except Exception as e:
# Handle potential errors from the API call itself
print(f"An error occurred during API call: {e}")
return jsonify({"error": "Failed to generate summary due to an internal API error."}), 500
Note: In the code above, we are directly accessing the attributes of the response object (e.g., response.candidates) instead of converting to a dictionary first. The google-genai library makes this possible, and it has the same effect as navigating the dictionary. It's slightly cleaner to read.
Test your understanding!
Imagine the API returned a response where the parts list was empty: "parts": []. What would happen in our new code block when it tries to access parts[0]?
Show answer
It would raise an IndexError because there is no item at index 0 in an empty list. Our except (KeyError, IndexError, AttributeError): block would catch this error, print a message to our server's log, and return a user-friendly JSON error to the frontend. This is exactly why this more robust approach is better!
An Even More Powerful Technique: Requesting JSON
So far, we've parsed the API's response structure. But what if we want the AI's output itself to be structured? For example, you might want a summary, a list of keywords, and a suggested title, all neatly organized. You can instruct the model to return its answer formatted as JSON.
This video gives a great overview of how to achieve this.
The video 'Getting JSON out of Gemini' by Sam Witteveen demonstrates a powerful feature: forcing the model's output to be in JSON format.
Watch the clip from 01:21 to 02:11. Notice the generation_config parameter. By setting response_mime_type to application/json, you are telling the Gemini model that its output must be a valid JSON string.
To do this, you configure the model like so:
import json
# Configure the model to expect JSON output
json_config = genai.GenerationConfig(response_mime_type="application/json")
model = genai.GenerativeModel('gemini-pro', generation_config=json_config)
# Update your prompt to ask for JSON
json_prompt = f"""
Act as an expert editor. Analyze the following text.
Return a JSON object with two keys: 'summary' and 'keywords'.
The 'summary' should be a 3-4 sentence summary.
The 'keywords' should be a list of 5 relevant keywords.
Text:
---
{user_text}
---
"""
response = model.generate_content(json_prompt)
# The response.text will now be a JSON STRING
# We need to parse it into a Python dictionary
ai_output = json.loads(response.text)
# Now you can access the parts
summary = ai_output['summary']
keywords = ai_output['keywords']
print("Summary:", summary)
print("Keywords:", keywords)
Here, we do another kind of parsing: using json.loads() to convert the text string from the AI into a Python dictionary. This is an incredibly useful technique for getting structured data back from the model for your application.
Conclusion
Great work today! You've moved beyond the basic response.text and learned how to properly parse the complex responses from the Gemini API. This is a critical skill for building a dependable and feature-rich AI application.
Key Takeaways:
- The Gemini API returns a structured object, not just plain text.
- Using
response.candidates[0].content.parts[0].textis a robust way to extract the generated text, as it allows you to handle unexpected response structures. - Wrapping your parsing logic in a
try...exceptblock is essential for catching errors and preventing your app from crashing. - You can configure the Gemini model to return its output as a JSON string by setting
response_mime_type="application/json". - To use this JSON string in Python, you must parse it into a dictionary using
json.loads().
In our next lesson, we will focus more deeply on implementing basic error handling for common API issues like invalid keys or rate limits. The parsing skills you learned today are a prerequisite, as inspecting the full API response is often necessary to understand what kind of error occurred.