mirror of
https://github.com/EvolutionAPI/adk-python.git
synced 2026-02-05 06:16:24 -06:00
Agent Development Kit(ADK)
An easy-to-use and powerful framework to build AI agents.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# 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 common
|
||||
|
||||
__all__ = [
|
||||
'common',
|
||||
]
|
||||
@@ -0,0 +1,300 @@
|
||||
# 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 keyword
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from fastapi.openapi.models import Response
|
||||
from fastapi.openapi.models import Schema
|
||||
from pydantic import BaseModel
|
||||
from pydantic import Field
|
||||
from pydantic import model_serializer
|
||||
|
||||
|
||||
def to_snake_case(text: str) -> str:
|
||||
"""Converts a string into snake_case.
|
||||
|
||||
Handles lowerCamelCase, UpperCamelCase, or space-separated case, acronyms
|
||||
(e.g., "REST API") and consecutive uppercase letters correctly. Also handles
|
||||
mixed cases with and without spaces.
|
||||
|
||||
Examples:
|
||||
```
|
||||
to_snake_case('camelCase') -> 'camel_case'
|
||||
to_snake_case('UpperCamelCase') -> 'upper_camel_case'
|
||||
to_snake_case('space separated') -> 'space_separated'
|
||||
```
|
||||
|
||||
Args:
|
||||
text: The input string.
|
||||
|
||||
Returns:
|
||||
The snake_case version of the string.
|
||||
"""
|
||||
|
||||
# Handle spaces and non-alphanumeric characters (replace with underscores)
|
||||
text = re.sub(r'[^a-zA-Z0-9]+', '_', text)
|
||||
|
||||
# Insert underscores before uppercase letters (handling both CamelCases)
|
||||
text = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', text) # lowerCamelCase
|
||||
text = re.sub(
|
||||
r'([A-Z]+)([A-Z][a-z])', r'\1_\2', text
|
||||
) # UpperCamelCase and acronyms
|
||||
|
||||
# Convert to lowercase
|
||||
text = text.lower()
|
||||
|
||||
# Remove consecutive underscores (clean up extra underscores)
|
||||
text = re.sub(r'_+', '_', text)
|
||||
|
||||
# Remove leading and trailing underscores
|
||||
text = text.strip('_')
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def rename_python_keywords(s: str, prefix: str = 'param_') -> str:
|
||||
"""Renames Python keywords by adding a prefix.
|
||||
|
||||
Example:
|
||||
```
|
||||
rename_python_keywords('if') -> 'param_if'
|
||||
rename_python_keywords('for') -> 'param_for'
|
||||
```
|
||||
|
||||
Args:
|
||||
s: The input string.
|
||||
prefix: The prefix to add to the keyword.
|
||||
|
||||
Returns:
|
||||
The renamed string.
|
||||
"""
|
||||
if keyword.iskeyword(s):
|
||||
return prefix + s
|
||||
return s
|
||||
|
||||
|
||||
class ApiParameter(BaseModel):
|
||||
"""Data class representing a function parameter."""
|
||||
|
||||
original_name: str
|
||||
param_location: str
|
||||
param_schema: Union[str, Schema]
|
||||
description: Optional[str] = ''
|
||||
py_name: Optional[str] = ''
|
||||
type_value: type[Any] = Field(default=None, init_var=False)
|
||||
type_hint: str = Field(default=None, init_var=False)
|
||||
|
||||
def model_post_init(self, _: Any):
|
||||
self.py_name = (
|
||||
self.py_name
|
||||
if self.py_name
|
||||
else rename_python_keywords(to_snake_case(self.original_name))
|
||||
)
|
||||
if isinstance(self.param_schema, str):
|
||||
self.param_schema = Schema.model_validate_json(self.param_schema)
|
||||
|
||||
self.description = self.description or self.param_schema.description or ''
|
||||
self.type_value = TypeHintHelper.get_type_value(self.param_schema)
|
||||
self.type_hint = TypeHintHelper.get_type_hint(self.param_schema)
|
||||
return self
|
||||
|
||||
@model_serializer
|
||||
def _serialize(self):
|
||||
return {
|
||||
'original_name': self.original_name,
|
||||
'param_location': self.param_location,
|
||||
'param_schema': self.param_schema,
|
||||
'description': self.description,
|
||||
'py_name': self.py_name,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.py_name}: {self.type_hint}'
|
||||
|
||||
def to_arg_string(self):
|
||||
"""Converts the parameter to an argument string for function call."""
|
||||
return f'{self.py_name}={self.py_name}'
|
||||
|
||||
def to_dict_property(self):
|
||||
"""Converts the parameter to a key:value string for dict property."""
|
||||
return f'"{self.py_name}": {self.py_name}'
|
||||
|
||||
def to_pydoc_string(self):
|
||||
"""Converts the parameter to a PyDoc parameter docstr."""
|
||||
return PydocHelper.generate_param_doc(self)
|
||||
|
||||
|
||||
class TypeHintHelper:
|
||||
"""Helper class for generating type hints."""
|
||||
|
||||
@staticmethod
|
||||
def get_type_value(schema: Schema) -> Any:
|
||||
"""Generates the Python type value for a given parameter."""
|
||||
param_type = schema.type if schema.type else Any
|
||||
|
||||
if param_type == 'integer':
|
||||
return int
|
||||
elif param_type == 'number':
|
||||
return float
|
||||
elif param_type == 'boolean':
|
||||
return bool
|
||||
elif param_type == 'string':
|
||||
return str
|
||||
elif param_type == 'array':
|
||||
items_type = Any
|
||||
if schema.items and schema.items.type:
|
||||
items_type = schema.items.type
|
||||
|
||||
if items_type == 'object':
|
||||
return List[Dict[str, Any]]
|
||||
else:
|
||||
type_map = {
|
||||
'integer': int,
|
||||
'number': float,
|
||||
'boolean': bool,
|
||||
'string': str,
|
||||
'object': Dict[str, Any],
|
||||
'array': List[Any],
|
||||
}
|
||||
return List[type_map.get(items_type, 'Any')]
|
||||
elif param_type == 'object':
|
||||
return Dict[str, Any]
|
||||
else:
|
||||
return Any
|
||||
|
||||
@staticmethod
|
||||
def get_type_hint(schema: Schema) -> str:
|
||||
"""Generates the Python type in string for a given parameter."""
|
||||
param_type = schema.type if schema.type else 'Any'
|
||||
|
||||
if param_type == 'integer':
|
||||
return 'int'
|
||||
elif param_type == 'number':
|
||||
return 'float'
|
||||
elif param_type == 'boolean':
|
||||
return 'bool'
|
||||
elif param_type == 'string':
|
||||
return 'str'
|
||||
elif param_type == 'array':
|
||||
items_type = 'Any'
|
||||
if schema.items and schema.items.type:
|
||||
items_type = schema.items.type
|
||||
|
||||
if items_type == 'object':
|
||||
return 'List[Dict[str, Any]]'
|
||||
else:
|
||||
type_map = {
|
||||
'integer': 'int',
|
||||
'number': 'float',
|
||||
'boolean': 'bool',
|
||||
'string': 'str',
|
||||
}
|
||||
return f"List[{type_map.get(items_type, 'Any')}]"
|
||||
elif param_type == 'object':
|
||||
return 'Dict[str, Any]'
|
||||
else:
|
||||
return 'Any'
|
||||
|
||||
|
||||
class PydocHelper:
|
||||
"""Helper class for generating PyDoc strings."""
|
||||
|
||||
@staticmethod
|
||||
def generate_param_doc(
|
||||
param: ApiParameter,
|
||||
) -> str:
|
||||
"""Generates a parameter documentation string.
|
||||
|
||||
Args:
|
||||
param: ApiParameter - The parameter to generate the documentation for.
|
||||
|
||||
Returns:
|
||||
str: The generated parameter Python documentation string.
|
||||
"""
|
||||
description = param.description.strip() if param.description else ''
|
||||
param_doc = f'{param.py_name} ({param.type_hint}): {description}'
|
||||
|
||||
if param.param_schema.type == 'object':
|
||||
properties = param.param_schema.properties
|
||||
if properties:
|
||||
param_doc += ' Object properties:\n'
|
||||
for prop_name, prop_details in properties.items():
|
||||
prop_desc = prop_details.description or ''
|
||||
prop_type = TypeHintHelper.get_type_hint(prop_details)
|
||||
param_doc += f' {prop_name} ({prop_type}): {prop_desc}\n'
|
||||
|
||||
return param_doc
|
||||
|
||||
@staticmethod
|
||||
def generate_return_doc(responses: Dict[str, Response]) -> str:
|
||||
"""Generates a return value documentation string.
|
||||
|
||||
Args:
|
||||
responses: Dict[str, TypedDict[Response]] - Response in an OpenAPI
|
||||
Operation
|
||||
|
||||
Returns:
|
||||
str: The generated return value Python documentation string.
|
||||
"""
|
||||
return_doc = ''
|
||||
|
||||
# Only consider 2xx responses for return type hinting.
|
||||
# Returns the 2xx response with the smallest status code number and with
|
||||
# content defined.
|
||||
sorted_responses = sorted(responses.items(), key=lambda item: int(item[0]))
|
||||
qualified_response = next(
|
||||
filter(
|
||||
lambda r: r[0].startswith('2') and r[1].content,
|
||||
sorted_responses,
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not qualified_response:
|
||||
return ''
|
||||
response_details = qualified_response[1]
|
||||
|
||||
description = (response_details.description or '').strip()
|
||||
content = response_details.content or {}
|
||||
|
||||
# Generate return type hint and properties for the first response type.
|
||||
# TODO(cheliu): Handle multiple content types.
|
||||
for _, schema_details in content.items():
|
||||
schema = schema_details.schema_ or {}
|
||||
|
||||
# Use a dummy Parameter object for return type hinting.
|
||||
dummy_param = ApiParameter(
|
||||
original_name='', param_location='', param_schema=schema
|
||||
)
|
||||
return_doc = f'Returns ({dummy_param.type_hint}): {description}'
|
||||
|
||||
response_type = schema.type or 'Any'
|
||||
if response_type != 'object':
|
||||
break
|
||||
properties = schema.properties
|
||||
if not properties:
|
||||
break
|
||||
return_doc += ' Object properties:\n'
|
||||
for prop_name, prop_details in properties.items():
|
||||
prop_desc = prop_details.description or ''
|
||||
prop_type = TypeHintHelper.get_type_hint(prop_details)
|
||||
return_doc += f' {prop_name} ({prop_type}): {prop_desc}\n'
|
||||
break
|
||||
|
||||
return return_doc
|
||||
Reference in New Issue
Block a user