From bccc6b5764905346cf91964db475f918d5f49d54 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:37:28 +0000 Subject: [PATCH 1/3] fix(infra): Initialize ApiRegistry stub in VerticeAgent Co-authored-by: JuanCS-Dev <227056558+JuanCS-Dev@users.noreply.github.com> --- .../vertice_core/a2a/proto/agent_card_pb2.py | 4 +- .../src/vertice_core/a2a/proto/common_pb2.py | 4 +- .../src/vertice_core/a2a/proto/message_pb2.py | 4 +- .../src/vertice_core/a2a/proto/service_pb2.py | 4 +- .../src/vertice_core/a2a/proto/task_pb2.py | 6 +-- .../vertice-core/src/vertice_core/adk/base.py | 4 ++ .../src/vertice_core/adk/registry.py | 31 +++++++++++++ .../agents/orchestrator/orchestrator.py | 6 +-- .../vertice_core/agents/router/__init__.py | 1 - .../vertice_core/clients/vertice_client.py | 9 ++-- .../vertice_core/memory/cortex/retrieval.py | 9 ++-- .../src/vertice_core/metacognition/engine.py | 4 +- .../plugins/builtin/git/__init__.py | 1 + .../plugins/examples/hello_world/__init__.py | 1 + .../vertice_core/plugins/plugin_manager.py | 6 +-- .../prometheus/distributed/registry.py | 4 +- .../mcp_server/tools/agent_tools_core.py | 14 ++---- .../vertice_core/providers/vertice_router.py | 17 +++---- .../src/vertice_core/shell_main.py | 6 +-- .../vertice_core/tui/components/dashboard.py | 4 +- .../vertice_core/tui/core/autoaudit/export.py | 12 ++--- .../tui/core/autoaudit/vertex_diagnostic.py | 12 ++--- .../tui/core/managers/status_manager.py | 4 +- .../tui/handlers/claude_parity_tasks.py | 8 +--- .../src/vertice_core/utils/parsing.py | 6 +-- tests/unit/test_adk_registry.py | 44 +++++++++++++++++++ 26 files changed, 135 insertions(+), 90 deletions(-) create mode 100644 packages/vertice-core/src/vertice_core/adk/registry.py create mode 100644 tests/unit/test_adk_registry.py diff --git a/packages/vertice-core/src/vertice_core/a2a/proto/agent_card_pb2.py b/packages/vertice-core/src/vertice_core/a2a/proto/agent_card_pb2.py index 04eea150..ca6076b9 100644 --- a/packages/vertice-core/src/vertice_core/a2a/proto/agent_card_pb2.py +++ b/packages/vertice-core/src/vertice_core/a2a/proto/agent_card_pb2.py @@ -22,9 +22,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "agent_card_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._options = None - _globals[ - "DESCRIPTOR" - ]._serialized_options = ( + _globals["DESCRIPTOR"]._serialized_options = ( b"\n\017com.vertice.a2aB\016AgentCardProtoZ\034github.com/vertice/a2a/proto" ) _globals["_OAUTH2CONFIG_SCOPESENTRY"]._options = None diff --git a/packages/vertice-core/src/vertice_core/a2a/proto/common_pb2.py b/packages/vertice-core/src/vertice_core/a2a/proto/common_pb2.py index 1127bdc5..39627e41 100644 --- a/packages/vertice-core/src/vertice_core/a2a/proto/common_pb2.py +++ b/packages/vertice-core/src/vertice_core/a2a/proto/common_pb2.py @@ -22,9 +22,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "common_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._options = None - _globals[ - "DESCRIPTOR" - ]._serialized_options = ( + _globals["DESCRIPTOR"]._serialized_options = ( b"\n\017com.vertice.a2aB\013CommonProtoZ\034github.com/vertice/a2a/proto" ) _globals["_PART_METADATAENTRY"]._options = None diff --git a/packages/vertice-core/src/vertice_core/a2a/proto/message_pb2.py b/packages/vertice-core/src/vertice_core/a2a/proto/message_pb2.py index 007df666..34e46c72 100644 --- a/packages/vertice-core/src/vertice_core/a2a/proto/message_pb2.py +++ b/packages/vertice-core/src/vertice_core/a2a/proto/message_pb2.py @@ -22,9 +22,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "message_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._options = None - _globals[ - "DESCRIPTOR" - ]._serialized_options = ( + _globals["DESCRIPTOR"]._serialized_options = ( b"\n\017com.vertice.a2aB\014MessageProtoZ\034github.com/vertice/a2a/proto" ) _globals["_MESSAGE_METADATAENTRY"]._options = None diff --git a/packages/vertice-core/src/vertice_core/a2a/proto/service_pb2.py b/packages/vertice-core/src/vertice_core/a2a/proto/service_pb2.py index 8cefd65b..c36df218 100644 --- a/packages/vertice-core/src/vertice_core/a2a/proto/service_pb2.py +++ b/packages/vertice-core/src/vertice_core/a2a/proto/service_pb2.py @@ -22,9 +22,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "service_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._options = None - _globals[ - "DESCRIPTOR" - ]._serialized_options = ( + _globals["DESCRIPTOR"]._serialized_options = ( b"\n\017com.vertice.a2aB\014ServiceProtoZ\034github.com/vertice/a2a/proto" ) _globals["_HEALTHCHECKRESPONSE_COMPONENTSENTRY"]._options = None diff --git a/packages/vertice-core/src/vertice_core/a2a/proto/task_pb2.py b/packages/vertice-core/src/vertice_core/a2a/proto/task_pb2.py index 5fe6d597..b68dd9e7 100644 --- a/packages/vertice-core/src/vertice_core/a2a/proto/task_pb2.py +++ b/packages/vertice-core/src/vertice_core/a2a/proto/task_pb2.py @@ -22,9 +22,9 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "task_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._options = None - _globals[ - "DESCRIPTOR" - ]._serialized_options = b"\n\017com.vertice.a2aB\tTaskProtoZ\034github.com/vertice/a2a/proto" + _globals["DESCRIPTOR"]._serialized_options = ( + b"\n\017com.vertice.a2aB\tTaskProtoZ\034github.com/vertice/a2a/proto" + ) _globals["_TASK_METADATAENTRY"]._options = None _globals["_TASK_METADATAENTRY"]._serialized_options = b"8\001" _globals["_SENDMESSAGEREQUEST_METADATAENTRY"]._options = None diff --git a/packages/vertice-core/src/vertice_core/adk/base.py b/packages/vertice-core/src/vertice_core/adk/base.py index aeb980ca..1f23aff9 100644 --- a/packages/vertice-core/src/vertice_core/adk/base.py +++ b/packages/vertice-core/src/vertice_core/adk/base.py @@ -10,6 +10,7 @@ from vertice_core.providers.vertex_ai import VertexAIProvider from vertice_core.memory.cortex.cortex import MemoryCortex from vertice_core.messaging.events import SystemEvent, get_event_bus +from vertice_core.adk.registry import ApiRegistry class VerticeAgent(abc.ABC): @@ -33,6 +34,9 @@ def __init__( self._provider = VertexAIProvider(project=project, location=location, model_name=model) self._cortex = MemoryCortex() + # Initialize ApiRegistry stub + self.api_registry = ApiRegistry(project=project, location=location) + def emit_event(self, event_type: str, payload: Mapping[str, Any]) -> None: """ Emit an internal telemetry event (fire-and-forget). diff --git a/packages/vertice-core/src/vertice_core/adk/registry.py b/packages/vertice-core/src/vertice_core/adk/registry.py new file mode 100644 index 00000000..9cdbe51d --- /dev/null +++ b/packages/vertice-core/src/vertice_core/adk/registry.py @@ -0,0 +1,31 @@ +"""Vertice ADK - Tool Registry Stub.""" + +from __future__ import annotations + +from typing import Any, Dict, Optional + + +class ApiRegistry: + """ + Stub for Vertex AI ApiRegistry. + Used for dynamic tool discovery in the Blueprint 2026. + + In the future, this will interface with: + google.cloud.aiplatform.reasoning_engines.ApiRegistry + """ + + def __init__(self, project: Optional[str] = None, location: str = "global") -> None: + self.project = project + self.location = location + self._tools: Dict[str, Any] = {} + + async def discover_tools(self) -> Dict[str, Any]: + """ + Discover available tools. + Currently returns the local in-memory registry. + """ + return self._tools + + def register_tool(self, name: str, tool: Any) -> None: + """Register a tool manually (for testing/stubbing).""" + self._tools[name] = tool diff --git a/packages/vertice-core/src/vertice_core/agents/orchestrator/orchestrator.py b/packages/vertice-core/src/vertice_core/agents/orchestrator/orchestrator.py index ef8425c8..d8788473 100644 --- a/packages/vertice-core/src/vertice_core/agents/orchestrator/orchestrator.py +++ b/packages/vertice-core/src/vertice_core/agents/orchestrator/orchestrator.py @@ -68,9 +68,9 @@ def __init__( self._iteration_count = 0 self._start_time: Optional[float] = None - self._on_state_change: Optional[ - Callable[[OrchestratorState, OrchestratorState], None] - ] = None + self._on_state_change: Optional[Callable[[OrchestratorState, OrchestratorState], None]] = ( + None + ) self._on_step_complete: Optional[Callable[[ExecutionStep, ExecutionResult], None]] = None def _transition_to( diff --git a/packages/vertice-core/src/vertice_core/agents/router/__init__.py b/packages/vertice-core/src/vertice_core/agents/router/__init__.py index 2fb4a890..9ff862bb 100644 --- a/packages/vertice-core/src/vertice_core/agents/router/__init__.py +++ b/packages/vertice-core/src/vertice_core/agents/router/__init__.py @@ -2,7 +2,6 @@ Router Module - Semantic routing for agent selection. """ - from .cache import RouterCacheMixin from .router import SemanticRouter from .similarity import SimilarityEngine diff --git a/packages/vertice-core/src/vertice_core/clients/vertice_client.py b/packages/vertice-core/src/vertice_core/clients/vertice_client.py index 8769826b..f76ea87a 100644 --- a/packages/vertice-core/src/vertice_core/clients/vertice_client.py +++ b/packages/vertice-core/src/vertice_core/clients/vertice_client.py @@ -71,8 +71,7 @@ def __init__(self, provider: str, retry_after: Optional[int] = None) -> None: class ProviderProtocol(Protocol): """Protocol for LLM providers.""" - def is_available(self) -> bool: - ... + def is_available(self) -> bool: ... async def stream_chat( self, @@ -80,8 +79,7 @@ async def stream_chat( max_tokens: int = DEFAULT_MAX_TOKENS, temperature: float = DEFAULT_TEMPERATURE, **kwargs: Any, - ) -> AsyncIterator[str]: - ... + ) -> AsyncIterator[str]: ... async def generate( self, @@ -89,8 +87,7 @@ async def generate( max_tokens: int = DEFAULT_MAX_TOKENS, temperature: float = DEFAULT_TEMPERATURE, **kwargs: Any, - ) -> str: - ... + ) -> str: ... @dataclass diff --git a/packages/vertice-core/src/vertice_core/memory/cortex/retrieval.py b/packages/vertice-core/src/vertice_core/memory/cortex/retrieval.py index 5a8289e6..57b41432 100644 --- a/packages/vertice-core/src/vertice_core/memory/cortex/retrieval.py +++ b/packages/vertice-core/src/vertice_core/memory/cortex/retrieval.py @@ -14,18 +14,15 @@ class MemorySubsystem(Protocol): """Protocol for memory subsystems that support search.""" - def search(self, query: str, limit: int = 5) -> Coroutine[Any, Any, List[Any]]: - ... + def search(self, query: str, limit: int = 5) -> Coroutine[Any, Any, List[Any]]: ... class CoreMemoryProtocol(Protocol): """Protocol for core memory.""" - def get_persona(self) -> Dict[str, Any]: - ... + def get_persona(self) -> Dict[str, Any]: ... - def to_context_string(self) -> str: - ... + def to_context_string(self) -> str: ... class ActiveRetrieval: diff --git a/packages/vertice-core/src/vertice_core/metacognition/engine.py b/packages/vertice-core/src/vertice_core/metacognition/engine.py index f33fc193..387db96b 100644 --- a/packages/vertice-core/src/vertice_core/metacognition/engine.py +++ b/packages/vertice-core/src/vertice_core/metacognition/engine.py @@ -179,9 +179,7 @@ def predict_outcome( recommendation = ( "proceed" if confidence > 0.6 - else "proceed_with_caution" - if confidence > 0.4 - else "reconsider" + else "proceed_with_caution" if confidence > 0.4 else "reconsider" ) return { diff --git a/packages/vertice-core/src/vertice_core/plugins/builtin/git/__init__.py b/packages/vertice-core/src/vertice_core/plugins/builtin/git/__init__.py index db0ac895..5419e169 100644 --- a/packages/vertice-core/src/vertice_core/plugins/builtin/git/__init__.py +++ b/packages/vertice-core/src/vertice_core/plugins/builtin/git/__init__.py @@ -1,4 +1,5 @@ """Git Integration Plugin.""" + from .plugin import GitPlugin __all__ = ["GitPlugin"] diff --git a/packages/vertice-core/src/vertice_core/plugins/examples/hello_world/__init__.py b/packages/vertice-core/src/vertice_core/plugins/examples/hello_world/__init__.py index 65632daa..2838cdd3 100644 --- a/packages/vertice-core/src/vertice_core/plugins/examples/hello_world/__init__.py +++ b/packages/vertice-core/src/vertice_core/plugins/examples/hello_world/__init__.py @@ -1,4 +1,5 @@ """Hello World example plugin.""" + from .plugin import HelloWorldPlugin __all__ = ["HelloWorldPlugin"] diff --git a/packages/vertice-core/src/vertice_core/plugins/plugin_manager.py b/packages/vertice-core/src/vertice_core/plugins/plugin_manager.py index a703af09..dbaa6077 100644 --- a/packages/vertice-core/src/vertice_core/plugins/plugin_manager.py +++ b/packages/vertice-core/src/vertice_core/plugins/plugin_manager.py @@ -8,11 +8,9 @@ class Plugin(Protocol): """Plugin interface.""" - async def initialize(self) -> None: - ... + async def initialize(self) -> None: ... - async def shutdown(self) -> None: - ... + async def shutdown(self) -> None: ... class PluginManager: diff --git a/packages/vertice-core/src/vertice_core/prometheus/distributed/registry.py b/packages/vertice-core/src/vertice_core/prometheus/distributed/registry.py index 8fdaf0fe..d360f45a 100644 --- a/packages/vertice-core/src/vertice_core/prometheus/distributed/registry.py +++ b/packages/vertice-core/src/vertice_core/prometheus/distributed/registry.py @@ -263,7 +263,9 @@ async def share_top_skills(self, top_k: int = 10, min_quality: float = 0.8) -> i skill for skill in all_skills[: top_k * 2] # Get more to filter if skill.success_rate >= min_quality - ][:top_k] # Take top k + ][ + :top_k + ] # Take top k if not top_skills: logger.debug("No high-quality skills to share") diff --git a/packages/vertice-core/src/vertice_core/prometheus/mcp_server/tools/agent_tools_core.py b/packages/vertice-core/src/vertice_core/prometheus/mcp_server/tools/agent_tools_core.py index c7c73c35..e6752133 100644 --- a/packages/vertice-core/src/vertice_core/prometheus/mcp_server/tools/agent_tools_core.py +++ b/packages/vertice-core/src/vertice_core/prometheus/mcp_server/tools/agent_tools_core.py @@ -438,22 +438,16 @@ def _calculate_overall_score( grade = ( "A" if final_score >= 8.5 - else "B" - if final_score >= 7.0 - else "C" - if final_score >= 5.5 - else "D" + else "B" if final_score >= 7.0 else "C" if final_score >= 5.5 else "D" ) return { "score": round(final_score, 1), "grade": grade, "issues_count": len(issues), - "recommendation": "Excellent" - if grade == "A" - else "Good" - if grade == "B" - else "Needs improvement", + "recommendation": ( + "Excellent" if grade == "A" else "Good" if grade == "B" else "Needs improvement" + ), } diff --git a/packages/vertice-core/src/vertice_core/providers/vertice_router.py b/packages/vertice-core/src/vertice_core/providers/vertice_router.py index ef2e100b..5be84afb 100644 --- a/packages/vertice-core/src/vertice_core/providers/vertice_router.py +++ b/packages/vertice-core/src/vertice_core/providers/vertice_router.py @@ -99,20 +99,17 @@ class RoutingDecision: class LLMProvider(Protocol): """Protocol for LLM providers.""" - def is_available(self) -> bool: - ... + def is_available(self) -> bool: ... - async def generate(self, messages: List[Dict], **kwargs) -> str: - ... + async def generate(self, messages: List[Dict], **kwargs) -> str: ... - async def stream_generate(self, messages: List[Dict], **kwargs) -> AsyncGenerator[str, None]: - ... + async def stream_generate( + self, messages: List[Dict], **kwargs + ) -> AsyncGenerator[str, None]: ... - async def stream_chat(self, messages: List[Dict], **kwargs) -> AsyncGenerator[str, None]: - ... + async def stream_chat(self, messages: List[Dict], **kwargs) -> AsyncGenerator[str, None]: ... - def get_model_info(self) -> ModelInfo: - ... + def get_model_info(self) -> ModelInfo: ... class VerticeRouter: diff --git a/packages/vertice-core/src/vertice_core/shell_main.py b/packages/vertice-core/src/vertice_core/shell_main.py index ea64fa0b..92880cbd 100644 --- a/packages/vertice-core/src/vertice_core/shell_main.py +++ b/packages/vertice-core/src/vertice_core/shell_main.py @@ -538,9 +538,9 @@ async def _check_auto_activation(self, user_input: str) -> None: context = ModeContext( cwd=str(Path.cwd()), env=dict(os.environ), - session_id=self.session_state.session_id - if hasattr(self, "session_state") - else None, + session_id=( + self.session_state.session_id if hasattr(self, "session_state") else None + ), ) action_data = { diff --git a/packages/vertice-core/src/vertice_core/tui/components/dashboard.py b/packages/vertice-core/src/vertice_core/tui/components/dashboard.py index 9e22a6c2..af4be6ce 100644 --- a/packages/vertice-core/src/vertice_core/tui/components/dashboard.py +++ b/packages/vertice-core/src/vertice_core/tui/components/dashboard.py @@ -356,9 +356,7 @@ def _render_metrics(self) -> Panel: mem_color = ( "green" if metrics.memory_percent < 50 - else "yellow" - if metrics.memory_percent < 80 - else "red" + else "yellow" if metrics.memory_percent < 80 else "red" ) table.add_row( "Memory:", diff --git a/packages/vertice-core/src/vertice_core/tui/core/autoaudit/export.py b/packages/vertice-core/src/vertice_core/tui/core/autoaudit/export.py index e27c0f6f..b62e6d07 100644 --- a/packages/vertice-core/src/vertice_core/tui/core/autoaudit/export.py +++ b/packages/vertice-core/src/vertice_core/tui/core/autoaudit/export.py @@ -138,9 +138,9 @@ def export_html(report: AuditReport, output_path: Optional[Path] = None) -> str: ) if output_path is None: - output_path = Path( - "logs/autoaudit" - ) / f"report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + output_path = ( + Path("logs/autoaudit") / f"report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + ) output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(html, encoding="utf-8") @@ -158,9 +158,9 @@ def export_json(report: AuditReport, output_path: Optional[Path] = None) -> str: import json if output_path is None: - output_path = Path( - "logs/autoaudit" - ) / f"report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + output_path = ( + Path("logs/autoaudit") / f"report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + ) output_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/packages/vertice-core/src/vertice_core/tui/core/autoaudit/vertex_diagnostic.py b/packages/vertice-core/src/vertice_core/tui/core/autoaudit/vertex_diagnostic.py index d8750944..2ededf2d 100644 --- a/packages/vertice-core/src/vertice_core/tui/core/autoaudit/vertex_diagnostic.py +++ b/packages/vertice-core/src/vertice_core/tui/core/autoaudit/vertex_diagnostic.py @@ -143,9 +143,7 @@ async def run(self) -> VertexAIDiagnosticReport: overall = ( "OK" if all(s.status == "OK" for s in self.steps) - else "PARTIAL" - if any(s.status == "OK" for s in self.steps) - else "FAIL" + else "PARTIAL" if any(s.status == "OK" for s in self.steps) else "FAIL" ) recommendations = self._generate_recommendations() @@ -325,9 +323,11 @@ async def _run_inference_test(self) -> None: step.details = { "model_used": model_name, "response_preview": response.text[:50], - "tokens_used": getattr(response.usage_metadata, "total_token_count", "N/A") - if hasattr(response, "usage_metadata") - else "N/A", + "tokens_used": ( + getattr(response.usage_metadata, "total_token_count", "N/A") + if hasattr(response, "usage_metadata") + else "N/A" + ), } else: step.status = "FAIL" diff --git a/packages/vertice-core/src/vertice_core/tui/core/managers/status_manager.py b/packages/vertice-core/src/vertice_core/tui/core/managers/status_manager.py index aa4a5dd1..594d9bfa 100644 --- a/packages/vertice-core/src/vertice_core/tui/core/managers/status_manager.py +++ b/packages/vertice-core/src/vertice_core/tui/core/managers/status_manager.py @@ -109,9 +109,7 @@ def check_health(self) -> Dict[str, Dict[str, Any]]: "status": ( "healthy" if agent_count >= 3 - else "degraded" - if agent_count > 0 - else "unhealthy" + else "degraded" if agent_count > 0 else "unhealthy" ), "message": f"{agent_count} agents available", "count": agent_count, diff --git a/packages/vertice-core/src/vertice_core/tui/handlers/claude_parity_tasks.py b/packages/vertice-core/src/vertice_core/tui/handlers/claude_parity_tasks.py index 1cdee6bc..57ddf9e0 100644 --- a/packages/vertice-core/src/vertice_core/tui/handlers/claude_parity_tasks.py +++ b/packages/vertice-core/src/vertice_core/tui/handlers/claude_parity_tasks.py @@ -56,9 +56,7 @@ async def _handle_bashes(self, args: str, view: "ResponseView") -> None: status_icon = ( "🟢" if t["status"] == "running" - else "✅" - if t["status"] == "completed" - else "❌" + else "✅" if t["status"] == "completed" else "❌" ) lines.append(f"{status_icon} `{t['id']}` - {t['command']} ({t['status']})") view.add_system_message("\n".join(lines)) @@ -211,9 +209,7 @@ async def _handle_subagents(self, args: str, view: "ResponseView") -> None: status_icon = ( "🟢" if s["status"] == "running" - else "✅" - if s["status"] == "completed" - else "❌" + else "✅" if s["status"] == "completed" else "❌" ) lines.append( f"{status_icon} `{s['id']}` - **{s['type']}** ({s['status']})\n" diff --git a/packages/vertice-core/src/vertice_core/utils/parsing.py b/packages/vertice-core/src/vertice_core/utils/parsing.py index a2a1087f..24bf1a40 100644 --- a/packages/vertice-core/src/vertice_core/utils/parsing.py +++ b/packages/vertice-core/src/vertice_core/utils/parsing.py @@ -101,12 +101,10 @@ def __init__( self.strategy = strategy @overload - def extract(self, text: str) -> dict[str, Any]: - ... + def extract(self, text: str) -> dict[str, Any]: ... @overload - def extract(self, text: str, default: T) -> dict[str, Any] | T: - ... + def extract(self, text: str, default: T) -> dict[str, Any] | T: ... def extract( self, diff --git a/tests/unit/test_adk_registry.py b/tests/unit/test_adk_registry.py new file mode 100644 index 00000000..c598c6d5 --- /dev/null +++ b/tests/unit/test_adk_registry.py @@ -0,0 +1,44 @@ +import pytest +from vertice_core.adk.registry import ApiRegistry +from vertice_core.adk.base import VerticeAgent +from collections.abc import Mapping +from typing import Any, Dict + + +@pytest.mark.asyncio +async def test_api_registry_stub(): + """Verify ApiRegistry stub functionality.""" + registry = ApiRegistry(project="test-project", location="us-central1") + + assert registry.project == "test-project" + assert registry.location == "us-central1" + + # Test tool registration + def dummy_tool(): + pass + + registry.register_tool("dummy", dummy_tool) + tools = await registry.discover_tools() + + assert "dummy" in tools + assert tools["dummy"] == dummy_tool + + +class MockAgent(VerticeAgent): + @property + def system_prompt(self): + return "Test prompt" + + async def query(self, *, input: str | Mapping[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"output": "ok"} + + +@pytest.mark.asyncio +async def test_vertice_agent_registry_integration(): + """Verify VerticeAgent initializes ApiRegistry.""" + agent = MockAgent(project="my-proj", location="us-west1") + + assert hasattr(agent, "api_registry") + assert isinstance(agent.api_registry, ApiRegistry) + assert agent.api_registry.project == "my-proj" + assert agent.api_registry.location == "us-west1" From 506d203043c1fdcea7e0cb8e9d6a4cea1c8b5f8b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:43:05 +0000 Subject: [PATCH 2/3] fix(ci): Update paths in quality workflow to match repo structure Co-authored-by: JuanCS-Dev <227056558+JuanCS-Dev@users.noreply.github.com> --- .github/workflows/quality.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index ad971b51..ad734e4a 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -26,36 +26,36 @@ jobs: - name: Check file sizes run: | echo "Checking file sizes (max 500 lines)..." - python scripts/check_file_size.py $(find vertice_cli vertice_tui vertice_core -name "*.py" -type f) + python scripts/check_file_size.py $(find packages/vertice-core/src/vertice_core -name "*.py" -type f) # Nesting Depth Check - Max 4 levels - name: Check nesting depth run: | echo "Checking nesting depth (max 4 levels)..." - python scripts/check_nesting.py $(find vertice_cli vertice_tui vertice_core -name "*.py" -type f) + python scripts/check_nesting.py $(find packages/vertice-core/src/vertice_core -name "*.py" -type f) # Cyclomatic Complexity Check - Max D (10-15) - name: Check cyclomatic complexity run: | echo "Checking cyclomatic complexity..." - radon cc vertice_cli/ vertice_tui/ vertice_core/ -a -nc --fail D + radon cc packages/vertice-core/src/vertice_core/ -a -nc --fail D # Maintainability Index Check - Min A (20+) - name: Check maintainability index run: | echo "Checking maintainability index..." - radon mi vertice_cli/ vertice_tui/ vertice_core/ -s + radon mi packages/vertice-core/src/vertice_core/ -s # Ruff linting - name: Run Ruff linter run: | - ruff check vertice_cli/ vertice_tui/ vertice_core/ + ruff check packages/vertice-core/src/vertice_core/ # Bare Exception Handler Check (Tech Debt Prevention) - name: Check for bare exception handlers run: | echo "Checking for bare 'except Exception:' handlers..." - COUNT=$(grep -rn "except Exception:" vertice_cli/ providers/ --include="*.py" | wc -l) + COUNT=$(grep -rn "except Exception:" packages/vertice-core/src/vertice_core/ --include="*.py" | wc -l) echo "Found $COUNT bare exception handlers" if [ "$COUNT" -gt 30 ]; then echo "::warning::Too many bare exception handlers ($COUNT). Target: <30" @@ -65,10 +65,10 @@ jobs: - name: Check for SessionManager duplication run: | echo "Checking for SessionManager class duplications..." - DUPS=$(grep -rn "class SessionManager" vertice_cli/ --include="*.py" | grep -v "core/session_manager" | grep -v "DEPRECATED" | wc -l) + DUPS=$(grep -rn "class SessionManager" packages/vertice-core/src/vertice_core/ --include="*.py" | grep -v "core/session_manager" | grep -v "DEPRECATED" | wc -l) if [ "$DUPS" -gt 0 ]; then echo "::error::Found $DUPS non-canonical SessionManager implementations" - grep -rn "class SessionManager" vertice_cli/ --include="*.py" | grep -v "core/session_manager" | grep -v "DEPRECATED" + grep -rn "class SessionManager" packages/vertice-core/src/vertice_core/ --include="*.py" | grep -v "core/session_manager" | grep -v "DEPRECATED" exit 1 fi echo "✅ No SessionManager duplication found" @@ -92,7 +92,7 @@ jobs: - name: Run tests with coverage run: | - pytest tests/ -v --cov=vertice_cli --cov=vertice_tui --cov=vertice_core --cov-report=xml --cov-fail-under=60 + pytest tests/ -v --cov=packages/vertice-core/src/vertice_core --cov-report=xml --cov-fail-under=60 - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 From d4a594956efb253a091a42d0237f2fa31009c53b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 29 Jan 2026 21:56:55 -0300 Subject: [PATCH 3/3] fix(infra): consolidate high-quality ApiRegistry implementation and tests --- JULES_ISSUES.md | 36 ++++++ ...RTICE_GOOGLE_SINGULARITY_BLUEPRINT_2026.md | 62 +++++++++- .../vertice-core/src/vertice_core/adk/base.py | 4 +- .../src/vertice_core/adk/registry.py | 108 +++++++++++++++--- tests/unit/test_vertice_adk_discovery.py | 76 ++++++++++++ 5 files changed, 267 insertions(+), 19 deletions(-) create mode 100644 JULES_ISSUES.md create mode 100644 tests/unit/test_vertice_adk_discovery.py diff --git a/JULES_ISSUES.md b/JULES_ISSUES.md new file mode 100644 index 00000000..b30a2623 --- /dev/null +++ b/JULES_ISSUES.md @@ -0,0 +1,36 @@ +# JULES AGENT BACKLOG - VERTICE-CODE SINGULARITY + +Este arquivo contém as issues iniciais para a atuação do agente **JULES**, focadas na transição para a Fase 7 (Cloud API Registry) e estabilização do padrão Google-Native 2026. + +--- + +## [JULES-001] Infra: Inicializar ApiRegistry stub no VerticeAgent +**TAG:** `JULES`, `ADK`, `INFRA` +**DESCRIÇÃO:** Integrar a classe `ApiRegistry` da Vertex AI na classe base dos agentes para permitir a descoberta dinâmica de ferramentas, conforme o Blueprint 2026. +**TAREFAS:** +- [ ] Modificar `packages/vertice-core/src/vertice_core/adk/base.py`. +- [ ] Importar `vertexai.preview.api_registry.ApiRegistry` (dentro de um bloco try-except para manter compatibilidade local). +- [ ] Adicionar `self.registry = ApiRegistry(project=project)` no `__init__`. +- [ ] Criar um método placeholder `self.discover_tools(service_name: str)` que encapsule a busca no registry. + +--- + +## [JULES-002] Deps: Alinhar requirements do Agent-Gateway com SDK 2026 +**TAG:** `JULES`, `DEPS`, `COMPLIANCE` +**DESCRIÇÃO:** Atualizar as dependências do gateway de agentes para garantir compatibilidade com as versões especificadas no Blueprint da Singularidade. +**TAREFAS:** +- [ ] Modificar `apps/agent-gateway/requirements.txt`. +- [ ] Atualizar `google-cloud-aiplatform` para `1.115.0`. +- [ ] Atualizar `google-genai` para `1.2.0`. +- [ ] Garantir que o `vertice-core` seja instalado em modo editável (`-e`). + +--- + +## [JULES-003] Test: Validar conformidade de descoberta dinâmica no SDK +**TAG:** `JULES`, `TEST`, `ADK` +**DESCRIÇÃO:** Criar um teste unitário que valide se o `VerticeAgent` consegue instanciar o `ApiRegistry` sem quebrar o runtime caso o ambiente não seja GCP. +**TAREFAS:** +- [ ] Modificar `tests/unit/test_vertice_adk_sdk.py`. +- [ ] Adicionar um caso de teste `test_agent_registry_initialization`. +- [ ] Mockar a resposta do `ApiRegistry` para simular a descoberta de ferramentas do Prometheus. +- [ ] Verificar se o agente emite o evento de telemetria "registry_init" corretamente. diff --git a/docs/google/VERTICE_GOOGLE_SINGULARITY_BLUEPRINT_2026.md b/docs/google/VERTICE_GOOGLE_SINGULARITY_BLUEPRINT_2026.md index dbe72ce3..4852fab3 100644 --- a/docs/google/VERTICE_GOOGLE_SINGULARITY_BLUEPRINT_2026.md +++ b/docs/google/VERTICE_GOOGLE_SINGULARITY_BLUEPRINT_2026.md @@ -108,6 +108,64 @@ pytest tests/unit/test_alloydb_migration.py tests/unit/test_alloydb_cutover_beha 2. **Immersive Experience:** Landing page com input central direto para o agente e dashboard de streaming CoT (Chain of Thought). 3. **Artifact Gallery:** Interface visual para gestão de código, UI e ativos gerados pela malha de agentes. +### FASE 7: O NEXUS DE FERRAMENTAS (Cloud API Registry) — 🆕 NOVA (29/01/2026) +**Objetivo:** Centralizar a descoberta e execução de ferramentas MCP via infraestrutura gerenciada da Google, eliminando a configuração manual de endpoints e centralizando a governança. + +#### 7.1 Registro e Ativação (Infrastructure) +O administrador deve habilitar os servidores MCP no projeto `vertice-ai`. + +* **Habilitar Ferramentas Google:** + ```bash + # Habilitar BigQuery como ferramenta MCP + gcloud beta api-registry mcp enable bigquery.googleapis.com --project=vertice-ai + ``` +* **Registrar Servidor Custom (Prometheus):** + Atualmente via **API Hub**, vinculando o endpoint do Cloud Run: + ```bash + # Registrar o endpoint do vertice-mcp + gcloud apigee apis create vertice-mcp \ + --display-name="Prometheus Tactical Tools" \ + --description="58+ tactical tools for code, git, and shell" \ + --project=vertice-ai + ``` + +#### 7.2 Implementação no ADK (Python SDK) +Atualização do `VerticeAgent` para descoberta dinâmica de ferramentas. Em vez de hardcoding, usamos o `ApiRegistry`. + +```python +from vertexai.preview.api_registry import ApiRegistry +from vertexai.preview.generative_models import GenerativeModel + +class VerticeAgent(abc.ABC): + def __init__(self, project="vertice-ai", location="us-central1"): + # Inicializa o Registro de Ferramentas da Google + self.registry = ApiRegistry(project=project) + + # Descoberta Dinâmica (O Nexus) + # Busca o toolset do Prometheus e do BigQuery + self.prometheus_tools = self.registry.get_toolset( + service_name="vertice-mcp.api.hub" + ) + self.google_tools = self.registry.get_toolset( + service_name="bigquery.googleapis.com" + ) + + async def query(self, input_text): + # O modelo agora tem acesso às ferramentas via Registry + model = GenerativeModel( + "gemini-3-flash", + tools=[self.prometheus_tools, self.google_tools] + ) + # ... execução do agente +``` + +#### 7.3 Governança IAM (Hardened) +A segurança não é mais por API Key, mas por identidade de serviço (Service Account). + +* **Role de Visualização:** `roles/cloudapiregistry.viewer` (Permite ao agente ver o que existe). +* **Role de Execução:** `roles/mcp.toolUser` (Permite ao agente chamar `call_tool`). +* **Isolação:** Cada agente (Coder, Security) deve ter sua própria SA, com permissões específicas no Registry. + --- ## 4. ESTRUTURA DE ARQUIVOS DEFINITIVA (2026) @@ -124,6 +182,7 @@ pytest tests/unit/test_alloydb_migration.py tests/unit/test_alloydb_cutover_beha │ ├── packages/ │ ├── vertice-core/ # O "Espírito" (Python Library) +│ │ ├── adk/ # Core ADK 2026 (BaseAgent, ApiRegistry) │ │ ├── agents/ # Definições Puras (ADK) │ │ ├── tools/ # Ferramentas (Google Search, Code Interpreter) │ │ └── memory/ # AlloyDB Connectors @@ -131,7 +190,7 @@ pytest tests/unit/test_alloydb_migration.py tests/unit/test_alloydb_cutover_beha │ └── infra/ ├── terraform/ # IaC para AlloyDB, Vertex AI, Cloud Run - └── cloudbuild.yaml # Pipeline CI/CD Unificado + └── api-registry/ # Configurações de registro de ferramentas MCP ``` --- @@ -143,6 +202,7 @@ pytest tests/unit/test_alloydb_migration.py tests/unit/test_alloydb_cutover_beha | **Escalabilidade** | Limitada pelo tamanho da VM | **Infinita** (Serverless) | | **Latência** | Alta (Python processando tudo) | **Baixa** (Streaming via AG-UI) | | **Manutenção** | Pesadelo (DevOps manual) | **Zero** (Managed Services) | +| **Discovery de Tools**| URLs fixas / .env | **Cloud API Registry** (Centralizado) | | **Custo Ocioso** | 100% (VM sempre ligada) | **Perto de Zero** (Scale to Zero) | | **Inteligência** | LLM legacy (pré‑Google‑native) | **Gemini 3 (Pro/Flash) via Vertex AI + Grounding** | diff --git a/packages/vertice-core/src/vertice_core/adk/base.py b/packages/vertice-core/src/vertice_core/adk/base.py index 1f23aff9..cd6a4ea3 100644 --- a/packages/vertice-core/src/vertice_core/adk/base.py +++ b/packages/vertice-core/src/vertice_core/adk/base.py @@ -34,8 +34,8 @@ def __init__( self._provider = VertexAIProvider(project=project, location=location, model_name=model) self._cortex = MemoryCortex() - # Initialize ApiRegistry stub - self.api_registry = ApiRegistry(project=project, location=location) + # Dynamic Discovery Registry + self.apis = ApiRegistry(project=project, location=location) def emit_event(self, event_type: str, payload: Mapping[str, Any]) -> None: """ diff --git a/packages/vertice-core/src/vertice_core/adk/registry.py b/packages/vertice-core/src/vertice_core/adk/registry.py index 9cdbe51d..aee4564b 100644 --- a/packages/vertice-core/src/vertice_core/adk/registry.py +++ b/packages/vertice-core/src/vertice_core/adk/registry.py @@ -1,31 +1,107 @@ -"""Vertice ADK - Tool Registry Stub.""" +"""Vertice ADK - Dynamic API Registry for 2026 Google-Native ecosystem.""" from __future__ import annotations -from typing import Any, Dict, Optional +import os +import logging +from typing import Any, Dict, List, Optional + +logger = logging.getLogger(__name__) + +# --- Google Gen AI SDK (Vertex AI) --- +HAS_GENAI_SDK = False +try: + from google import genai + + HAS_GENAI_SDK = True +except ImportError: + pass class ApiRegistry: """ - Stub for Vertex AI ApiRegistry. - Used for dynamic tool discovery in the Blueprint 2026. - - In the future, this will interface with: - google.cloud.aiplatform.reasoning_engines.ApiRegistry + Dynamic registry for discovering and managing available APIs and models. + Ensures runtime compatibility across different environments (GCP vs local). """ def __init__(self, project: Optional[str] = None, location: str = "global") -> None: - self.project = project + self.project = project or os.getenv("GOOGLE_CLOUD_PROJECT") self.location = location - self._tools: Dict[str, Any] = {} + self._genai_client = None + + # Check environment + self._is_gcp = self._check_gcp_environment() + logger.debug(f"ApiRegistry initialized. GCP detected: {self._is_gcp}") + + def _check_gcp_environment(self) -> bool: + """Heuristic to detect if running in a GCP environment.""" + # Check for standard GCP environment variables + return any( + [ + os.getenv("GOOGLE_CLOUD_PROJECT"), + os.getenv("K_SERVICE"), # Cloud Run + os.getenv("GAE_SERVICE"), # App Engine + os.path.exists("/var/run/secrets/google"), # GKE + ] + ) + + def _get_genai_client(self) -> Optional[Any]: + """Lazy initialization of the GenAI client.""" + if self._genai_client is not None: + return self._genai_client - async def discover_tools(self) -> Dict[str, Any]: + if not HAS_GENAI_SDK: + logger.debug("google-genai SDK not installed, discovery disabled.") + return None + + try: + # We don't want this to raise during instantiation of the registry + self._genai_client = genai.Client( + vertexai=True, + project=self.project, + location=self.location, + ) + return self._genai_client + except Exception as e: + logger.warning(f"Failed to initialize GenAI client for discovery: {e}") + return None + + async def discover_models(self) -> List[Dict[str, Any]]: """ - Discover available tools. - Currently returns the local in-memory registry. + Dynamically discover available Gemini models on Vertex AI. + + Returns a list of model metadata. Returns an empty list if discovery is + unavailable or fails. """ - return self._tools + client = self._get_genai_client() + if client is None: + return [] + + try: + # Note: client.aio.models.list is the pattern for async discovery in 2026 SDK + response = await client.aio.models.list(config={"page_size": 50}) + + # Handle both Pager and direct list response + models = getattr(response, "models", response) + + return [ + { + "name": getattr(m, "name", str(m)), + "display_name": getattr(m, "display_name", ""), + "description": getattr(m, "description", ""), + "capabilities": getattr(m, "supported_generation_methods", []), + } + for m in models + ] + except Exception as e: + logger.warning(f"Dynamic model discovery failed: {e}") + return [] - def register_tool(self, name: str, tool: Any) -> None: - """Register a tool manually (for testing/stubbing).""" - self._tools[name] = tool + def get_status(self) -> Dict[str, Any]: + """Return the current status of the registry and environment.""" + return { + "is_gcp": self._is_gcp, + "has_sdk": HAS_GENAI_SDK, + "project": self.project, + "location": self.location, + } diff --git a/tests/unit/test_vertice_adk_discovery.py b/tests/unit/test_vertice_adk_discovery.py new file mode 100644 index 00000000..aea5fc1a --- /dev/null +++ b/tests/unit/test_vertice_adk_discovery.py @@ -0,0 +1,76 @@ +import pytest +import os +from unittest.mock import MagicMock, patch +from vertice_core.adk.base import VerticeAgent +from vertice_core.adk.registry import ApiRegistry +from typing import Dict, Any +from collections.abc import Mapping + + +class MockAgent(VerticeAgent): + """Minimal agent for testing discovery.""" + + @property + def system_prompt(self): + return "Test prompt" + + async def query(self, *, input: str | Mapping[str, Any], **kwargs: Any) -> Dict[str, Any]: + return {"output": "test"} + + +def test_api_registry_instantiation_no_gcp(): + """Valida se o ApiRegistry pode ser instanciado fora do GCP sem erro.""" + # Garante que as env vars do GCP não estão presentes + with patch.dict(os.environ, {}, clear=True): + registry = ApiRegistry(project=None) + status = registry.get_status() + + assert status["is_gcp"] is False + assert registry.project is None + + +def test_vertice_agent_instantiates_api_registry(): + """Valida se o VerticeAgent instancia o ApiRegistry automaticamente.""" + agent = MockAgent(project="test-project") + + assert hasattr(agent, "apis") + assert isinstance(agent.apis, ApiRegistry) + assert agent.apis.project == "test-project" + + +@pytest.mark.asyncio +async def test_api_registry_discovery_safe_fail(): + """Valida se a descoberta falha graciosamente sem o SDK ou credenciais.""" + with patch("vertice_core.adk.registry.HAS_GENAI_SDK", False): + registry = ApiRegistry() + models = await registry.discover_models() + assert models == [] + + +@pytest.mark.asyncio +async def test_api_registry_discovery_mocked(): + """Valida o fluxo de descoberta com o SDK mockado.""" + mock_client = MagicMock() + + # Mock para client.aio.models.list + mock_model = MagicMock() + mock_model.name = "models/gemini-3-pro" + mock_model.display_name = "Gemini 3 Pro" + + mock_response = MagicMock() + mock_response.models = [mock_model] + + # Mock async + async def mock_list(*args, **kwargs): + return mock_response + + mock_client.aio.models.list = mock_list + + with patch("vertice_core.adk.registry.HAS_GENAI_SDK", True): + with patch("google.genai.Client", return_value=mock_client): + registry = ApiRegistry(project="test", location="global") + models = await registry.discover_models() + + assert len(models) == 1 + assert models[0]["name"] == "models/gemini-3-pro" + assert models[0]["display_name"] == "Gemini 3 Pro"