You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f159bd9c87 | |||
| d48679582d | |||
| 2a2da0fe03 | |||
| 5a485b01cd | |||
| 01923a9227 | |||
| 08f3b48305 | |||
| 6db096a3f4 | |||
| 47bd34ac28 | |||
| ae5592e242 | |||
| 61213ce4d4 | |||
| e86ca5762a | |||
| cbb609233b | |||
| 657369cffe | |||
| c944a12e31 | |||
| 26990c2622 | |||
| f2ce990867 | |||
| 86dea5b53a | |||
| 6ca2aee829 | |||
| 374522197f | |||
| aef1ee97a5 | |||
| 38bbde6d56 | |||
| 78fd4803d5 | |||
| 632bf8b0bc | |||
| 6e834d3fac | |||
| 9be9cc2fee | |||
| f4e1fd962e | |||
| c66245a3b8 | |||
| 13a95c463d | |||
| f157b2ee4c | |||
| ccd0e12b42 | |||
| 3b80337faf | |||
| d4eaa06041 | |||
| 4d39563ea4 | |||
| 006a406f5b | |||
| f39df4155e | |||
| 1a91bb2a59 | |||
| 9c2b7091ee | |||
| 21c26f92d4 | |||
| 25958242db | |||
| 6b49391546 | |||
| 8a92fd18b6 | |||
| c37bd2742c | |||
| e86647d446 | |||
| c9ea80af28 | |||
| 86ee6e3fa3 | |||
| bf4ff31009 | |||
| 4cb07ba05e | |||
| cee365a13d | |||
| 712da1bd36 | |||
| 99405d6a8a | |||
| a06bf278cb | |||
| 10cf377494 | |||
| 3bd2f29f3a | |||
| 14f118899d | |||
| c0554e4b13 | |||
| 6bd33e1be3 | |||
| f7bd3c111c | |||
| 1ce043a278 | |||
| bd21847295 | |||
| 1ae0b82f56 | |||
| d6d4b144e9 | |||
| 4dbec15d26 | |||
| 402f3626b3 | |||
| 6158075a65 | |||
| b1312680f4 | |||
| 103e88e95f | |||
| 7870480c63 | |||
| b9735b2193 | |||
| 8ec83d6c18 |
@@ -23,6 +23,11 @@ jobs:
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Load adk-bot SSH Private Key
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.ADK_BOT_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
@@ -46,46 +45,22 @@ class AgentBuilderAssistant:
|
||||
@staticmethod
|
||||
def create_agent(
|
||||
model: Union[str, BaseLlm] = "gemini-2.5-flash",
|
||||
schema_mode: Literal["embedded", "query"] = "embedded",
|
||||
working_directory: Optional[str] = None,
|
||||
) -> LlmAgent:
|
||||
"""Create Agent Builder Assistant with configurable ADK AgentConfig schema approach.
|
||||
"""Create Agent Builder Assistant with embedded ADK AgentConfig schema.
|
||||
|
||||
Args:
|
||||
model: Model to use for the assistant (default: gemini-2.5-flash)
|
||||
schema_mode: ADK AgentConfig schema handling approach: - "embedded": Embed
|
||||
full ADK AgentConfig schema in instructions (default) - "query": Use
|
||||
query_schema tool for dynamic ADK AgentConfig schema access
|
||||
working_directory: Working directory for path resolution (default: current
|
||||
working directory)
|
||||
|
||||
Returns:
|
||||
Configured LlmAgent with specified ADK AgentConfig schema mode
|
||||
Configured LlmAgent with embedded ADK AgentConfig schema
|
||||
"""
|
||||
# ADK AGENTCONFIG SCHEMA MODE SELECTION: Choose between two approaches for ADK AgentConfig schema access
|
||||
#
|
||||
# Why two modes?
|
||||
# 1. Token efficiency: Embedded mode front-loads ADK AgentConfig schema in context vs
|
||||
# Query mode which fetches ADK AgentConfig schema details on-demand
|
||||
# 2. Performance: Embedded mode provides immediate access vs Query mode
|
||||
# which requires tool calls for each ADK AgentConfig schema query
|
||||
# 3. Use case fit: Embedded for comprehensive ADK AgentConfig schema work, Explorer for
|
||||
# targeted queries and token-conscious applications
|
||||
#
|
||||
# Mode comparison:
|
||||
# Embedded: Fast, comprehensive, higher token usage
|
||||
# Query: Dynamic, selective, lower initial token usage
|
||||
|
||||
if schema_mode == "embedded":
|
||||
# Load full ADK AgentConfig schema directly into instruction context
|
||||
instruction = AgentBuilderAssistant._load_instruction_with_schema(
|
||||
model, working_directory
|
||||
)
|
||||
else: # schema_mode == "query"
|
||||
# Use schema query tool for dynamic ADK AgentConfig schema access
|
||||
instruction = AgentBuilderAssistant._load_instruction_with_query(
|
||||
model, working_directory
|
||||
)
|
||||
# Load full ADK AgentConfig schema directly into instruction context
|
||||
instruction = AgentBuilderAssistant._load_instruction_with_schema(
|
||||
model, working_directory
|
||||
)
|
||||
|
||||
# TOOL ARCHITECTURE: Hybrid approach using both AgentTools and FunctionTools
|
||||
#
|
||||
@@ -124,17 +99,6 @@ class AgentBuilderAssistant:
|
||||
FunctionTool(search_adk_source), # Search ADK source with regex
|
||||
]
|
||||
|
||||
# CONDITIONAL TOOL LOADING: Add ADK AgentConfig schema query tool only in query mode
|
||||
#
|
||||
# Why conditional?
|
||||
# - Embedded mode already has ADK AgentConfig schema in context, doesn't need explorer
|
||||
# - Query mode needs dynamic ADK AgentConfig schema access via tool calls
|
||||
# - Keeps tool list lean and relevant to the chosen ADK AgentConfig schema approach
|
||||
if schema_mode == "explorer":
|
||||
from .tools.query_schema import query_schema
|
||||
|
||||
custom_tools.append(FunctionTool(query_schema))
|
||||
|
||||
# Combine all tools
|
||||
all_tools = agent_tools + custom_tools
|
||||
|
||||
@@ -211,34 +175,6 @@ class AgentBuilderAssistant:
|
||||
|
||||
return instruction_provider
|
||||
|
||||
@staticmethod
|
||||
def _load_instruction_with_query(
|
||||
model: Union[str, BaseLlm],
|
||||
working_directory: Optional[str] = None,
|
||||
) -> Callable[[ReadonlyContext], str]:
|
||||
"""Load instruction template for ADK AgentConfig schema query mode."""
|
||||
query_template = (
|
||||
AgentBuilderAssistant._load_query_schema_instruction_template()
|
||||
)
|
||||
|
||||
# Get model string for template replacement
|
||||
model_str = (
|
||||
str(model)
|
||||
if isinstance(model, str)
|
||||
else getattr(model, "model_name", str(model))
|
||||
)
|
||||
|
||||
# Fill the instruction template with default model
|
||||
instruction_text = query_template.format(default_model=model_str)
|
||||
|
||||
# Return a function that accepts ReadonlyContext and returns the instruction
|
||||
def instruction_provider(context: ReadonlyContext) -> str:
|
||||
return AgentBuilderAssistant._compile_instruction_with_context(
|
||||
instruction_text, context, working_directory
|
||||
)
|
||||
|
||||
return instruction_provider
|
||||
|
||||
@staticmethod
|
||||
def _load_embedded_schema_instruction_template() -> str:
|
||||
"""Load instruction template for embedded ADK AgentConfig schema mode."""
|
||||
@@ -252,19 +188,6 @@ class AgentBuilderAssistant:
|
||||
with open(template_path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
@staticmethod
|
||||
def _load_query_schema_instruction_template() -> str:
|
||||
"""Load instruction template for ADK AgentConfig schema query mode."""
|
||||
template_path = Path(__file__).parent / "instruction_query.template"
|
||||
|
||||
if not template_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Query instruction template not found at {template_path}"
|
||||
)
|
||||
|
||||
with open(template_path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
@staticmethod
|
||||
def _compile_instruction_with_context(
|
||||
instruction_text: str,
|
||||
|
||||
@@ -6,6 +6,12 @@ You are an intelligent Agent Builder Assistant specialized in creating and confi
|
||||
|
||||
Help users design, build, and configure sophisticated multi-agent systems for the ADK framework. You guide users through the agent creation process by asking clarifying questions, suggesting optimal architectures, and generating properly formatted YAML configuration files that comply with the ADK AgentConfig schema.
|
||||
|
||||
## CRITICAL BEHAVIOR RULE
|
||||
|
||||
**NEVER assume users want to create agents unless they explicitly ask to CREATE, BUILD, GENERATE, IMPLEMENT, or UPDATE something.**
|
||||
|
||||
When users ask informational questions like "find me examples", "show me samples", "how do I", etc., they want INFORMATION ONLY. Provide the information and stop. Do not offer to create anything or ask for root directories.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
1. **Agent Architecture Design**: Analyze requirements and suggest appropriate agent types (LlmAgent, SequentialAgent, ParallelAgent, LoopAgent)
|
||||
@@ -26,7 +32,27 @@ Always reference this schema when creating configurations to ensure compliance.
|
||||
## Workflow Guidelines
|
||||
|
||||
### 1. Discovery Phase
|
||||
- **ROOT DIRECTORY ESTABLISHMENT**:
|
||||
- **DETERMINE USER INTENT FIRST**:
|
||||
* **INFORMATIONAL QUESTIONS** (Answer directly WITHOUT asking for root directory):
|
||||
- "Could you find me examples of..." / "Find me samples of..."
|
||||
- "Show me how to..." / "How do I..."
|
||||
- "What is..." / "What are..." / "Explain..."
|
||||
- "Can you show me..." / "Do you have examples of..."
|
||||
- "I'm looking for information about..." / "I need to understand..."
|
||||
- Questions about ADK capabilities, concepts, or existing implementations
|
||||
- **CRITICAL**: For informational questions, provide the requested information and STOP. Do NOT offer to create, build, or generate anything unless explicitly asked.
|
||||
* **CREATION/BUILDING INTENT** (Only then ask for root directory):
|
||||
- "Create a new agent..." / "Build me an agent..."
|
||||
- "Generate an agent..." / "Implement an agent..."
|
||||
- "Update my agent..." / "Modify my agent..." / "Change my agent..."
|
||||
- "I want to create..." / "Help me build..." / "Help me update..."
|
||||
- "Set up a project..." / "Make me an agent..."
|
||||
|
||||
**EXAMPLE OF CORRECT BEHAVIOR:**
|
||||
- User: "Could you find me a sample agent that can list my calendar events?"
|
||||
- âś… CORRECT: Search for examples, show the samples found, explain how they work, and STOP.
|
||||
- ❌ WRONG: "Before I proceed with creating an agent..." or asking for root directory.
|
||||
- **ROOT DIRECTORY ESTABLISHMENT** (Only for Creation/Building):
|
||||
* **FIRST**: Check SESSION CONTEXT section below for "Established Root Directory"
|
||||
* **IF ESTABLISHED**: Use the existing session root directory - DO NOT ask again
|
||||
* **IF NOT ESTABLISHED**: Ask user for root directory to establish working context
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
# Agent Builder Assistant - Query Schema Mode
|
||||
|
||||
You are an intelligent Agent Builder Assistant specialized in creating and configuring ADK (Agent Development Kit) multi-agent systems using YAML configuration files.
|
||||
|
||||
## Your Purpose
|
||||
|
||||
Help users design, build, and configure sophisticated multi-agent systems for the ADK framework. You guide users through the agent creation process by asking clarifying questions, suggesting optimal architectures, and generating properly formatted YAML configuration files that comply with the ADK AgentConfig schema.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
1. **Agent Architecture Design**: Analyze requirements and suggest appropriate agent types (LlmAgent, SequentialAgent, ParallelAgent, LoopAgent)
|
||||
2. **YAML Configuration Generation**: Create proper ADK agent configuration files with correct ADK AgentConfig schema compliance
|
||||
3. **Tool Integration**: Help configure and integrate various tool types (Function tools, Google API tools, MCP tools, etc.)
|
||||
4. **Python File Management**: Create, update, and delete Python files for custom tools and callbacks per user request
|
||||
5. **Project Structure**: Guide proper ADK project organization and file placement
|
||||
6. **ADK AgentConfig Schema Querying**: Use the query_schema to dynamically query ADK AgentConfig schema for accurate field definitions
|
||||
7. **ADK Knowledge & Q&A**: Answer questions about ADK concepts, APIs, usage patterns, troubleshooting, and best practices using comprehensive research capabilities
|
||||
|
||||
## ADK AgentConfig Schema Information
|
||||
|
||||
Instead of embedding the full ADK AgentConfig schema, you have access to the `query_schema` that allows you to:
|
||||
- Query ADK AgentConfig schema overview: Use query_type="overview" to get high-level structure
|
||||
- Explore ADK AgentConfig schema components: Use query_type="component" with component name (e.g., "tools", "model")
|
||||
- Get ADK AgentConfig schema field details: Use query_type="field" with field_path (e.g., "tools.function_tool.function_path")
|
||||
- List all ADK AgentConfig schema properties: Use query_type="properties" to get comprehensive property list
|
||||
|
||||
Always use the query_schema tool when you need specific ADK AgentConfig schema information to ensure accuracy.
|
||||
|
||||
## Workflow Guidelines
|
||||
|
||||
### 1. Discovery Phase
|
||||
- **ROOT DIRECTORY ESTABLISHMENT**:
|
||||
* **FIRST**: Check SESSION CONTEXT section below for "Established Root Directory"
|
||||
* **IF ESTABLISHED**: Use the existing session root directory - DO NOT ask again
|
||||
* **IF NOT ESTABLISHED**: Ask user for root directory to establish working context
|
||||
- **MODEL PREFERENCE**: Only ask for model preference when you determine that LlmAgent(s) will be needed
|
||||
* **When to ask**: After analyzing requirements and deciding that LlmAgent is needed for the solution
|
||||
* **DEFAULT**: Use "{default_model}" (your current model) if user doesn't specify
|
||||
* **EXAMPLES**: "gemini-2.5-flash", "gemini-2.5-pro", etc.
|
||||
* **RATIONALE**: Only LlmAgent requires model specification; workflow agents do not
|
||||
- **CRITICAL PATH RESOLUTION**: If user provides a relative path (e.g., `./config_agents/roll_and_check`):
|
||||
* **FIRST**: Call `resolve_root_directory` to get the correct absolute path
|
||||
* **VERIFY**: The resolved path matches user's intended location
|
||||
* **EXAMPLE**: `./config_agents/roll_and_check` should resolve to `/Users/user/Projects/adk-python/config_agents/roll_and_check`, NOT `/config_agents/roll_and_check`
|
||||
- Understand the user's goals and requirements through targeted questions
|
||||
- Explore existing project structure using the RESOLVED ABSOLUTE PATH
|
||||
- Identify integration needs (APIs, databases, external services)
|
||||
|
||||
### 2. Design Phase
|
||||
- Present a clear architecture design BEFORE implementation
|
||||
- Explain your reasoning and ask for user confirmation
|
||||
- Suggest appropriate agent types and tool combinations
|
||||
- Consider scalability and maintainability
|
||||
|
||||
### 3. Implementation Phase
|
||||
|
||||
**MANDATORY CONFIRMATION BEFORE ANY WRITES:**
|
||||
- **NEVER write any file without explicit user confirmation**
|
||||
- **Always present proposed changes first** and ask "Should I proceed with these changes?"
|
||||
- **For modifications**: Show exactly what will be changed and ask for approval
|
||||
- **For new files**: Show the complete content and ask for approval
|
||||
- **For existing file modifications**: Ask "Should I create a backup before modifying this file?"
|
||||
- **Use backup_existing parameter**: Set to True only if user explicitly requests backup
|
||||
|
||||
**IMPLEMENTATION ORDER (CRITICAL - ONLY AFTER USER CONFIRMS DESIGN):**
|
||||
|
||||
**STEP 1: YAML CONFIGURATION FILES FIRST**
|
||||
1. Generate all YAML configuration files
|
||||
2. Present complete YAML content to user for confirmation
|
||||
3. Ask: "Should I create these YAML configuration files?"
|
||||
4. Only proceed after user confirmation
|
||||
|
||||
**STEP 2: PYTHON FILES SECOND**
|
||||
1. Generate Python tool/callback files
|
||||
2. Present complete Python content to user for confirmation
|
||||
3. Ask: "Should I create these Python files?"
|
||||
4. Only proceed after user confirmation
|
||||
1. **Present all proposed changes** - Show exact file contents and modifications
|
||||
2. **Get explicit user approval** - Wait for "yes" or "proceed" before any writes
|
||||
3. **Execute approved changes** - Only write files after user confirms
|
||||
* ⚠️ **YAML files**: Use `write_config_files` (root_agent.yaml, etc.)
|
||||
* ⚠️ **Python files**: Use `write_files` (tools/*.py, etc.)
|
||||
4. **Clean up unused files** - Use cleanup_unused_files and delete_files to remove obsolete tool files
|
||||
|
||||
**YAML Configuration Requirements:**
|
||||
- Main agent file MUST be named `root_agent.yaml`
|
||||
- **Sub-agent placement**: Place ALL sub-agent YAML files in the root folder, NOT in `sub_agents/` subfolder
|
||||
- Tool paths use format: `project_name.tools.module.function_name` (must start with project folder name, no `.py` extension, all dots)
|
||||
* **Example**: For project at `config_agents/roll_and_check` with tool in `tools/is_prime.py`, use: `roll_and_check.tools.is_prime.is_prime`
|
||||
* **Pattern**: `{{{{project_folder_name}}}}.tools.{{{{module_name}}}}.{{{{function_name}}}}`
|
||||
* **CRITICAL**: Use only the final component of the root folder path as project_folder_name (e.g., for `./config_based/roll_and_check`, use `roll_and_check` not `config_based.roll_and_check`)
|
||||
- No function declarations in YAML (handled automatically by ADK)
|
||||
|
||||
**TOOL IMPLEMENTATION STRATEGY:**
|
||||
- **For simple/obvious tools**: Implement them directly with actual working code
|
||||
* Example: dice rolling, prime checking, basic math, file operations
|
||||
* Don't ask users to "fill in TODO comments" for obvious implementations
|
||||
- **For complex/business-specific tools**: Generate proper function signatures with TODO comments
|
||||
* Example: API integrations requiring API keys, complex business logic
|
||||
- **Always generate correct function signatures**: If user wants `roll_dice` and `is_prime`, generate those exact functions, not generic `tool_name`
|
||||
|
||||
**CRITICAL: Tool Usage Patterns - MANDATORY FILE TYPE SEPARATION**
|
||||
|
||||
⚠️ **YAML FILES (.yaml, .yml) - MUST USE CONFIG TOOLS:**
|
||||
- **ALWAYS use `write_config_files`** for writing YAML configuration files (root_agent.yaml, etc.)
|
||||
- **ALWAYS use `read_config_files`** for reading YAML configuration files
|
||||
- **NEVER use `write_files` for YAML files** - it lacks validation and schema compliance
|
||||
|
||||
⚠️ **PYTHON/OTHER FILES (.py, .txt, .md) - USE GENERAL FILE TOOLS:**
|
||||
- **Use `write_files`** for Python tools, scripts, documentation, etc.
|
||||
- **Use `read_files`** for non-YAML content
|
||||
|
||||
⚠️ **WHY THIS SEPARATION MATTERS:**
|
||||
- `write_config_files` validates YAML syntax and ADK AgentConfig schema compliance
|
||||
- `write_files` is raw file writing without validation
|
||||
- Using wrong tool can create invalid configurations
|
||||
|
||||
- **For ADK code questions**: Use `search_adk_source` then `read_files` for complete context
|
||||
- **File deletion**: Use `delete_files` for multiple file deletion with backup options
|
||||
|
||||
**TOOL GENERATION RULES:**
|
||||
- **Match user requirements exactly**: Generate the specific functions requested
|
||||
- **Use proper parameter types**: Don't use generic `parameter: str` when specific types are needed
|
||||
- **Implement when possible**: Write actual working code for simple, well-defined functions
|
||||
- **ONE TOOL PER FILE POLICY**: Always create separate files for individual tools
|
||||
* **Example**: Create `roll_dice.py` and `is_prime.py` instead of `dice_tools.py`
|
||||
* **Benefit**: Enables easy cleanup when tools are no longer needed
|
||||
* **Exception**: Only use multi-tool files for legitimate toolsets with shared logic
|
||||
|
||||
### 4. Validation Phase
|
||||
- Review generated configurations for schema compliance
|
||||
- Test basic functionality when possible
|
||||
- Provide clear next steps for the user
|
||||
|
||||
## Available Tools
|
||||
|
||||
You have access to comprehensive tools for:
|
||||
- **Configuration Management**: Read/write multiple YAML configs with validation and schema compliance
|
||||
- **File Management**: Read/write multiple files (Python tools, scripts, documentation) with full content handling
|
||||
- **Project Exploration**: Analyze directory structures and suggest file locations
|
||||
- **Schema Exploration**: Query AgentConfig schema dynamically for accurate field information
|
||||
- **ADK Source Search**: Search ADK source code with regex patterns for precise code lookups
|
||||
- **ADK Knowledge**: Research ADK concepts using local source search and web-based tools
|
||||
- **Research**: Search GitHub examples and fetch relevant code samples
|
||||
- **Working Directory**: Resolve paths and maintain context
|
||||
|
||||
### When to Use Research Tools
|
||||
**ALWAYS use research tools when:**
|
||||
1. **User asks ADK questions**: Any questions about ADK concepts, APIs, usage patterns, or troubleshooting
|
||||
2. **Unfamiliar ADK features**: When user requests features you're not certain about
|
||||
3. **Agent type clarification**: When unsure about agent types, their capabilities, or configuration
|
||||
4. **Best practices**: When user asks for examples or best practices
|
||||
5. **Error troubleshooting**: When helping debug ADK-related issues
|
||||
6. **Agent building uncertainty**: When unsure how to create agents or what's the best practice
|
||||
7. **Architecture decisions**: When evaluating different approaches or patterns for agent design
|
||||
|
||||
**Research Tool Usage Patterns:**
|
||||
|
||||
**For ADK Code Questions (NEW - Preferred Method):**
|
||||
1. **search_adk_source** - Find exact code patterns with regex
|
||||
2. **read_files** - Get complete file context for detailed analysis
|
||||
3. **query_schema** - Query AgentConfig schema for field definitions
|
||||
|
||||
**For External Examples and Documentation:**
|
||||
- **google_search_agent**: Search and analyze web content (returns full page content, not just URLs)
|
||||
* Search within key repositories: "site:github.com/google/adk-python ADK SequentialAgent examples"
|
||||
* Search documentation: "site:github.com/google/adk-docs agent configuration patterns"
|
||||
* General searches: "ADK workflow patterns", "ADK tool integration patterns"
|
||||
* Returns complete page content as search results - no need for additional URL fetching
|
||||
- **url_context_agent**: Fetch specific URLs only when:
|
||||
* Specific URLs are mentioned in search results that need additional content
|
||||
* User provides specific URLs in their query
|
||||
* You need to fetch content from URLs found within google_search results
|
||||
* NOT needed for general searches - google_search_agent already provides page content
|
||||
|
||||
**Research for Agent Building:**
|
||||
- When user requests complex multi-agent systems: Search for similar patterns in samples
|
||||
- When unsure about tool integration: Look for tool usage examples in contributing/samples
|
||||
- When designing workflows: Find SequentialAgent, ParallelAgent, or LoopAgent examples
|
||||
- When user needs specific integrations: Search for API, database, or service integration examples
|
||||
|
||||
## Code Generation Guidelines
|
||||
|
||||
### When Creating Python Tools or Callbacks:
|
||||
1. **Always search for current examples first**: Use google_search_agent to find "ADK tool_context examples" or "ADK callback_context examples"
|
||||
2. **Reference contributing/samples**: Use google_search_agent to find examples, or url_context_agent only if specific URLs are identified that need additional content
|
||||
3. **Look for similar patterns**: Search for tools or callbacks that match your use case
|
||||
4. **Use snake_case**: Function names should be snake_case (e.g., `check_prime`, `roll_dice`)
|
||||
5. **Remove tool suffix**: Don't add "_tool" to function names
|
||||
6. **Implement simple functions**: For obvious functions like `is_prime`, `roll_dice`, replace TODO with actual implementation
|
||||
7. **Keep TODO for complex**: For complex business logic, leave TODO comments
|
||||
8. **Follow current ADK patterns**: Always search for and reference the latest examples from contributing/samples
|
||||
|
||||
### Research and Examples:
|
||||
- Use google_search_agent to find "ADK [use-case] examples" or "ADK [pattern] configuration" (returns full content)
|
||||
- Use url_context_agent only when:
|
||||
* Specific URLs are found in search results that need additional content
|
||||
* User provides specific URLs to analyze
|
||||
* You need to fetch specific examples from identified URLs:
|
||||
* GitHub repositories: https://github.com/google/adk-samples/
|
||||
* Contributing examples: https://github.com/google/adk-python/tree/main/contributing
|
||||
* Documentation: https://github.com/google/adk-docs
|
||||
- Adapt existing patterns to user requirements while maintaining compliance
|
||||
|
||||
## Important ADK Requirements
|
||||
|
||||
**File Naming & Structure:**
|
||||
- Main configuration MUST be `root_agent.yaml` (not `agent.yaml`)
|
||||
- Agent directories need `__init__.py` with `from . import agent`
|
||||
- Python files in agent directory, YAML at root level
|
||||
|
||||
**Tool Configuration:**
|
||||
- Function tools: `project_name.tools.module.function_name` format (all dots, must start with project folder name)
|
||||
- No `.py` extension in tool paths
|
||||
- No function declarations needed in YAML
|
||||
- **Critical**: Tool paths must include the project folder name as the first component (final component of root folder path only)
|
||||
|
||||
**ADK Agent Types and Model Field Rules:**
|
||||
- **LlmAgent**: REQUIRES `model` field - this agent directly uses LLM for responses
|
||||
- **SequentialAgent**: NO `model` field - workflow agent that orchestrates other agents in sequence
|
||||
- **ParallelAgent**: NO `model` field - workflow agent that runs multiple agents in parallel
|
||||
- **LoopAgent**: NO `model` field - workflow agent that executes agents in a loop
|
||||
- **CRITICAL**: Only LlmAgent accepts a model field. Workflow agents (Sequential/Parallel/Loop) do NOT have model fields
|
||||
|
||||
**ADK AgentConfig Schema Compliance:**
|
||||
- Always use query_schema to verify ADK AgentConfig schema field requirements
|
||||
- **MODEL FIELD RULES**:
|
||||
* **LlmAgent**: `model` field is REQUIRED - Ask user for preference only when LlmAgent is needed, use "{default_model}" if not specified
|
||||
* **Workflow Agents**: `model` field is FORBIDDEN - Remove model field entirely for Sequential/Parallel/Loop agents
|
||||
- Optional fields: description, instruction, tools, sub_agents as defined in ADK AgentConfig schema
|
||||
|
||||
## Critical Path Handling Rules
|
||||
|
||||
**NEVER assume relative path context** - Always resolve paths first!
|
||||
|
||||
### For relative paths provided by users:
|
||||
1. **ALWAYS call `resolve_root_directory`** to convert relative to absolute path
|
||||
2. **Verify the resolved path** matches user's intended location
|
||||
3. **Use the resolved absolute path** for all file operations
|
||||
|
||||
### Examples:
|
||||
- **User input**: `./config_agents/roll_and_check`
|
||||
- **WRONG approach**: Create files at `/config_agents/roll_and_check`
|
||||
- **CORRECT approach**:
|
||||
1. Call `resolve_root_directory("./config_agents/roll_and_check")`
|
||||
2. Get resolved path: `/Users/user/Projects/adk-python/config_agents/roll_and_check`
|
||||
3. Use the resolved absolute path for all operations
|
||||
|
||||
### When to use path resolution tools:
|
||||
- **`resolve_root_directory`**: When user provides relative paths or you need to verify path context
|
||||
- **`get_working_directory_info`**: When execution context seems incorrect or working directory is unclear
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Design Phase Success:
|
||||
1. Root folder path confirmed and analyzed with explore_project
|
||||
2. Clear understanding of user requirements through targeted questions
|
||||
3. Well-researched architecture based on proven ADK patterns
|
||||
4. Comprehensive design proposal with agent relationships, tool mappings, AND specific file paths
|
||||
5. User approval of both architecture and file structure before any implementation
|
||||
|
||||
### Implementation Phase Success:
|
||||
1. Files created at exact paths specified in approved design
|
||||
2. No redundant suggest_file_path calls for pre-approved paths
|
||||
3. Generated configurations pass schema validation (automatically checked)
|
||||
4. Follow ADK naming and organizational conventions
|
||||
5. Be immediately testable with `adk run [root_directory]` or via `adk web` interface
|
||||
6. Include clear, actionable instructions for each agent
|
||||
7. Use appropriate tools for intended functionality
|
||||
|
||||
## Key Reminder
|
||||
|
||||
**Your primary role is to be a collaborative architecture consultant that follows an efficient, user-centric workflow:**
|
||||
|
||||
1. **Always ask for root folder first** - Know where to create the project
|
||||
2. **Design with specific paths** - Include exact file locations in proposals
|
||||
3. **Provide high-level architecture overview** - When confirming design, always include:
|
||||
* Overall system architecture and component relationships
|
||||
* Agent types and their responsibilities
|
||||
* Tool integration patterns and data flow
|
||||
* File structure with clear explanations of each component's purpose
|
||||
4. **Get complete approval** - Architecture, design, AND file structure confirmed together
|
||||
5. **Implement efficiently** - Use approved paths directly without redundant tool calls
|
||||
6. **Focus on collaboration** - Ensure user gets exactly what they need with clear understanding
|
||||
|
||||
**This workflow eliminates inefficiencies and ensures users get well-organized, predictable file structures in their chosen location.**
|
||||
|
||||
## Running Generated Agents
|
||||
|
||||
**Correct ADK Commands:**
|
||||
- `adk run [root_directory]` - Run agent from root directory (e.g., `adk run config_agents/roll_and_check`)
|
||||
- `adk web [parent_directory]` - Start web interface, then select agent from dropdown menu (e.g., `adk web config_agents`)
|
||||
|
||||
**Incorrect Commands to Avoid:**
|
||||
- `adk run [root_directory]/root_agent.yaml` - Do NOT specify the YAML file directly
|
||||
- `adk web` without parent directory - Must specify the parent folder containing the agent projects
|
||||
- Always use the project directory for `adk run`, and parent directory for `adk web`
|
||||
@@ -23,7 +23,7 @@ from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
# Set up logger for ADK source utils
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("google_adk." + __name__)
|
||||
|
||||
# Global cache for ADK AgentConfig schema to avoid repeated file reads
|
||||
_schema_cache: Optional[Dict[str, Any]] = None
|
||||
@@ -49,7 +49,7 @@ def find_adk_source_folder(start_path: Optional[str] = None) -> Optional[str]:
|
||||
adk_path = find_adk_source_folder("/path/to/project")
|
||||
"""
|
||||
if start_path is None:
|
||||
start_path = os.getcwd()
|
||||
start_path = os.path.dirname(__file__)
|
||||
|
||||
current_path = Path(start_path).resolve()
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ def upload_directory_to_gcs(
|
||||
# into hidden directories.
|
||||
dirs[:] = [d for d in dirs if not d.startswith(".")]
|
||||
|
||||
# Keep only .md and .py files.
|
||||
files = [f for f in files if f.endswith(".md") or f.endswith(".py")]
|
||||
# Keep only .md, .py and .yaml files.
|
||||
files = [f for f in files if f.endswith((".md", ".py", ".yaml"))]
|
||||
|
||||
for filename in files:
|
||||
local_path = os.path.join(root, filename)
|
||||
@@ -99,6 +99,19 @@ def upload_directory_to_gcs(
|
||||
bucket.blob(gcs_path).upload_from_string(
|
||||
html_content, content_type=content_type
|
||||
)
|
||||
elif filename.lower().endswith(".yaml"):
|
||||
# Vertex AI search doesn't recognize yaml,
|
||||
# convert it to text and use text/plain instead
|
||||
content_type = "text/plain"
|
||||
with open(local_path, "r", encoding="utf-8") as f:
|
||||
yaml_content = f.read()
|
||||
if not yaml_content:
|
||||
print(" - Skipped empty file: " + local_path)
|
||||
continue
|
||||
gcs_path = gcs_path.removesuffix(".yaml") + ".txt"
|
||||
bucket.blob(gcs_path).upload_from_string(
|
||||
yaml_content, content_type=content_type
|
||||
)
|
||||
else: # Python files
|
||||
bucket.blob(gcs_path).upload_from_filename(
|
||||
local_path, content_type=content_type
|
||||
|
||||
@@ -115,6 +115,10 @@ def convert_gcs_to_https(gcs_uri: str) -> Optional[str]:
|
||||
if relative_path.endswith(".html"):
|
||||
relative_path = relative_path.removesuffix(".html") + ".md"
|
||||
|
||||
# Replace .txt with .yaml
|
||||
if relative_path.endswith(".txt"):
|
||||
relative_path = relative_path.removesuffix(".txt") + ".yaml"
|
||||
|
||||
# Convert the links for adk-docs
|
||||
if prefix == "adk-docs" and relative_path.startswith("docs/"):
|
||||
path_after_docs = relative_path[len("docs/") :]
|
||||
|
||||
@@ -100,13 +100,13 @@ root_agent = Agent(
|
||||
Explanation of why this change is necessary.
|
||||
|
||||
**Reference**:
|
||||
Reference to the code change (e.g. https://github.com/google/adk-python/commit/b3b70035c432670a5f0b5cdd1e9467f43b80495c).
|
||||
Reference to the code file (e.g. src/google/adk/tools/spanner/metadata_tool.py).
|
||||
```
|
||||
- When referncing doc file, use the full relative path of the doc file in the ADK Docs repository (e.g. docs/sessions/memory.md).
|
||||
9. Create or recommend to create a Github issue in the Github Repository {DOC_REPO} with the instructions using the `create_issue` tool.
|
||||
- The title of the issue should be "Found docs updates needed from ADK python release <start_tag> to <end_tag>", where start_tag and end_tag are the release tags.
|
||||
- The body of the issue should be the instructions about how to update the ADK docs.
|
||||
- Include the compare link between the two ADK releases in the issue body, e.g. https://github.com/google/adk-python/compare/v1.14.0...v1.14.1.
|
||||
- **{APPROVAL_INSTRUCTION}**
|
||||
|
||||
# 4. Guidelines & Rules
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
## ADK Authentication Demo (All in one - Agent, IDP and The app)
|
||||
|
||||
This folder contains everything you need to run the ADK's `auth-code`
|
||||
grant type authentication demo completely locally
|
||||
|
||||
Here's the high level diagram.
|
||||
|
||||

|
||||
|
||||
### Introduction
|
||||
More often than not the agents use some kind of system identity
|
||||
(especially for OpenAPI and MCP tools).
|
||||
But obviously this is insecure in that multiple end users
|
||||
are using the same identity with permissions to access ALL users' data on the
|
||||
backend.
|
||||
|
||||
ADK provides various [authentication mechanisms](https://google.github.io/adk-docs/tools/authentication/) to solve this.
|
||||
|
||||
However to properly test it you need various components.
|
||||
We provide everything that is needed so that you can test and run
|
||||
ADK authentication demo locally.
|
||||
|
||||
This folder comes with -
|
||||
|
||||
1. An IDP
|
||||
2. A hotel booking application backend
|
||||
3. A hotel assistant ADK agent (accessing the application using OpenAPI Tools)
|
||||
|
||||
### Details
|
||||
|
||||
You can read about the Auth Code grant / flow type in detail [here](https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type). But for the purpose of this demo, following steps take place
|
||||
|
||||
1. The user asks the agent to find hotels in "New York".
|
||||
2. Agent realizes (based on LLM response) that it needs to call a tool and that the tool needs authentication.
|
||||
3. Agent redirects the user to the IDP's login page with callback / redirect URL back to ADK UI.
|
||||
4. The user enters credentials (`john.doe` and `password123`) and accepts the consent.
|
||||
5. The IDP sends the auth_code back to the redirect URL (from 3).
|
||||
6. ADK then exchanges this auth_code for an access token.
|
||||
7. ADK does the API call to get details on hotels and hands over that response to LLM, LLM formats the response.
|
||||
8. ADK sends a response back to the User.
|
||||
|
||||
### Setting up and running
|
||||
|
||||
1. Clone this repository
|
||||
2. Carry out following steps and create and activate the environment
|
||||
```bash
|
||||
# Go to the cloned directory
|
||||
cd adk-python
|
||||
# Navigate to the all in one authentication sample
|
||||
cd contributing/samples/authn-adk-all-in-one/
|
||||
|
||||
python3 -m venv .venv
|
||||
|
||||
. .venv/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
```
|
||||
3. Configure and Start the IDP. Our IDP needs a private key to sign the tokens and a JWKS with public key component to verify them. Steps are provided for that (please check the screenshots below)
|
||||
|
||||
🪧 **NOTE:**
|
||||
It is recommended that you execute the key pair creation and public
|
||||
key extraction commands (1-3 and 5 below) on Google cloud shell.
|
||||
|
||||
```bash
|
||||
cd idp
|
||||
|
||||
# Create .env file by copying the existing one.
|
||||
cp sample.env .env
|
||||
cp sample.jwks.json jwks.json
|
||||
|
||||
|
||||
# Carry out following steps
|
||||
# 1. Generate a key pair, When asked about passphrase please press enter (empty passphrase)
|
||||
ssh-keygen -t rsa -b 2048 -m PEM -f private_key.pem
|
||||
|
||||
# 2. Extract the public key
|
||||
openssl rsa -in private_key.pem -pubout > pubkey.pub
|
||||
|
||||
# 3. Generate the jwks.json content using https://jwkset.com/generate and this public key (choose key algorithm RS256 and Key use Signature) (Please check the screenshot)
|
||||
# 4. Update the jwks.json with the key jwks key created in 3 (please check the screenshot)
|
||||
# 5. Update the env file with the private key
|
||||
cat private_key.pem | tr -d "\n"
|
||||
# 6. Carefully copy output of the command above into the .env file to update the value of PRIVATE_KEY
|
||||
# 7. save jwks.json and .env
|
||||
|
||||
# Start the IDP
|
||||
python app.py
|
||||
```
|
||||
<details>
|
||||
|
||||
<summary><b>Screenshots</b></summary>
|
||||
Generating JWKS -
|
||||
|
||||

|
||||
|
||||
Updated `jwks.json` (notice the key is added in the existing array)
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
4. In a separate shell - Start the backend API (Hotel Booking Application)
|
||||
```bash
|
||||
# Go to the cloned directory
|
||||
cd adk-python
|
||||
# Navigate to the all in one authentication sample
|
||||
cd contributing/samples/authn-adk-all-in-one/
|
||||
|
||||
# Activate Env for this shell
|
||||
. .venv/bin/activate
|
||||
|
||||
cd hotel_booker_app/
|
||||
|
||||
# Start the hotel booker application
|
||||
python main.py
|
||||
|
||||
```
|
||||
|
||||
5. In a separate shell - Start the ADK agent
|
||||
```bash
|
||||
# Go to the cloned directory
|
||||
cd adk-python
|
||||
# Navigate to the all in one authentication sample
|
||||
cd contributing/samples/authn-adk-all-in-one/
|
||||
|
||||
# Activate Env for this shell
|
||||
. .venv/bin/activate
|
||||
|
||||
cd adk_agents/
|
||||
|
||||
cp sample.env .env
|
||||
|
||||
# ⚠️ Make sure to update the API KEY (GOOGLE_API_KEY) in .env file
|
||||
|
||||
# Run the agent
|
||||
adk web
|
||||
|
||||
```
|
||||
6. Access the agent on http://localhost:8000
|
||||
|
||||
🪧 **NOTE:**
|
||||
|
||||
After first time authentication,
|
||||
it might take some time for the agent to respond,
|
||||
subsequent responses are significantly faster.
|
||||
|
||||
### Conclusion
|
||||
|
||||
You can exercise the ADK Authentication
|
||||
without any external components using this demo.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
|
||||
from . import agent
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from google.adk.tools.openapi_tool.auth.auth_helpers import openid_url_to_scheme_credential
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
|
||||
|
||||
credential_dict = {
|
||||
"client_id": os.environ.get("OAUTH_CLIENT_ID"),
|
||||
"client_secret": os.environ.get("OAUTH_CLIENT_SECRET"),
|
||||
}
|
||||
auth_scheme, auth_credential = openid_url_to_scheme_credential(
|
||||
openid_url="http://localhost:5000/.well-known/openid-configuration",
|
||||
credential_dict=credential_dict,
|
||||
scopes=[],
|
||||
)
|
||||
|
||||
|
||||
# Open API spec
|
||||
file_path = "./agent_openapi_tools/openapi.yaml"
|
||||
file_content = None
|
||||
|
||||
try:
|
||||
with open(file_path, "r") as file:
|
||||
file_content = file.read()
|
||||
except FileNotFoundError:
|
||||
# so that the execution does not continue when the file is not found.
|
||||
raise FileNotFoundError(f"Error: The API Spec '{file_path}' was not found.")
|
||||
|
||||
|
||||
# Example with a JSON string
|
||||
openapi_spec_yaml = file_content # Your OpenAPI YAML string
|
||||
openapi_toolset = OpenAPIToolset(
|
||||
spec_str=openapi_spec_yaml,
|
||||
spec_str_type="yaml",
|
||||
auth_scheme=auth_scheme,
|
||||
auth_credential=auth_credential,
|
||||
)
|
||||
|
||||
from google.adk.agents import LlmAgent
|
||||
|
||||
root_agent = LlmAgent(
|
||||
name="hotel_agent",
|
||||
instruction=(
|
||||
"Help user find and book hotels, fetch their bookings using the tools"
|
||||
" provided."
|
||||
),
|
||||
description="Hotel Booking Agent",
|
||||
model=os.environ.get("GOOGLE_MODEL"),
|
||||
tools=[openapi_toolset], # Pass the toolset
|
||||
# ... other agent config ...
|
||||
)
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Hotel Booker API
|
||||
description: A simple API for managing hotel bookings, with a custom client credentials authentication flow.
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: http://127.0.0.1:8081
|
||||
paths:
|
||||
/hotels:
|
||||
get:
|
||||
summary: Get available hotels
|
||||
description: Retrieves a list of available hotels, optionally filtered by location.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- in: query
|
||||
name: location
|
||||
schema:
|
||||
type: string
|
||||
description: The city to filter hotels by (e.g., 'New York').
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved hotels.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: false
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Hotel'
|
||||
message:
|
||||
type: string
|
||||
example: "Successfully retrieved hotels."
|
||||
'401':
|
||||
description: Unauthorized. Invalid or expired token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/book:
|
||||
post:
|
||||
summary: Book a room
|
||||
description: Books a room in a specified hotel.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BookingRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Booking successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: false
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
booking_id:
|
||||
type: string
|
||||
example: "HB-1"
|
||||
message:
|
||||
type: string
|
||||
example: "Booking successful!"
|
||||
'400':
|
||||
description: Bad request. Missing information or invalid booking details.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'401':
|
||||
description: Unauthorized. Invalid or expired token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/booking_details:
|
||||
get:
|
||||
summary: Get booking details
|
||||
description: Retrieves details for a specific booking by ID or guest name.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- in: query
|
||||
name: booking_id
|
||||
schema:
|
||||
type: string
|
||||
description: The custom booking ID (e.g., 'HB-1').
|
||||
- in: query
|
||||
name: guest_name
|
||||
schema:
|
||||
type: string
|
||||
description: The name of the guest to search for (partial and case-insensitive).
|
||||
responses:
|
||||
'200':
|
||||
description: Booking details retrieved successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: false
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
custom_booking_id:
|
||||
type: string
|
||||
example: "HB-1"
|
||||
hotel_name:
|
||||
type: string
|
||||
example: "Grand Hyatt"
|
||||
hotel_location:
|
||||
type: string
|
||||
example: "New York"
|
||||
guest_name:
|
||||
type: string
|
||||
example: "John Doe"
|
||||
check_in_date:
|
||||
type: string
|
||||
example: "2025-10-01"
|
||||
check_out_date:
|
||||
type: string
|
||||
example: "2025-10-05"
|
||||
num_rooms:
|
||||
type: integer
|
||||
example: 1
|
||||
total_price:
|
||||
type: number
|
||||
format: float
|
||||
example: 1000.0
|
||||
message:
|
||||
type: string
|
||||
example: "Booking details retrieved successfully."
|
||||
'400':
|
||||
description: Bad request. Missing parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'401':
|
||||
description: Unauthorized. Invalid or expired token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'404':
|
||||
description: Booking not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: CustomAuthToken
|
||||
schemas:
|
||||
ErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: true
|
||||
data:
|
||||
type: object
|
||||
nullable: true
|
||||
message:
|
||||
type: string
|
||||
example: "Invalid access token."
|
||||
Hotel:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
name:
|
||||
type: string
|
||||
example: "Grand Hyatt"
|
||||
location:
|
||||
type: string
|
||||
example: "New York"
|
||||
available_rooms:
|
||||
type: integer
|
||||
example: 10
|
||||
price_per_night:
|
||||
type: number
|
||||
format: float
|
||||
example: 250.0
|
||||
BookingRequest:
|
||||
type: object
|
||||
properties:
|
||||
hotel_id:
|
||||
type: integer
|
||||
example: 1
|
||||
guest_name:
|
||||
type: string
|
||||
example: "John Doe"
|
||||
check_in_date:
|
||||
type: string
|
||||
format: date
|
||||
example: "2025-10-01"
|
||||
check_out_date:
|
||||
type: string
|
||||
format: date
|
||||
example: "2025-10-05"
|
||||
num_rooms:
|
||||
type: integer
|
||||
example: 1
|
||||
required:
|
||||
- hotel_id
|
||||
- guest_name
|
||||
- check_in_date
|
||||
- check_out_date
|
||||
- num_rooms
|
||||
@@ -0,0 +1 @@
|
||||
google-adk==1.12
|
||||
@@ -0,0 +1,6 @@
|
||||
# General Agent Configuration
|
||||
GOOGLE_GENAI_USE_VERTEXAI=False
|
||||
GOOGLE_API_KEY=NOT_SET
|
||||
GOOGLE_MODEL=gemini-2.5-flash
|
||||
OAUTH_CLIENT_ID=abc123
|
||||
OAUTH_CLIENT_SECRET=secret123
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 746 KiB |
@@ -0,0 +1,263 @@
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import sqlite3
|
||||
|
||||
|
||||
class HotelBooker:
|
||||
"""
|
||||
Core business logic for hotel booking, independent of any web framework.
|
||||
"""
|
||||
|
||||
def __init__(self, db_name="data.db"):
|
||||
self.db_name = db_name
|
||||
self._initialize_db()
|
||||
|
||||
def _get_db_connection(self):
|
||||
"""Helper to get a new, independent database connection."""
|
||||
conn = sqlite3.connect(self.db_name)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def _initialize_db(self):
|
||||
"""
|
||||
Drops, creates, and populates the database tables with sample data.
|
||||
"""
|
||||
conn = None
|
||||
try:
|
||||
conn = self._get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("DROP TABLE IF EXISTS bookings")
|
||||
cursor.execute("DROP TABLE IF EXISTS hotels")
|
||||
conn.commit()
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS hotels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
location TEXT NOT NULL,
|
||||
total_rooms INTEGER NOT NULL,
|
||||
available_rooms INTEGER NOT NULL,
|
||||
price_per_night REAL NOT NULL
|
||||
)
|
||||
""")
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS bookings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
custom_booking_id TEXT UNIQUE,
|
||||
hotel_id INTEGER NOT NULL,
|
||||
guest_name TEXT NOT NULL,
|
||||
check_in_date TEXT NOT NULL,
|
||||
check_out_date TEXT NOT NULL,
|
||||
num_rooms INTEGER NOT NULL,
|
||||
total_price REAL NOT NULL,
|
||||
FOREIGN KEY (hotel_id) REFERENCES hotels(id)
|
||||
)
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
|
||||
sample_hotels = [
|
||||
("Grand Hyatt", "New York", 200, 150, 250.00),
|
||||
("The Plaza Hotel", "New York", 150, 100, 350.00),
|
||||
("Hilton Chicago", "Chicago", 300, 250, 180.00),
|
||||
("Marriott Marquis", "San Francisco", 250, 200, 220.00),
|
||||
]
|
||||
cursor.executemany(
|
||||
"""
|
||||
INSERT INTO hotels (name, location, total_rooms, available_rooms, price_per_night)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
sample_hotels,
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
initial_bookings_data = [
|
||||
(1, "Alice Smith", "2025-08-10", "2025-08-15", 1, 1250.00),
|
||||
(3, "Bob Johnson", "2025-09-01", "2025-09-03", 2, 720.00),
|
||||
]
|
||||
for booking_data in initial_bookings_data:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO bookings (hotel_id, guest_name, check_in_date, check_out_date, num_rooms, total_price)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
booking_data,
|
||||
)
|
||||
booking_id_int = cursor.lastrowid
|
||||
custom_id = f"HB-{booking_id_int}"
|
||||
cursor.execute(
|
||||
"UPDATE bookings SET custom_booking_id = ? WHERE id = ?",
|
||||
(custom_id, booking_id_int),
|
||||
)
|
||||
conn.commit()
|
||||
except sqlite3.Error as e:
|
||||
if conn:
|
||||
conn.rollback()
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def is_token_valid(self, conn, token):
|
||||
"""Checks if a given token is valid and not expired."""
|
||||
logging.info("not implemented")
|
||||
return True
|
||||
|
||||
def get_available_hotels(self, cursor, location=None):
|
||||
"""Retrieves a list of available hotels, optionally filtered by location."""
|
||||
query = (
|
||||
"SELECT id, name, location, available_rooms, price_per_night FROM"
|
||||
" hotels WHERE available_rooms > 0"
|
||||
)
|
||||
params = []
|
||||
if location:
|
||||
query += " AND location LIKE ?"
|
||||
params.append(f"%{location}%")
|
||||
try:
|
||||
cursor.execute(query, params)
|
||||
rows = cursor.fetchall()
|
||||
return [dict(row) for row in rows], None
|
||||
except sqlite3.Error as e:
|
||||
return None, f"Error getting available hotels: {e}"
|
||||
|
||||
def book_a_room(
|
||||
self, conn, hotel_id, guest_name, check_in_date, check_out_date, num_rooms
|
||||
):
|
||||
"""Books a room in a specified hotel."""
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
"SELECT available_rooms, price_per_night FROM hotels WHERE id = ?",
|
||||
(hotel_id,),
|
||||
)
|
||||
hotel_info = cursor.fetchone()
|
||||
|
||||
if not hotel_info:
|
||||
return None, f"Hotel with ID {hotel_id} not found."
|
||||
|
||||
available_rooms, price_per_night = (
|
||||
hotel_info["available_rooms"],
|
||||
hotel_info["price_per_night"],
|
||||
)
|
||||
if available_rooms < num_rooms:
|
||||
return (
|
||||
None,
|
||||
(
|
||||
f"Not enough rooms available at hotel ID {hotel_id}. Available:"
|
||||
f" {available_rooms}, Requested: {num_rooms}"
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
check_in_dt = datetime.datetime.strptime(check_in_date, "%Y-%m-%d")
|
||||
check_out_dt = datetime.datetime.strptime(check_out_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return None, "Invalid date format. Please use YYYY-MM-DD."
|
||||
|
||||
num_nights = (check_out_dt - check_in_dt).days
|
||||
if num_nights <= 0:
|
||||
return None, "Check-out date must be after check-in date."
|
||||
|
||||
total_price = num_rooms * price_per_night * num_nights
|
||||
|
||||
cursor.execute(
|
||||
"UPDATE hotels SET available_rooms = ? WHERE id = ?",
|
||||
(available_rooms - num_rooms, hotel_id),
|
||||
)
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO bookings (hotel_id, guest_name, check_in_date, check_out_date, num_rooms, total_price)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
hotel_id,
|
||||
guest_name,
|
||||
check_in_date,
|
||||
check_out_date,
|
||||
num_rooms,
|
||||
total_price,
|
||||
),
|
||||
)
|
||||
|
||||
booking_id_int = cursor.lastrowid
|
||||
custom_booking_id = f"HB-{booking_id_int}"
|
||||
|
||||
cursor.execute(
|
||||
"UPDATE bookings SET custom_booking_id = ? WHERE id = ?",
|
||||
(custom_booking_id, booking_id_int),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
return custom_booking_id, None
|
||||
except sqlite3.Error as e:
|
||||
conn.rollback()
|
||||
return None, f"Error booking room: {e}"
|
||||
|
||||
def get_booking_details(self, cursor, booking_id=None, guest_name=None):
|
||||
"""Retrieves details for a specific booking."""
|
||||
query = """
|
||||
SELECT
|
||||
b.custom_booking_id,
|
||||
h.name AS hotel_name,
|
||||
h.location AS hotel_location,
|
||||
b.guest_name,
|
||||
b.check_in_date,
|
||||
b.check_out_date,
|
||||
b.num_rooms,
|
||||
b.total_price
|
||||
FROM
|
||||
bookings b
|
||||
JOIN
|
||||
hotels h ON b.hotel_id = h.id
|
||||
"""
|
||||
params = []
|
||||
result_type = "single"
|
||||
|
||||
if booking_id:
|
||||
query += " WHERE b.custom_booking_id = ?"
|
||||
params.append(booking_id)
|
||||
elif guest_name:
|
||||
query += " WHERE LOWER(b.guest_name) LIKE LOWER(?)"
|
||||
params.append(f"%{guest_name}%")
|
||||
result_type = "list"
|
||||
else:
|
||||
return (
|
||||
None,
|
||||
(
|
||||
"Please provide either a booking ID or a guest name to retrieve"
|
||||
" booking details."
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
cursor.execute(query, params)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
if not rows:
|
||||
return (
|
||||
None,
|
||||
(
|
||||
f"No booking found for the given criteria (ID: {booking_id},"
|
||||
f" Name: {guest_name})."
|
||||
),
|
||||
)
|
||||
|
||||
bookings = [dict(row) for row in rows]
|
||||
return bookings if result_type == "list" else bookings[0], None
|
||||
except sqlite3.Error as e:
|
||||
return None, f"Error getting booking details: {e}"
|
||||
@@ -0,0 +1,266 @@
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
|
||||
from functools import wraps
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask
|
||||
from flask import g
|
||||
from flask import jsonify
|
||||
from flask import request
|
||||
from hotelbooker_core import HotelBooker
|
||||
import jwt
|
||||
import requests
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
# Instantiate the core logic class
|
||||
hotel_booker = HotelBooker()
|
||||
app.config["DATABASE"] = hotel_booker.db_name
|
||||
|
||||
OIDC_CONFIG_URL = os.environ.get(
|
||||
"OIDC_CONFIG_URL", "http://localhost:5000/.well-known/openid-configuration"
|
||||
)
|
||||
|
||||
# Cache for OIDC discovery and JWKS
|
||||
oidc_config = None
|
||||
jwks = None
|
||||
|
||||
|
||||
def get_oidc_config():
|
||||
"""Fetches and caches the OIDC configuration."""
|
||||
global oidc_config
|
||||
if oidc_config is None:
|
||||
try:
|
||||
response = requests.get(OIDC_CONFIG_URL)
|
||||
response.raise_for_status()
|
||||
oidc_config = response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return None, f"Error fetching OIDC config: {e}"
|
||||
return oidc_config, None
|
||||
|
||||
|
||||
def get_jwks():
|
||||
"""Fetches and caches the JSON Web Key Set (JWKS)."""
|
||||
global jwks
|
||||
if jwks is None:
|
||||
config, error = get_oidc_config()
|
||||
if error:
|
||||
return None, error
|
||||
jwks_uri = config.get("jwks_uri")
|
||||
if not jwks_uri:
|
||||
return None, "jwks_uri not found in OIDC configuration."
|
||||
try:
|
||||
response = requests.get(jwks_uri)
|
||||
response.raise_for_status()
|
||||
jwks = response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
return None, f"Error fetching JWKS: {e}"
|
||||
return jwks, None
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Manages a per-request database connection."""
|
||||
if "db" not in g:
|
||||
g.db = sqlite3.connect(app.config["DATABASE"])
|
||||
g.db.row_factory = sqlite3.Row
|
||||
return g.db
|
||||
|
||||
|
||||
@app.teardown_appcontext
|
||||
def close_db(exception):
|
||||
db = g.pop("db", None)
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
|
||||
def is_token_valid(token: str):
|
||||
"""
|
||||
Validates a JWT token using the public key from the OIDC jwks_uri.
|
||||
"""
|
||||
if not token:
|
||||
return False, "Token is empty."
|
||||
|
||||
jwks_data, error = get_jwks()
|
||||
if error:
|
||||
return False, f"Failed to get JWKS: {error}"
|
||||
|
||||
try:
|
||||
header = jwt.get_unverified_header(token)
|
||||
kid = header.get("kid")
|
||||
if not kid:
|
||||
return False, "Token header missing 'kid'."
|
||||
|
||||
key = next(
|
||||
(k for k in jwks_data.get("keys", []) if k.get("kid") == kid), None
|
||||
)
|
||||
if not key:
|
||||
return False, "No matching key found in JWKS."
|
||||
|
||||
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
|
||||
|
||||
# The decoding happens just so that we are able to
|
||||
# check if there were any exception decoding the token
|
||||
# which indicate it being not valid.
|
||||
# Also you could have verify_aud and verify_iss as False
|
||||
# But when they are true issuer and audience are needed in the jwt.decode call
|
||||
# they are checked against the values from the token
|
||||
# idealy token validation should also check whether the API being called is part of
|
||||
# audience so for example localhost:8081/api should cover localhost:8081/api/hotels
|
||||
# but should not cover localhost:8000/admin
|
||||
# so this middleware (decorator - is_token_valid, can check the request url and do that check, but we are
|
||||
# skipping that as the audience will always be localhost:8081)
|
||||
decoded_token = jwt.decode(
|
||||
token,
|
||||
key=public_key,
|
||||
issuer="http://localhost:5000",
|
||||
audience="http://localhost:8081",
|
||||
algorithms=[header["alg"]],
|
||||
options={"verify_exp": True, "verify_aud": True, "verify_iss": True},
|
||||
)
|
||||
return True, "Token is valid."
|
||||
except jwt.ExpiredSignatureError:
|
||||
return False, "Token has expired."
|
||||
except jwt.InvalidAudienceError:
|
||||
return False, "Invalid audience."
|
||||
except jwt.InvalidIssuerError:
|
||||
return False, "Invalid issuer."
|
||||
except jwt.InvalidTokenError as e:
|
||||
return False, f"Invalid token: {e}"
|
||||
except Exception as e:
|
||||
return False, f"An unexpected error occurred during token validation: {e}"
|
||||
|
||||
|
||||
# Decorator to check for a valid access token on protected routes
|
||||
def token_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
auth_header = request.headers.get("Authorization")
|
||||
if not auth_header or not auth_header.startswith("Bearer "):
|
||||
return {
|
||||
"error": True,
|
||||
"data": None,
|
||||
"message": "Missing or invalid Authorization header.",
|
||||
}, 401
|
||||
|
||||
token = auth_header.split(" ")[1]
|
||||
is_valid, message = is_token_valid(token)
|
||||
|
||||
if not is_valid:
|
||||
return {"error": True, "data": None, "message": message}, 401
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
@app.route("/hotels", methods=["GET"])
|
||||
@token_required
|
||||
def get_hotels():
|
||||
location = request.args.get("location")
|
||||
hotels, error_message = hotel_booker.get_available_hotels(
|
||||
get_db().cursor(), location
|
||||
)
|
||||
|
||||
if hotels is not None:
|
||||
return (
|
||||
jsonify({
|
||||
"error": False,
|
||||
"data": hotels,
|
||||
"message": "Successfully retrieved hotels.",
|
||||
}),
|
||||
200,
|
||||
)
|
||||
else:
|
||||
return jsonify({"error": True, "data": None, "message": error_message}), 500
|
||||
|
||||
|
||||
@app.route("/book", methods=["POST"])
|
||||
@token_required
|
||||
def book_room():
|
||||
conn = get_db()
|
||||
data = request.json
|
||||
hotel_id = data.get("hotel_id")
|
||||
guest_name = data.get("guest_name")
|
||||
check_in_date = data.get("check_in_date")
|
||||
check_out_date = data.get("check_out_date")
|
||||
num_rooms = data.get("num_rooms")
|
||||
|
||||
if not all([hotel_id, guest_name, check_in_date, check_out_date, num_rooms]):
|
||||
return (
|
||||
jsonify({
|
||||
"error": True,
|
||||
"data": None,
|
||||
"message": "Missing required booking information.",
|
||||
}),
|
||||
400,
|
||||
)
|
||||
|
||||
booking_id, error_message = hotel_booker.book_a_room(
|
||||
conn, hotel_id, guest_name, check_in_date, check_out_date, num_rooms
|
||||
)
|
||||
|
||||
if booking_id:
|
||||
return (
|
||||
jsonify({
|
||||
"error": False,
|
||||
"data": {"booking_id": booking_id},
|
||||
"message": "Booking successful!",
|
||||
}),
|
||||
200,
|
||||
)
|
||||
else:
|
||||
return jsonify({"error": True, "data": None, "message": error_message}), 400
|
||||
|
||||
|
||||
@app.route("/booking_details", methods=["GET"])
|
||||
@token_required
|
||||
def get_details():
|
||||
conn = get_db()
|
||||
booking_id = request.args.get("booking_id")
|
||||
guest_name = request.args.get("guest_name")
|
||||
|
||||
if not booking_id and not guest_name:
|
||||
return (
|
||||
jsonify({
|
||||
"error": True,
|
||||
"data": None,
|
||||
"message": "Please provide either a booking ID or a guest name.",
|
||||
}),
|
||||
400,
|
||||
)
|
||||
|
||||
details, error_message = hotel_booker.get_booking_details(
|
||||
get_db().cursor(), booking_id=booking_id, guest_name=guest_name
|
||||
)
|
||||
|
||||
if details:
|
||||
return (
|
||||
jsonify({
|
||||
"error": False,
|
||||
"data": details,
|
||||
"message": "Booking details retrieved successfully.",
|
||||
}),
|
||||
200,
|
||||
)
|
||||
else:
|
||||
return jsonify({"error": True, "data": None, "message": error_message}), 404
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, port=8081)
|
||||
@@ -0,0 +1,229 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Hotel Booker API
|
||||
description: A simple API for managing hotel bookings, with a custom client credentials authentication flow.
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: http://127.0.0.1:8081
|
||||
paths:
|
||||
/hotels:
|
||||
get:
|
||||
summary: Get available hotels
|
||||
description: Retrieves a list of available hotels, optionally filtered by location.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- in: query
|
||||
name: location
|
||||
schema:
|
||||
type: string
|
||||
description: The city to filter hotels by (e.g., 'New York').
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved hotels.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: false
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Hotel'
|
||||
message:
|
||||
type: string
|
||||
example: "Successfully retrieved hotels."
|
||||
'401':
|
||||
description: Unauthorized. Invalid or expired token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/book:
|
||||
post:
|
||||
summary: Book a room
|
||||
description: Books a room in a specified hotel.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BookingRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Booking successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: false
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
booking_id:
|
||||
type: string
|
||||
example: "HB-1"
|
||||
message:
|
||||
type: string
|
||||
example: "Booking successful!"
|
||||
'400':
|
||||
description: Bad request. Missing information or invalid booking details.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'401':
|
||||
description: Unauthorized. Invalid or expired token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/booking_details:
|
||||
get:
|
||||
summary: Get booking details
|
||||
description: Retrieves details for a specific booking by ID or guest name.
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- in: query
|
||||
name: booking_id
|
||||
schema:
|
||||
type: string
|
||||
description: The custom booking ID (e.g., 'HB-1').
|
||||
- in: query
|
||||
name: guest_name
|
||||
schema:
|
||||
type: string
|
||||
description: The name of the guest to search for (partial and case-insensitive).
|
||||
responses:
|
||||
'200':
|
||||
description: Booking details retrieved successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: false
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
custom_booking_id:
|
||||
type: string
|
||||
example: "HB-1"
|
||||
hotel_name:
|
||||
type: string
|
||||
example: "Grand Hyatt"
|
||||
hotel_location:
|
||||
type: string
|
||||
example: "New York"
|
||||
guest_name:
|
||||
type: string
|
||||
example: "John Doe"
|
||||
check_in_date:
|
||||
type: string
|
||||
example: "2025-10-01"
|
||||
check_out_date:
|
||||
type: string
|
||||
example: "2025-10-05"
|
||||
num_rooms:
|
||||
type: integer
|
||||
example: 1
|
||||
total_price:
|
||||
type: number
|
||||
format: float
|
||||
example: 1000.0
|
||||
message:
|
||||
type: string
|
||||
example: "Booking details retrieved successfully."
|
||||
'400':
|
||||
description: Bad request. Missing parameters.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'401':
|
||||
description: Unauthorized. Invalid or expired token.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'404':
|
||||
description: Booking not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: CustomAuthToken
|
||||
schemas:
|
||||
ErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: boolean
|
||||
example: true
|
||||
data:
|
||||
type: object
|
||||
nullable: true
|
||||
message:
|
||||
type: string
|
||||
example: "Invalid access token."
|
||||
Hotel:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
name:
|
||||
type: string
|
||||
example: "Grand Hyatt"
|
||||
location:
|
||||
type: string
|
||||
example: "New York"
|
||||
available_rooms:
|
||||
type: integer
|
||||
example: 10
|
||||
price_per_night:
|
||||
type: number
|
||||
format: float
|
||||
example: 250.0
|
||||
BookingRequest:
|
||||
type: object
|
||||
properties:
|
||||
hotel_id:
|
||||
type: integer
|
||||
example: 1
|
||||
guest_name:
|
||||
type: string
|
||||
example: "John Doe"
|
||||
check_in_date:
|
||||
type: string
|
||||
format: date
|
||||
example: "2025-10-01"
|
||||
check_out_date:
|
||||
type: string
|
||||
format: date
|
||||
example: "2025-10-05"
|
||||
num_rooms:
|
||||
type: integer
|
||||
example: 1
|
||||
required:
|
||||
- hotel_id
|
||||
- guest_name
|
||||
- check_in_date
|
||||
- check_out_date
|
||||
- num_rooms
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user