kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kubiya-control-plane-api might be problematic. Click here for more details.
- control_plane_api/README.md +266 -0
- control_plane_api/__init__.py +0 -0
- control_plane_api/__version__.py +1 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +98 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
- control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
- control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
- control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
- control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
- control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
- control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
- control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
- control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
- control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
- control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +379 -0
- control_plane_api/app/activities/team_activities.py +410 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +354 -0
- control_plane_api/app/config/model_pricing.py +318 -0
- control_plane_api/app/config.py +95 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/job_executor.py +312 -0
- control_plane_api/app/lib/kubiya_client.py +235 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/planning_tools/__init__.py +22 -0
- control_plane_api/app/lib/planning_tools/agents.py +155 -0
- control_plane_api/app/lib/planning_tools/base.py +189 -0
- control_plane_api/app/lib/planning_tools/environments.py +214 -0
- control_plane_api/app/lib/planning_tools/resources.py +240 -0
- control_plane_api/app/lib/planning_tools/teams.py +198 -0
- control_plane_api/app/lib/policy_enforcer_client.py +939 -0
- control_plane_api/app/lib/redis_client.py +436 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/temporal_client.py +138 -0
- control_plane_api/app/lib/validation/__init__.py +20 -0
- control_plane_api/app/lib/validation/runtime_validation.py +287 -0
- control_plane_api/app/main.py +128 -0
- control_plane_api/app/middleware/__init__.py +8 -0
- control_plane_api/app/middleware/auth.py +513 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +27 -0
- control_plane_api/app/models/agent.py +79 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +81 -0
- control_plane_api/app/models/environment.py +63 -0
- control_plane_api/app/models/execution.py +93 -0
- control_plane_api/app/models/job.py +179 -0
- control_plane_api/app/models/llm_model.py +75 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +47 -0
- control_plane_api/app/models/session.py +38 -0
- control_plane_api/app/models/team.py +66 -0
- control_plane_api/app/models/workflow.py +55 -0
- control_plane_api/app/policies/README.md +121 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +364 -0
- control_plane_api/app/routers/agents_v2.py +1260 -0
- control_plane_api/app/routers/analytics.py +1014 -0
- control_plane_api/app/routers/context_manager.py +562 -0
- control_plane_api/app/routers/environment_context.py +270 -0
- control_plane_api/app/routers/environments.py +715 -0
- control_plane_api/app/routers/execution_environment.py +517 -0
- control_plane_api/app/routers/executions.py +1911 -0
- control_plane_api/app/routers/health.py +92 -0
- control_plane_api/app/routers/health_v2.py +326 -0
- control_plane_api/app/routers/integrations.py +274 -0
- control_plane_api/app/routers/jobs.py +1344 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +361 -0
- control_plane_api/app/routers/policies.py +639 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +902 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +155 -0
- control_plane_api/app/routers/skills.py +1001 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/task_planning.py +1256 -0
- control_plane_api/app/routers/task_queues.py +654 -0
- control_plane_api/app/routers/team_context.py +270 -0
- control_plane_api/app/routers/teams.py +1400 -0
- control_plane_api/app/routers/worker_queues.py +1545 -0
- control_plane_api/app/routers/workers.py +935 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/job_schemas.py +295 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_service.py +619 -0
- control_plane_api/app/services/litellm_service.py +190 -0
- control_plane_api/app/services/policy_service.py +525 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/skills/__init__.py +44 -0
- control_plane_api/app/skills/base.py +229 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/data_visualization.py +154 -0
- control_plane_api/app/skills/docker.py +104 -0
- control_plane_api/app/skills/file_generation.py +94 -0
- control_plane_api/app/skills/file_system.py +110 -0
- control_plane_api/app/skills/python.py +92 -0
- control_plane_api/app/skills/registry.py +65 -0
- control_plane_api/app/skills/shell.py +102 -0
- control_plane_api/app/skills/workflow_executor.py +469 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +507 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/team_execution.py +399 -0
- control_plane_api/scripts/seed_models.py +239 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1241 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/runtime_activities.py +388 -0
- control_plane_api/worker/activities/skill_activities.py +267 -0
- control_plane_api/worker/activities/team_activities.py +1217 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +275 -0
- control_plane_api/worker/control_plane_client.py +529 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +31 -0
- control_plane_api/worker/runtimes/base.py +789 -0
- control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
- control_plane_api/worker/runtimes/default_runtime.py +617 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_executor.py +422 -0
- control_plane_api/worker/services/agent_executor_v2.py +383 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/data_visualization.py +827 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +194 -0
- control_plane_api/worker/services/skill_factory.py +175 -0
- control_plane_api/worker/services/team_executor.py +574 -0
- control_plane_api/worker/services/team_executor_v2.py +465 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +305 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +373 -0
- control_plane_api/worker/worker.py +753 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +589 -0
- control_plane_api/worker/workflows/team_execution.py +429 -0
- kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
- kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
- kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
- kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
- kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
- kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
- kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
- {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
- {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LiteLLM Pricing Integration - Fetch and cache model pricing data
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import structlog
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
logger = structlog.get_logger()
|
|
12
|
+
|
|
13
|
+
# Cache for pricing data
|
|
14
|
+
_pricing_cache: Optional[Dict] = None
|
|
15
|
+
_cache_timestamp: Optional[datetime] = None
|
|
16
|
+
_cache_lock = asyncio.Lock()
|
|
17
|
+
|
|
18
|
+
PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/refs/heads/main/model_prices_and_context_window.json"
|
|
19
|
+
CACHE_TTL_HOURS = 24 # Refresh pricing data daily
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def get_litellm_pricing() -> Dict:
|
|
23
|
+
"""
|
|
24
|
+
Fetch LiteLLM pricing data with caching
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dict containing model pricing information
|
|
28
|
+
"""
|
|
29
|
+
global _pricing_cache, _cache_timestamp
|
|
30
|
+
|
|
31
|
+
async with _cache_lock:
|
|
32
|
+
# Check if cache is valid
|
|
33
|
+
if _pricing_cache and _cache_timestamp:
|
|
34
|
+
age = datetime.utcnow() - _cache_timestamp
|
|
35
|
+
if age < timedelta(hours=CACHE_TTL_HOURS):
|
|
36
|
+
logger.debug("litellm_pricing_cache_hit", age_hours=age.total_seconds() / 3600)
|
|
37
|
+
return _pricing_cache
|
|
38
|
+
|
|
39
|
+
# Fetch fresh pricing data
|
|
40
|
+
logger.info("fetching_litellm_pricing", url=PRICING_URL)
|
|
41
|
+
try:
|
|
42
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
43
|
+
response = await client.get(PRICING_URL)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
pricing_data = response.json()
|
|
46
|
+
|
|
47
|
+
# Update cache
|
|
48
|
+
_pricing_cache = pricing_data
|
|
49
|
+
_cache_timestamp = datetime.utcnow()
|
|
50
|
+
|
|
51
|
+
logger.info(
|
|
52
|
+
"litellm_pricing_fetched_successfully",
|
|
53
|
+
models_count=len(pricing_data),
|
|
54
|
+
cached_until=(_cache_timestamp + timedelta(hours=CACHE_TTL_HOURS)).isoformat()
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return pricing_data
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error("litellm_pricing_fetch_failed", error=str(e), error_type=type(e).__name__)
|
|
60
|
+
# Return empty dict on failure
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_model_pricing(model_id: str, pricing_data: Dict) -> Optional[Dict]:
|
|
65
|
+
"""
|
|
66
|
+
Get pricing information for a specific model
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
model_id: Model identifier (e.g., "claude-sonnet-4", "gpt-4o")
|
|
70
|
+
pricing_data: Full pricing data from LiteLLM
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict with pricing info or None if not found
|
|
74
|
+
"""
|
|
75
|
+
# Try exact match first
|
|
76
|
+
if model_id in pricing_data:
|
|
77
|
+
return pricing_data[model_id]
|
|
78
|
+
|
|
79
|
+
# Try common variations
|
|
80
|
+
variations = [
|
|
81
|
+
model_id,
|
|
82
|
+
f"openai/{model_id}",
|
|
83
|
+
f"anthropic/{model_id}",
|
|
84
|
+
f"anthropic.{model_id}",
|
|
85
|
+
f"bedrock/{model_id}",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
for variation in variations:
|
|
89
|
+
if variation in pricing_data:
|
|
90
|
+
return pricing_data[variation]
|
|
91
|
+
|
|
92
|
+
logger.warning("model_pricing_not_found", model_id=model_id, tried_variations=variations)
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def calculate_llm_cost(
|
|
97
|
+
model_id: str,
|
|
98
|
+
estimated_input_tokens: int,
|
|
99
|
+
estimated_output_tokens: int,
|
|
100
|
+
pricing_data: Dict
|
|
101
|
+
) -> tuple[float, float, float]:
|
|
102
|
+
"""
|
|
103
|
+
Calculate LLM cost for a model
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
model_id: Model identifier
|
|
107
|
+
estimated_input_tokens: Expected input tokens
|
|
108
|
+
estimated_output_tokens: Expected output tokens
|
|
109
|
+
pricing_data: Full pricing data from LiteLLM
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Tuple of (cost_per_1k_input, cost_per_1k_output, total_cost)
|
|
113
|
+
"""
|
|
114
|
+
model_pricing = get_model_pricing(model_id, pricing_data)
|
|
115
|
+
|
|
116
|
+
if not model_pricing:
|
|
117
|
+
# Fallback defaults
|
|
118
|
+
logger.warning("using_default_pricing", model_id=model_id)
|
|
119
|
+
return (0.003, 0.015, 0.0)
|
|
120
|
+
|
|
121
|
+
input_cost_per_token = model_pricing.get("input_cost_per_token", 0.000003)
|
|
122
|
+
output_cost_per_token = model_pricing.get("output_cost_per_token", 0.000015)
|
|
123
|
+
|
|
124
|
+
# Convert to per-1k pricing for display
|
|
125
|
+
input_cost_per_1k = input_cost_per_token * 1000
|
|
126
|
+
output_cost_per_1k = output_cost_per_token * 1000
|
|
127
|
+
|
|
128
|
+
# Calculate total cost
|
|
129
|
+
total_cost = (
|
|
130
|
+
(estimated_input_tokens * input_cost_per_token) +
|
|
131
|
+
(estimated_output_tokens * output_cost_per_token)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return (input_cost_per_1k, output_cost_per_1k, total_cost)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_model_display_name(model_id: str) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Get human-readable model name
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
model_id: Model identifier
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Display name
|
|
146
|
+
"""
|
|
147
|
+
# Map of common model IDs to display names
|
|
148
|
+
display_names = {
|
|
149
|
+
"claude-sonnet-4": "Claude Sonnet 4",
|
|
150
|
+
"claude-3-5-sonnet-20241022": "Claude 3.5 Sonnet",
|
|
151
|
+
"claude-3-5-sonnet-20240620": "Claude 3.5 Sonnet (Legacy)",
|
|
152
|
+
"claude-3-opus-20240229": "Claude 3 Opus",
|
|
153
|
+
"claude-3-haiku-20240307": "Claude 3 Haiku",
|
|
154
|
+
"gpt-4o": "GPT-4o",
|
|
155
|
+
"gpt-4o-mini": "GPT-4o Mini",
|
|
156
|
+
"gpt-4-turbo": "GPT-4 Turbo",
|
|
157
|
+
"gpt-4": "GPT-4",
|
|
158
|
+
"gpt-3.5-turbo": "GPT-3.5 Turbo",
|
|
159
|
+
"o1": "OpenAI o1",
|
|
160
|
+
"o1-mini": "OpenAI o1-mini",
|
|
161
|
+
"gemini-2.0-flash-exp": "Gemini 2.0 Flash",
|
|
162
|
+
"gemini-1.5-pro": "Gemini 1.5 Pro",
|
|
163
|
+
"gemini-1.5-flash": "Gemini 1.5 Flash",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return display_names.get(model_id, model_id.replace("-", " ").title())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Planning Tools - Modular tools for the task planning agent
|
|
3
|
+
|
|
4
|
+
This package provides decoupled, maintainable tools organized by category:
|
|
5
|
+
- agents: Agent-related context and operations
|
|
6
|
+
- teams: Team-related context and operations
|
|
7
|
+
- environments: Environment and infrastructure context
|
|
8
|
+
- resources: General resource and capability queries
|
|
9
|
+
- workflows: Workflow and execution context
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from control_plane_api.app.lib.planning_tools.agents import AgentsContextTools
|
|
13
|
+
from control_plane_api.app.lib.planning_tools.teams import TeamsContextTools
|
|
14
|
+
from control_plane_api.app.lib.planning_tools.environments import EnvironmentsContextTools
|
|
15
|
+
from control_plane_api.app.lib.planning_tools.resources import ResourcesContextTools
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AgentsContextTools",
|
|
19
|
+
"TeamsContextTools",
|
|
20
|
+
"EnvironmentsContextTools",
|
|
21
|
+
"ResourcesContextTools",
|
|
22
|
+
]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Context Tools - Fetch agent information for task planning
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from control_plane_api.app.lib.planning_tools.base import BasePlanningTools
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentsContextTools(BasePlanningTools):
|
|
10
|
+
"""
|
|
11
|
+
Tools for fetching agent context and capabilities
|
|
12
|
+
|
|
13
|
+
Provides methods to:
|
|
14
|
+
- List all available agents
|
|
15
|
+
- Get detailed agent information
|
|
16
|
+
- Query agent capabilities
|
|
17
|
+
- Check agent availability
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, base_url: str = "http://localhost:8000", organization_id: Optional[str] = None):
|
|
21
|
+
super().__init__(base_url=base_url, organization_id=organization_id)
|
|
22
|
+
self.name = "agent_context_tools"
|
|
23
|
+
|
|
24
|
+
async def list_agents(self, limit: int = 50) -> str:
|
|
25
|
+
"""
|
|
26
|
+
List all available agents with their basic information
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
limit: Maximum number of agents to return
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Formatted string with agent information including:
|
|
33
|
+
- Agent name and ID
|
|
34
|
+
- Model being used
|
|
35
|
+
- Description/capabilities
|
|
36
|
+
- Current status
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
params = {"limit": limit}
|
|
40
|
+
if self.organization_id:
|
|
41
|
+
params["organization_id"] = self.organization_id
|
|
42
|
+
|
|
43
|
+
response = await self._make_request("GET", "/agents", params=params)
|
|
44
|
+
|
|
45
|
+
agents = response if isinstance(response, list) else response.get("agents", [])
|
|
46
|
+
|
|
47
|
+
return self._format_list_response(
|
|
48
|
+
items=agents,
|
|
49
|
+
title="Available Agents",
|
|
50
|
+
key_fields=["model_id", "description", "status", "runner_name"],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
return f"Error listing agents: {str(e)}"
|
|
55
|
+
|
|
56
|
+
async def get_agent_details(self, agent_id: str) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Get detailed information about a specific agent
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
agent_id: ID of the agent to fetch
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Detailed agent information including:
|
|
65
|
+
- Full configuration
|
|
66
|
+
- Available tools/capabilities
|
|
67
|
+
- Model details
|
|
68
|
+
- Resource requirements
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
response = await self._make_request("GET", f"/agents/{agent_id}")
|
|
72
|
+
|
|
73
|
+
return self._format_detail_response(
|
|
74
|
+
item=response,
|
|
75
|
+
title=f"Agent Details: {response.get('name', 'Unknown')}",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return f"Error fetching agent {agent_id}: {str(e)}"
|
|
80
|
+
|
|
81
|
+
async def search_agents_by_capability(self, capability: str) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Search for agents that have a specific capability
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
capability: Capability to search for (e.g., "kubernetes", "aws", "python")
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of agents matching the capability
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
# First get all agents
|
|
93
|
+
params = {}
|
|
94
|
+
if self.organization_id:
|
|
95
|
+
params["organization_id"] = self.organization_id
|
|
96
|
+
|
|
97
|
+
response = await self._make_request("GET", "/agents", params=params)
|
|
98
|
+
agents = response if isinstance(response, list) else response.get("agents", [])
|
|
99
|
+
|
|
100
|
+
# Filter by capability (search in description and tools)
|
|
101
|
+
matching_agents = []
|
|
102
|
+
for agent in agents:
|
|
103
|
+
agent_text = f"{agent.get('name', '')} {agent.get('description', '')}".lower()
|
|
104
|
+
if capability.lower() in agent_text:
|
|
105
|
+
matching_agents.append(agent)
|
|
106
|
+
|
|
107
|
+
return self._format_list_response(
|
|
108
|
+
items=matching_agents,
|
|
109
|
+
title=f"Agents with '{capability}' capability",
|
|
110
|
+
key_fields=["model_id", "description"],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return f"Error searching agents: {str(e)}"
|
|
115
|
+
|
|
116
|
+
async def get_agent_execution_history(self, agent_id: str, limit: int = 10) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Get recent execution history for an agent
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
agent_id: ID of the agent
|
|
122
|
+
limit: Number of recent executions to fetch
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Recent execution history with success rates
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
params = {"limit": limit, "entity_id": agent_id}
|
|
129
|
+
response = await self._make_request("GET", "/executions", params=params)
|
|
130
|
+
|
|
131
|
+
executions = response if isinstance(response, list) else response.get("executions", [])
|
|
132
|
+
|
|
133
|
+
if not executions:
|
|
134
|
+
return f"No execution history found for agent {agent_id}"
|
|
135
|
+
|
|
136
|
+
# Calculate success rate
|
|
137
|
+
completed = sum(1 for e in executions if e.get("status") == "completed")
|
|
138
|
+
total = len(executions)
|
|
139
|
+
success_rate = (completed / total * 100) if total > 0 else 0
|
|
140
|
+
|
|
141
|
+
output = [
|
|
142
|
+
f"Execution History for Agent (Last {total} runs):",
|
|
143
|
+
f"Success Rate: {success_rate:.1f}% ({completed}/{total})",
|
|
144
|
+
"\nRecent Executions:",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
for idx, execution in enumerate(executions[:5], 1):
|
|
148
|
+
status = execution.get("status", "unknown")
|
|
149
|
+
prompt = execution.get("prompt", "No description")[:50]
|
|
150
|
+
output.append(f"{idx}. Status: {status} | Task: {prompt}...")
|
|
151
|
+
|
|
152
|
+
return "\n".join(output)
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
return f"Error fetching execution history: {str(e)}"
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes and utilities for planning tools
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
|
+
from agno.tools.toolkit import Toolkit
|
|
7
|
+
import structlog
|
|
8
|
+
import httpx
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
logger = structlog.get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BasePlanningTools(Toolkit):
|
|
15
|
+
"""
|
|
16
|
+
Base class for all planning tools with common utilities
|
|
17
|
+
|
|
18
|
+
Provides:
|
|
19
|
+
- HTTP client for API calls
|
|
20
|
+
- Common error handling
|
|
21
|
+
- Response formatting
|
|
22
|
+
- Logging utilities
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
base_url: str = "http://localhost:8000",
|
|
28
|
+
organization_id: Optional[str] = None,
|
|
29
|
+
timeout: int = 30,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Initialize base planning tools
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
base_url: Base URL for the control plane API
|
|
36
|
+
organization_id: Organization context for filtering
|
|
37
|
+
timeout: HTTP request timeout in seconds
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(name="base_planning_tools")
|
|
40
|
+
self.base_url = base_url.rstrip("/")
|
|
41
|
+
self.organization_id = organization_id
|
|
42
|
+
self.timeout = timeout
|
|
43
|
+
self._client: Optional[httpx.AsyncClient] = None
|
|
44
|
+
|
|
45
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
46
|
+
"""Get or create HTTP client"""
|
|
47
|
+
if self._client is None:
|
|
48
|
+
self._client = httpx.AsyncClient(
|
|
49
|
+
base_url=self.base_url,
|
|
50
|
+
timeout=self.timeout,
|
|
51
|
+
headers={"Content-Type": "application/json"},
|
|
52
|
+
)
|
|
53
|
+
return self._client
|
|
54
|
+
|
|
55
|
+
async def _make_request(
|
|
56
|
+
self,
|
|
57
|
+
method: str,
|
|
58
|
+
endpoint: str,
|
|
59
|
+
params: Optional[Dict[str, Any]] = None,
|
|
60
|
+
data: Optional[Dict[str, Any]] = None,
|
|
61
|
+
) -> Dict[str, Any]:
|
|
62
|
+
"""
|
|
63
|
+
Make HTTP request with error handling
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
method: HTTP method (GET, POST, etc.)
|
|
67
|
+
endpoint: API endpoint (will be appended to base_url)
|
|
68
|
+
params: Query parameters
|
|
69
|
+
data: Request body data
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Parsed JSON response
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
Exception: If request fails
|
|
76
|
+
"""
|
|
77
|
+
client = await self._get_client()
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
logger.info(
|
|
81
|
+
"planning_tool_request",
|
|
82
|
+
method=method,
|
|
83
|
+
endpoint=endpoint,
|
|
84
|
+
has_params=bool(params),
|
|
85
|
+
has_data=bool(data),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
response = await client.request(
|
|
89
|
+
method=method,
|
|
90
|
+
url=endpoint,
|
|
91
|
+
params=params,
|
|
92
|
+
json=data,
|
|
93
|
+
)
|
|
94
|
+
response.raise_for_status()
|
|
95
|
+
|
|
96
|
+
result = response.json()
|
|
97
|
+
logger.info(
|
|
98
|
+
"planning_tool_response",
|
|
99
|
+
endpoint=endpoint,
|
|
100
|
+
status_code=response.status_code,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
except httpx.HTTPStatusError as e:
|
|
106
|
+
logger.error(
|
|
107
|
+
"planning_tool_http_error",
|
|
108
|
+
endpoint=endpoint,
|
|
109
|
+
status_code=e.response.status_code,
|
|
110
|
+
error=str(e),
|
|
111
|
+
)
|
|
112
|
+
raise Exception(f"HTTP {e.response.status_code}: {str(e)}")
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(
|
|
115
|
+
"planning_tool_error",
|
|
116
|
+
endpoint=endpoint,
|
|
117
|
+
error=str(e),
|
|
118
|
+
error_type=type(e).__name__,
|
|
119
|
+
)
|
|
120
|
+
raise
|
|
121
|
+
|
|
122
|
+
def _format_list_response(
|
|
123
|
+
self,
|
|
124
|
+
items: List[Dict[str, Any]],
|
|
125
|
+
title: str,
|
|
126
|
+
key_fields: List[str],
|
|
127
|
+
) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Format a list of items as a readable string
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
items: List of items to format
|
|
133
|
+
title: Title for the list
|
|
134
|
+
key_fields: Fields to include in the output
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Formatted string representation
|
|
138
|
+
"""
|
|
139
|
+
if not items:
|
|
140
|
+
return f"{title}: None available"
|
|
141
|
+
|
|
142
|
+
output = [f"{title} ({len(items)} total):"]
|
|
143
|
+
for idx, item in enumerate(items, 1):
|
|
144
|
+
output.append(f"\n{idx}. {item.get('name', 'Unnamed')} (ID: {item.get('id', 'N/A')})")
|
|
145
|
+
for field in key_fields:
|
|
146
|
+
if field in item and item[field]:
|
|
147
|
+
value = item[field]
|
|
148
|
+
# Format nested objects
|
|
149
|
+
if isinstance(value, dict):
|
|
150
|
+
value = json.dumps(value, indent=2)
|
|
151
|
+
elif isinstance(value, list):
|
|
152
|
+
value = f"{len(value)} items"
|
|
153
|
+
output.append(f" - {field}: {value}")
|
|
154
|
+
|
|
155
|
+
return "\n".join(output)
|
|
156
|
+
|
|
157
|
+
def _format_detail_response(
|
|
158
|
+
self,
|
|
159
|
+
item: Dict[str, Any],
|
|
160
|
+
title: str,
|
|
161
|
+
) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Format a single item as a readable string
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
item: Item to format
|
|
167
|
+
title: Title for the item
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Formatted string representation
|
|
171
|
+
"""
|
|
172
|
+
if not item:
|
|
173
|
+
return f"{title}: Not found"
|
|
174
|
+
|
|
175
|
+
output = [f"{title}:"]
|
|
176
|
+
for key, value in item.items():
|
|
177
|
+
if isinstance(value, dict):
|
|
178
|
+
value = json.dumps(value, indent=2)
|
|
179
|
+
elif isinstance(value, list):
|
|
180
|
+
value = f"{len(value)} items: {', '.join([str(v) for v in value[:3]])}{'...' if len(value) > 3 else ''}"
|
|
181
|
+
output.append(f" {key}: {value}")
|
|
182
|
+
|
|
183
|
+
return "\n".join(output)
|
|
184
|
+
|
|
185
|
+
async def close(self):
|
|
186
|
+
"""Close HTTP client"""
|
|
187
|
+
if self._client:
|
|
188
|
+
await self._client.aclose()
|
|
189
|
+
self._client = None
|