|
15 | 15 | """Agent factory for creating Agent Builder Assistant with embedded schema.""" |
16 | 16 |
|
17 | 17 | from pathlib import Path |
| 18 | +import textwrap |
| 19 | +from typing import Any |
18 | 20 | from typing import Callable |
19 | 21 | from typing import Optional |
20 | 22 | from typing import Union |
|
43 | 45 | class AgentBuilderAssistant: |
44 | 46 | """Agent Builder Assistant factory for creating configured instances.""" |
45 | 47 |
|
| 48 | + _CORE_SCHEMA_DEF_NAMES: tuple[str, ...] = ( |
| 49 | + "LlmAgentConfig", |
| 50 | + "LoopAgentConfig", |
| 51 | + "ParallelAgentConfig", |
| 52 | + "SequentialAgentConfig", |
| 53 | + "BaseAgentConfig", |
| 54 | + "AgentRefConfig", |
| 55 | + "CodeConfig", |
| 56 | + "ArgumentConfig", |
| 57 | + "ToolArgsConfig", |
| 58 | + "google__adk__tools__tool_configs__ToolConfig", |
| 59 | + ) |
| 60 | + _GEN_CONFIG_FIELDS: tuple[str, ...] = ( |
| 61 | + "temperature", |
| 62 | + "topP", |
| 63 | + "topK", |
| 64 | + "maxOutputTokens", |
| 65 | + ) |
| 66 | + |
46 | 67 | @staticmethod |
47 | 68 | def create_agent( |
48 | | - model: Union[str, BaseLlm] = "gemini-2.5-flash", |
| 69 | + model: Union[str, BaseLlm] = "gemini-2.5-pro", |
49 | 70 | working_directory: Optional[str] = None, |
50 | 71 | ) -> LlmAgent: |
51 | 72 | """Create Agent Builder Assistant with embedded ADK AgentConfig schema. |
@@ -125,28 +146,208 @@ def create_agent( |
125 | 146 | def _load_schema() -> str: |
126 | 147 | """Load ADK AgentConfig.json schema content and format for YAML embedding.""" |
127 | 148 |
|
128 | | - # CENTRALIZED ADK AGENTCONFIG SCHEMA LOADING: Use common utility function |
129 | | - # This avoids duplication across multiple files and provides consistent |
130 | | - # ADK AgentConfig schema loading with caching and error handling. |
131 | | - schema_content = load_agent_config_schema( |
132 | | - raw_format=True, # Get as JSON string |
| 149 | + schema_dict = load_agent_config_schema(raw_format=False) |
| 150 | + subset = AgentBuilderAssistant._extract_core_schema(schema_dict) |
| 151 | + return AgentBuilderAssistant._build_schema_reference(subset) |
| 152 | + |
| 153 | + @staticmethod |
| 154 | + def _build_schema_reference(schema: dict[str, Any]) -> str: |
| 155 | + """Create compact AgentConfig reference text for prompt embedding.""" |
| 156 | + |
| 157 | + defs: dict[str, Any] = schema.get("$defs", {}) |
| 158 | + top_level_fields: dict[str, Any] = schema.get("properties", {}) |
| 159 | + wrapper = textwrap.TextWrapper(width=78) |
| 160 | + lines: list[str] = [] |
| 161 | + |
| 162 | + def add(text: str = "", indent: int = 0) -> None: |
| 163 | + """Append wrapped text with indentation.""" |
| 164 | + if not text: |
| 165 | + lines.append("") |
| 166 | + return |
| 167 | + indent_str = " " * indent |
| 168 | + wrapper.initial_indent = indent_str |
| 169 | + wrapper.subsequent_indent = indent_str |
| 170 | + lines.extend(wrapper.fill(text).split("\n")) |
| 171 | + |
| 172 | + add("ADK AgentConfig quick reference") |
| 173 | + add("--------------------------------") |
| 174 | + |
| 175 | + add() |
| 176 | + add("LlmAgent (agent_class: LlmAgent)") |
| 177 | + add( |
| 178 | + "Required fields: name, instruction. ADK best practice is to always set" |
| 179 | + " model explicitly.", |
| 180 | + indent=2, |
| 181 | + ) |
| 182 | + add("Optional fields:", indent=2) |
| 183 | + add("agent_class: defaults to LlmAgent; keep for clarity.", indent=4) |
| 184 | + add("description: short summary string.", indent=4) |
| 185 | + add("sub_agents: list of AgentRef entries (see below).", indent=4) |
| 186 | + add( |
| 187 | + "before_agent_callbacks / after_agent_callbacks: list of CodeConfig " |
| 188 | + "entries that run before or after the agent loop.", |
| 189 | + indent=4, |
| 190 | + ) |
| 191 | + add("model: string model id (required in practice).", indent=4) |
| 192 | + add( |
| 193 | + "disallow_transfer_to_parent / disallow_transfer_to_peers: booleans to " |
| 194 | + "restrict automatic transfer.", |
| 195 | + indent=4, |
| 196 | + ) |
| 197 | + add( |
| 198 | + "input_schema / output_schema: JSON schema objects to validate inputs " |
| 199 | + "and outputs.", |
| 200 | + indent=4, |
| 201 | + ) |
| 202 | + add("output_key: name to store agent output in session context.", indent=4) |
| 203 | + add( |
| 204 | + "include_contents: bool; include tool/LLM contents in response.", |
| 205 | + indent=4, |
| 206 | + ) |
| 207 | + add("tools: list of ToolConfig entries (see below).", indent=4) |
| 208 | + add( |
| 209 | + "before_model_callbacks / after_model_callbacks: list of CodeConfig " |
| 210 | + "entries around LLM calls.", |
| 211 | + indent=4, |
| 212 | + ) |
| 213 | + add( |
| 214 | + "before_tool_callbacks / after_tool_callbacks: list of CodeConfig " |
| 215 | + "entries around tool calls.", |
| 216 | + indent=4, |
| 217 | + ) |
| 218 | + add( |
| 219 | + "generate_content_config: passes directly to google.genai " |
| 220 | + "GenerateContentConfig (supporting temperature, topP, topK, " |
| 221 | + "maxOutputTokens, safetySettings, responseSchema, routingConfig," |
| 222 | + " etc.).", |
| 223 | + indent=4, |
133 | 224 | ) |
134 | 225 |
|
135 | | - # Format as indented code block for instruction embedding |
136 | | - # |
137 | | - # Why indentation is needed: |
138 | | - # - The ADK AgentConfig schema gets embedded into instruction templates using .format() |
139 | | - # - Proper indentation maintains readability in the final instruction |
140 | | - # - Code block markers (```) help LLMs recognize this as structured data |
141 | | - # |
142 | | - # Example final instruction format: |
143 | | - # "Here is the ADK AgentConfig schema: |
144 | | - # ```json |
145 | | - # {"type": "object", "properties": {...}} |
146 | | - # ```" |
147 | | - lines = schema_content.split("\n") |
148 | | - indented_lines = [" " + line for line in lines] # 2-space indent |
149 | | - return "```json\n" + "\n".join(indented_lines) + "\n ```" |
| 226 | + add() |
| 227 | + add("Workflow agents (LoopAgent, ParallelAgent, SequentialAgent)") |
| 228 | + add( |
| 229 | + "Share BaseAgent fields: agent_class, name, description, sub_agents, " |
| 230 | + "before/after_agent_callbacks. Never declare model, instruction, or " |
| 231 | + "tools on workflow orchestrators.", |
| 232 | + indent=2, |
| 233 | + ) |
| 234 | + add( |
| 235 | + "LoopAgent adds max_iterations (int) controlling iteration cap.", |
| 236 | + indent=2, |
| 237 | + ) |
| 238 | + |
| 239 | + add() |
| 240 | + add("AgentRef") |
| 241 | + add( |
| 242 | + "Used inside sub_agents lists. Provide either config_path (string path " |
| 243 | + "to another YAML file) or code (dotted Python reference) to locate the " |
| 244 | + "sub-agent definition.", |
| 245 | + indent=2, |
| 246 | + ) |
| 247 | + |
| 248 | + add() |
| 249 | + add("ToolConfig") |
| 250 | + add( |
| 251 | + "Items inside tools arrays. Required field name (string). For built-in " |
| 252 | + "tools use the exported short name, for custom tools use the dotted " |
| 253 | + "module path.", |
| 254 | + indent=2, |
| 255 | + ) |
| 256 | + add( |
| 257 | + "args: optional object of additional keyword arguments. Use simple " |
| 258 | + "key-value pairs (ToolArgsConfig) or structured ArgumentConfig entries " |
| 259 | + "when a list is required by callbacks.", |
| 260 | + indent=2, |
| 261 | + ) |
| 262 | + |
| 263 | + add() |
| 264 | + add("ArgumentConfig") |
| 265 | + add( |
| 266 | + "Represents a single argument. value is required and may be any JSON " |
| 267 | + "type. name is optional (null allowed). Often used in callback args.", |
| 268 | + indent=2, |
| 269 | + ) |
| 270 | + |
| 271 | + add() |
| 272 | + add("CodeConfig") |
| 273 | + add( |
| 274 | + "References Python code for callbacks or dynamic tool creation." |
| 275 | + " Requires name (dotted path). args is an optional list of" |
| 276 | + " ArgumentConfig items executed when invoking the function.", |
| 277 | + indent=2, |
| 278 | + ) |
| 279 | + |
| 280 | + add() |
| 281 | + add("GenerateContentConfig highlights") |
| 282 | + add( |
| 283 | + "Controls LLM generation behavior. Common fields: maxOutputTokens, " |
| 284 | + "temperature, topP, topK, candidateCount, responseMimeType, " |
| 285 | + "responseSchema/responseJsonSchema, automaticFunctionCalling, " |
| 286 | + "safetySettings, routingConfig; see Vertex AI GenAI docs for full " |
| 287 | + "semantics.", |
| 288 | + indent=2, |
| 289 | + ) |
| 290 | + |
| 291 | + add() |
| 292 | + add( |
| 293 | + "All other schema definitions in AgentConfig.json remain available but " |
| 294 | + "are rarely needed for typical agent setups. Refer to the source file " |
| 295 | + "for exhaustive field descriptions when implementing advanced configs.", |
| 296 | + ) |
| 297 | + |
| 298 | + if top_level_fields: |
| 299 | + add() |
| 300 | + add("Top-level AgentConfig fields (from schema)") |
| 301 | + for field_name in sorted(top_level_fields): |
| 302 | + description = top_level_fields[field_name].get("description", "") |
| 303 | + if description: |
| 304 | + add(f"{field_name}: {description}", indent=2) |
| 305 | + else: |
| 306 | + add(field_name, indent=2) |
| 307 | + |
| 308 | + if defs: |
| 309 | + add() |
| 310 | + add("Additional schema definitions") |
| 311 | + for def_name in sorted(defs): |
| 312 | + description = defs[def_name].get("description", "") |
| 313 | + if description: |
| 314 | + add(f"{def_name}: {description}", indent=2) |
| 315 | + else: |
| 316 | + add(def_name, indent=2) |
| 317 | + |
| 318 | + return "```text\n" + "\n".join(lines) + "\n```" |
| 319 | + |
| 320 | + @staticmethod |
| 321 | + def _extract_core_schema(schema: dict[str, Any]) -> dict[str, Any]: |
| 322 | + """Return only the schema nodes surfaced by the assistant.""" |
| 323 | + |
| 324 | + defs = schema.get("$defs", {}) |
| 325 | + filtered_defs: dict[str, Any] = {} |
| 326 | + for key in AgentBuilderAssistant._CORE_SCHEMA_DEF_NAMES: |
| 327 | + if key in defs: |
| 328 | + filtered_defs[key] = defs[key] |
| 329 | + |
| 330 | + gen_config = defs.get("GenerateContentConfig") |
| 331 | + if gen_config: |
| 332 | + properties = gen_config.get("properties", {}) |
| 333 | + filtered_defs["GenerateContentConfig"] = { |
| 334 | + "title": gen_config.get("title", "GenerateContentConfig"), |
| 335 | + "description": ( |
| 336 | + "Common LLM generation knobs exposed by the Agent Builder." |
| 337 | + ), |
| 338 | + "type": "object", |
| 339 | + "additionalProperties": False, |
| 340 | + "properties": { |
| 341 | + key: properties[key] |
| 342 | + for key in AgentBuilderAssistant._GEN_CONFIG_FIELDS |
| 343 | + if key in properties |
| 344 | + }, |
| 345 | + } |
| 346 | + |
| 347 | + return { |
| 348 | + "$defs": filtered_defs, |
| 349 | + "properties": schema.get("properties", {}), |
| 350 | + } |
150 | 351 |
|
151 | 352 | @staticmethod |
152 | 353 | def _load_instruction_with_schema( |
|
0 commit comments