{
  "source_file": "0930",
  "source_platform": "Hashnode",
  "author": "Juan Carlos Olamendy",
  "title_listed": "Building Tools for Google ADK Agents: Complete Tutorial",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://juancolamendy.hashnode.dev/building-tools-for-google-adk-agents-complete-tutorial",
  "publish_date": "2025-09-30",
  "corpus_stage": "",
  "notes": "",
  "id": "4d0df569f3db",
  "collected_at": "2026-10-02T14:10:49",
  "title": "Building Tools for Google ADK Agents: Complete Tutorial",
  "body_markdown": "Building Tools for Google ADK Agents: Complete Tutorial\nPublished\nSeptember 30, 2025\n•\n9\nmin read\n•\nView as Markdown\nJ\nJuan Carlos Olamendy\nSenior AI Engineer | Building & Telling Stories about AI/ML Systems | Software Engineer\nHave you ever watched your AI agent struggle — locked in endless loops of “I don’t know how to help” while users grow impatient and annoyed?\nFor many developers, building intelligent agents becomes an exercise in frustration, wasted hours, and groaning at error logs when rudimentary bots fail at even simple tasks.\nEvery moment lost is burning cash, chewing up developer time, and driving up support tickets as agents fall short.\nThe real problem isn’t just technical; it’s a war of efficiency, cost, and sheer exhaustion as bots hit their limits and resources disappear into debugging rabbit holes.\nThis guide exists to break that cycle.\nBy mastering how to build powerful, purposeful tools for Google’s Agent Development Kit (ADK), you unlock faster development, lower support costs, and bots that can actually solve real problems.\nRead on to discover step-by-step patterns, code, and design advice that will transform your agents from frustrating conversational dead-ends into action-taking superpowers.\nIntroduction: Why ADK Tools Matter\nModern AI agents can do much more than answer questions.\nTools let them calculate, research, manage data, and perform actions on your behalf.\nIn Google ADK, every meaningful capability comes from well-designed tools that act as bridges between the model and external systems.\nWhether you’re building a mortgage advisor, a customer support bot, or an automation workhorse, understanding tool development is critical.\nThis tutorial presents the full blueprint for building reliable, flexible, and robust ADK tools.\nWhat Are Tools in Google ADK?\nGoogle ADK defines tools as call-ready Python functions that agents leverage during conversations.\nEach tool is a callable extension of the language model, offering access to calculations, APIs, files, and more.\nThe agent chooses when to trigger a tool, based on user input or context.\nTools open new dimensions — agents move from passive responders to action-taking assistants.\nCommon use cases include:\nData processing and calculations\nExternal service/API access\nConversation state management\nDatabase and filesystem operations\nControl over agent behavior within sessions\nAnatomy of a Well-Built Tool\nEvery ADK tool should adhere to clear standards for easy integration and reliability.\nKey aspects include:\nA function signature with typed parameters\nAn informative docstring describing arguments and usage\nStructured return values with status reporting\nConsistent error handling for exceptions and edge cases\nWriting Your First Tool: The Simple Calculator\nStart with the basics — a tool that adds two numbers.\nThis hands-on example sets the pattern for building robust ADK tools.\ndef\nadd_numbers\n(\na: float, b: float\n) -> dict:\n\"\"\"Adds two numbers together.\n\n    Use this tool for adding two numeric values.\n    Args:\n        a: The first number.\n        b: The second number.\n    Returns:\n        dict: Status, result, and operation string.\n    \"\"\"\ntry\n:\n        result = a + b\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"result\"\n: result,\n\"operation\"\n:\nf\"\n{a}\n+\n{b}\n=\n{result}\n\"\n}\nexcept\nException\nas\ne:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Failed to add numbers:\n{str(e)}\n\"\n}\nThe tool validates inputs and returns a structured dictionary, including an easy-to-read operation summary.\nThis predictable format makes it simple for your agent to parse the result and respond to the user appropriately.\nAdvanced Calculations with Mortgage Payments\nIncrease complexity by adding business logic and input validation.\nHere’s an expanded example: a mortgage calculator for financial applications.\ndef\ncalculate_mortgage_payment\n(\nprincipal: float, annual_interest_rate: float, years: int\n) -> dict:\n\"\"\"Calculates monthly mortgage payments.\n\n    Args:\n        principal: Initial loan amount.\n        annual_interest_rate: Annual interest (percent).\n        years: Loan term (years).\n    Returns:\n        dict: Status, payment breakdown, and error (if any).\n    \"\"\"\ntry\n:\n        monthly_rate = (annual_interest_rate /\n100\n) /\n12\nnum_payments = years *\n12\nif\nmonthly_rate <\n0\nor\nprincipal <=\n0\nor\nnum_payments <=\n0\n:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\n\"Inputs must be positive. Interest rate cannot be negative.\"\n}\nif\nmonthly_rate ==\n0\n:\n            monthly_payment = principal / num_payments\nelse\n:\n            monthly_payment = principal * (monthly_rate * (\n1\n+ monthly_rate) ** num_payments) / ((\n1\n+ monthly_rate) ** num_payments -\n1\n)\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"monthly_payment\"\n: round(monthly_payment,\n2\n),\n\"total_payments\"\n: round(monthly_payment * num_payments,\n2\n),\n\"total_interest\"\n: round((monthly_payment * num_payments) - principal,\n2\n),\n\"principal\"\n: principal,\n\"interest_rate\"\n: annual_interest_rate,\n\"loan_term_years\"\n: years\n        }\nexcept\nException\nas\ne:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Failed to calculate mortgage payment:\n{str(e)}\n\"\n}\nShort paragraphs in the tool’s docstring clarify intended usage and argument expectations, while error handling returns clear feedback for invalid scenarios.\nAgent with Tools Example\nAfter building tools, attach them to your agent using Google ADK’s APIs.\nfrom\ngoogle.adk.agents\nimport\nAgent\n\nmortgage_advisor = Agent(\n    name=\n\"mortgage_advisor\"\n,\n    model=\n\"gemini-2.5-flash\"\n,\n    description=\n\"Calculates and explains mortgage payments.\"\n,\n    instruction=\n\"Use calculate_mortgage_payment for payment queries, then explain the results.\"\n,\n    tools=[calculate_mortgage_payment]\n)\nA concise setup gives your agent instant calculation power, ready for deployment.\nState Management. Leveraging ToolContext for Advanced Features\nThe\nToolContext\nparameter brings state management and more into your tool’s scope.\nIt offers:\nAccess to cross-session data (persistent state)\nControl over agent behavior and conversation flow\nDetailed user context information\nA Customer Profile Manager\nTools can update or fetch structured user data, providing continuity across sessions.\nfrom\ngoogle.adk.tools.tool_context\nimport\nToolContext\nimport\ndatetime\ndef\nupdate_customer_profile\n(\nfield: str, value: str, tool_context: ToolContext\n) -> dict:\n\"\"\"Updates a customer's profile information.\n\n    Args:\n        field: Profile field to update.\n        value: New value.\n        tool_context: Provided by ADK.\n    Returns:\n        dict: Success status and summary.\n    \"\"\"\ntry\n:\n        profile_key =\n\"user:customer_profile\"\nprofile = tool_context.state.get(profile_key, {})\nif\n\"update_history\"\nnot\nin\nprofile:\n            profile[\n\"update_history\"\n] = []\n        old_value = profile.get(field)\n        profile[field] = value\n        profile[\n\"update_history\"\n].append({\n\"field\"\n: field,\n\"old_value\"\n: old_value,\n\"new_value\"\n: value,\n\"timestamp\"\n: datetime.datetime.now().isoformat()\n        })\n        tool_context.state[profile_key] = profile\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"message\"\n:\nf\"Updated\n{field}\nto '\n{value}\n'\"\n,\n\"profile_summary\"\n: {k: v\nfor\nk, v\nin\nprofile.items()\nif\nk !=\n\"update_history\"\n}\n        }\nexcept\nException\nas\ne:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Failed to update profile:\n{str(e)}\n\"\n}\nAnother tool fetches customer info if needed:\ndef\nget_customer_profile\n(\ntool_context: ToolContext\n) -> dict:\n\"\"\"Retrieves current customer profile.\n\n    Args:\n        tool_context: Provided by ADK.\n    Returns:\n        dict: Profile details or error.\n    \"\"\"\ntry\n:\n        profile_key =\n\"user:customer_profile\"\nprofile = tool_context.state.get(profile_key, {})\nif\nnot\nprofile:\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"message\"\n:\n\"No customer profile found.\"\n,\n\"profile\"\n: {}\n            }\n        clean_profile = {k: v\nfor\nk, v\nin\nprofile.items()\nif\nk !=\n\"update_history\"\n}\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"message\"\n:\n\"Customer profile retrieved.\"\n,\n\"profile\"\n: clean_profile,\n\"last_updated\"\n: profile.get(\n\"update_history\"\n, [{}])[\n-1\n].get(\n\"timestamp\"\n,\n\"Never\"\n)\n        }\nexcept\nException\nas\ne:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Failed to retrieve profile:\n{str(e)}\n\"\n}\nStateful patterns allow your agent to persist context and manage long-running tasks.\nControlling Agent Flow. Escalation and Transfer: Agent Handoffs\nSometimes your bot needs to escalate or hand off conversations to humans.\nTools can automate this, ensuring seamless transitions.\ndef\nescalate_to_support\n(\nissue_type: str, severity: int, description: str, tool_context: ToolContext\n) -> dict:\n\"\"\"Escalates issues to human support.\n\n    Args:\n        issue_type: Issue category.\n        severity: 1-5 scale.\n        description: Details.\n        tool_context: Provided by ADK.\n    Returns:\n        dict: Escalation status and next steps.\n    \"\"\"\ntry\n:\n        escalation_data = {\n\"issue_type\"\n: issue_type,\n\"severity\"\n: severity,\n\"description\"\n: description,\n\"timestamp\"\n: datetime.datetime.now().isoformat(),\n\"escalation_id\"\n:\nf\"ESC-\n{datetime.datetime.now().strftime(\n'%Y%m%d%H%M%S'\n)}\n\"\n}\n        tool_context.state[\n\"current_escalation\"\n] = escalation_data\nif\nseverity >=\n4\n:\n            tool_context.actions.transfer_to_agent =\n\"human_support_agent\"\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"action\"\n:\n\"immediate_transfer\"\n,\n\"message\"\n:\n\"High severity. Transferring to human support.\"\n,\n\"escalation_id\"\n: escalation_data[\n\"escalation_id\"\n],\n\"estimated_wait_time\"\n:\n\"2-5 minutes\"\n}\nelif\nseverity ==\n3\n:\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"action\"\n:\n\"callback_scheduled\"\n,\n\"message\"\n:\nf\"\n{issue_type}\nissue logged. Callback in 2 hours.\"\n,\n\"escalation_id\"\n: escalation_data[\n\"escalation_id\"\n],\n\"next_steps\"\n: [\n\"Ticket created\"\n,\n\"Callback scheduled\"\n,\n\"Email confirmation\"\n]\n            }\nelse\n:\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"action\"\n:\n\"ticket_created\"\n,\n\"message\"\n:\nf\"\n{issue_type}\nissue logged. Response in 24 hours.\"\n,\n\"escalation_id\"\n: escalation_data[\n\"escalation_id\"\n],\n\"next_steps\"\n: [\n\"Ticket created\"\n,\n\"Expect email in 24 hours\"\n]\n            }\nexcept\nException\nas\ne:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Escalation failed:\n{str(e)}\n\"\n}\nCheck for escalation status anytime:\ndef\ncheck_escalation_status\n(\ntool_context: ToolContext\n) -> dict:\n\"\"\"Checks current escalation status.\n    Args:\n        tool_context: Provided by ADK.\n    Returns:\n        dict: Escalation details.\n    \"\"\"\ntry\n:\n        escalation = tool_context.state.get(\n\"current_escalation\"\n)\nif\nnot\nescalation:\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"message\"\n:\n\"No active escalations.\"\n,\n\"has_escalation\"\n:\nFalse\n}\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"has_escalation\"\n:\nTrue\n,\n\"escalation\"\n: {\n\"id\"\n: escalation[\n\"escalation_id\"\n],\n\"type\"\n: escalation[\n\"issue_type\"\n],\n\"severity\"\n: escalation[\n\"severity\"\n],\n\"created\"\n: escalation[\n\"timestamp\"\n],\n\"description\"\n: escalation[\n\"description\"\n]\n            },\n\"message\"\n:\nf\"Active escalation:\n{escalation[\n'escalation_id'\n]}\n\"\n}\nexcept\nException\nas\ne:\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Check escalation failed:\n{str(e)}\n\"\n}\nBuilding agent flow tools helps bridge gaps between automation and human support.\nUsing Built-In ADK Tools\nGoogle Search Integration\nAgents often need up-to-date external data.\nADK provides tools like\ngoogle_search\nso agents can research live web content without leaving their workflow.\nfrom\ngoogle.adk.tools\nimport\ngoogle_search\ndef\ncreate_research_agent\n():\nreturn\nAgent(\n        name=\n\"research_assistant\"\n,\n        model=\n\"gemini-2.0-flash-exp\"\n,\n        instruction=\n\"\"\"\n        Use google_search to find current information.\n        Always cite sources and explain recency.\n        \"\"\"\n,\n        tools=[google_search]\n    )\nThis pattern lets agents respond with the latest information, crucial for data-driven applications.\nCode Execution Tool\nProgrammatic agents must often run and validate code snippets.\nADK's\ncode_interpreter\ntool enables on-the-fly code testing, analysis, and demonstration.\nfrom\ngoogle.adk.tools\nimport\ncode_interpreter\ndef\ncreate_coding_assistant\n():\nreturn\nAgent(\n        name=\n\"coding_assistant\"\n,\n        model=\n\"gemini-2.0-flash-exp\"\n,\n        instruction=\n\"\"\"Execute Python code with code_interpreter. Explain results before and after execution.\"\"\"\n,\n        tools=[code_interpreter]\n    )\nRun code, check results, analyze data — all inside the agent’s workflow.\nBest Practices and Design Patterns\nConsistent Return Structures\nUniformity matters for both agent parsing and reliability.\nSuccess Pattern\n{\n\"status\"\n:\n\"success\"\n,\n\"data\"\n: {},\n# Actual response data\n\"message\"\n:\n\"Human-readable message\"\n}\nError Pattern\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\n\"Clear error explanation\"\n,\n\"error_code\"\n:\n\"OPTIONAL_ERROR_CODE\"\n}\nInput Validation\nNever trust incoming data blindly.\nValidate early; inform the agent clearly on rejection.\nimport\nre\ndef\nvalidate_email_tool\n(\nemail: str\n) -> dict:\n\"\"\"Validates email address format.\"\"\"\nif\nnot\nemail\nor\nnot\nisinstance(email, str):\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\n\"Email must be a non-empty string\"\n}\n    email_pattern =\nr'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\nif\nnot\nre.match(email_pattern, email.strip()):\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\n\"Invalid email format\"\n}\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"email\"\n: email.strip().lower(),\n\"message\"\n:\n\"Email format is valid\"\n}\nTesting for business rule compliance leads to lower error rates in production.\nLogging and Debugging\nMonitor for issues with clear logging.\nMake debugging easy by capturing all relevant details.\nimport\nlogging\ndef\ndebug_tool_example\n(\ndata: str, tool_context: ToolContext\n) -> dict:\n\"\"\"Logging example.\"\"\"\nlogger = logging.getLogger(\nf\"tool.\n{__name__}\n\"\n)\ntry\n:\n        logger.info(\nf\"Processing data:\n{data[:\n50\n]}\n...\"\n)\n        result = process_data(data)\n        logger.info(\nf\"Completed successfully, size:\n{len(str(result))}\n\"\n)\nreturn\n{\n\"status\"\n:\n\"success\"\n,\n\"result\"\n: result\n        }\nexcept\nException\nas\ne:\n        logger.error(\nf\"Tool failed:\n{str(e)}\n\"\n, exc_info=\nTrue\n)\nreturn\n{\n\"status\"\n:\n\"error\"\n,\n\"error_message\"\n:\nf\"Processing failed:\n{str(e)}\n\"\n}\nShort, targeted logs aid rapid identification of flaws and exceptions during development and production.\nConclusion\nThe power of ADK agents lies in the tools you build for them.\nEvery reliable, useful, and robust tool follows the practices outlined here:\nStart with the simplest working unit; expand as needed\nTreat errors as first-class citizens, reporting them cleanly to agents/users\nUse\nToolContext\nwherever persistent state or agent control is needed\nUnit test every tool for correctness, edge cases, and robustness\nDocument functions with precise, comprehensive docstrings\nStandardize return formats for predictability and interoperability\nBy mastering tool creation in ADK, you turn conversational agents into active digital teammates, capable of solving practical problems and running workflows at scale.\nThis foundation ensures your journey into ADK agent tool development yields apps that are powerful, maintainable, and deeply effective.\nNow, unleash your imagination and build agents that truly act in the real world.\nPS:\nIf you like this article, share it with others ♻️\nWould help a lot ❤️\nAnd feel free to follow me for more content like this.\n#\nartificial-intelligence\n#\nlarge-language-models\n#\nllm\n#\nagents\n#\nagentic-ai\n#\ngoogle-adk",
  "metadata": {
    "title": "Building Tools for Google ADK Agents: Complete Tutorial",
    "description": "Have you ever watched your AI agent struggle — locked in endless loops of “I don’t know how to help” while users grow impatient and annoyed?\nFor many developers, building intelligent agents becomes an exercise in frustration, wasted hours, and groani...",
    "keywords": null,
    "author": null,
    "og:title": "Building Tools for Google ADK Agents: Complete Tutorial",
    "og:description": "Have you ever watched your AI agent struggle — locked in endless loops of “I don’t know how to help” while users grow impatient and annoyed?\nFor many developers, building intelligent agents becomes an exercise in frustration, wasted hours, and groani...",
    "og:url": "https://juancolamendy.hashnode.dev/building-tools-for-google-adk-agents-complete-tutorial",
    "og:site_name": "Juan C Olamendy",
    "og:image": "https://cdn.hashnode.com/res/hashnode/image/upload/v1759249285356/58792764-fc49-4560-8306-7af94ee3602f.png",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:type": "article",
    "twitter:card": "summary_large_image",
    "twitter:creator": "https://twitter.com/juancolamendy",
    "twitter:title": "Building Tools for Google ADK Agents: Complete Tutorial",
    "twitter:description": "Have you ever watched your AI agent struggle — locked in endless loops of “I don’t know how to help” while users grow impatient and annoyed?\nFor many developers, building intelligent agents becomes an exercise in frustration, wasted hours, and groani...",
    "twitter:image": "https://cdn.hashnode.com/res/hashnode/image/upload/v1759249285356/58792764-fc49-4560-8306-7af94ee3602f.png",
    "article:published_time": "2025-09-30T16:24:32.625Z",
    "article:author": "https://juancolamendy.hashnode.dev/@juancolamendy"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/4d0df569f3db/code/body-000.txt",
      "chars": 591,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-001.txt",
      "chars": 1502,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-002.txt",
      "chars": 322,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-003.txt",
      "chars": 1277,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-004.txt",
      "chars": 995,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-005.txt",
      "chars": 2044,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-006.txt",
      "chars": 1103,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-007.txt",
      "chars": 341,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-008.txt",
      "chars": 320,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-009.txt",
      "chars": 109,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-010.txt",
      "chars": 115,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-011.txt",
      "chars": 632,
      "language": "text"
    },
    {
      "file": "attachments/4d0df569f3db/code/body-012.txt",
      "chars": 634,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/res/hashnode/image/upload/v1759249285356/58792764-fc49-4560-8306-7af94ee3602f.png",
      "bytes": 2697640,
      "reused": true,
      "local_path": "attachments/4d0df569f3db/images/000.png",
      "src": "https://cdn.hashnode.com/res/hashnode/image/upload/v1759249285356/58792764-fc49-4560-8306-7af94ee3602f.png",
      "alt": "Building Tools for Google ADK Agents: Complete Tutorial",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://juancolamendy.hashnode.dev/building-tools-for-google-adk-agents-complete-tutorial.md",
      "label": "View as Markdown",
      "type": "a"
    },
    {
      "href": "https://hashnode.com/@juancolamendy",
      "label": "J",
      "type": "a"
    },
    {
      "href": "https://twitter.com/juancolamendy",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://www.linkedin.com/in/juancolamendy/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://juancolamendy.hashnode.dev/tag/artificial-intelligence",
      "label": "# artificial-intelligence",
      "type": "a"
    },
    {
      "href": "https://juancolamendy.hashnode.dev/tag/large-language-models",
      "label": "# large-language-models",
      "type": "a"
    },
    {
      "href": "https://juancolamendy.hashnode.dev/tag/llm",
      "label": "# llm",
      "type": "a"
    },
    {
      "href": "https://juancolamendy.hashnode.dev/tag/agents",
      "label": "# agents",
      "type": "a"
    },
    {
      "href": "https://juancolamendy.hashnode.dev/tag/agentic-ai",
      "label": "# agentic-ai",
      "type": "a"
    },
    {
      "href": "https://juancolamendy.hashnode.dev/tag/google-adk",
      "label": "# google-adk",
      "type": "a"
    }
  ],
  "retry_at": "2026-10-03T08:05:56",
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:50:35Z",
  "source_url": "https://juancolamendy.hashnode.dev/building-tools-for-google-adk-agents-complete-tutorial",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 15360,
  "expected_body_images": 1,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/4d0df569f3db/pages/source.html",
  "local_html": "attachments/4d0df569f3db/pages/article.html",
  "body_base_path": "attachments/4d0df569f3db/pages/",
  "body_markdown_path": "attachments/4d0df569f3db/pages/article.md"
}