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,410 @@
|
|
|
1
|
+
"""Team-related Temporal activities"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
from temporalio import activity
|
|
7
|
+
import structlog
|
|
8
|
+
|
|
9
|
+
from agno.agent import Agent
|
|
10
|
+
from agno.team import Team
|
|
11
|
+
from agno.models.litellm import LiteLLM
|
|
12
|
+
|
|
13
|
+
from control_plane_api.app.lib.supabase import get_supabase
|
|
14
|
+
from control_plane_api.app.activities.agent_activities import update_execution_status, ActivityUpdateExecutionInput
|
|
15
|
+
|
|
16
|
+
logger = structlog.get_logger()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ActivityGetTeamAgentsInput:
|
|
21
|
+
"""Input for get_team_agents activity"""
|
|
22
|
+
team_id: str
|
|
23
|
+
organization_id: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ActivityExecuteTeamInput:
|
|
28
|
+
"""Input for execute_team_coordination activity"""
|
|
29
|
+
execution_id: str
|
|
30
|
+
team_id: str
|
|
31
|
+
organization_id: str
|
|
32
|
+
prompt: str
|
|
33
|
+
system_prompt: Optional[str] = None
|
|
34
|
+
agents: List[dict] = None
|
|
35
|
+
team_config: dict = None
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
if self.agents is None:
|
|
39
|
+
self.agents = []
|
|
40
|
+
if self.team_config is None:
|
|
41
|
+
self.team_config = {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@activity.defn
|
|
45
|
+
async def get_team_agents(input: ActivityGetTeamAgentsInput) -> dict:
|
|
46
|
+
"""
|
|
47
|
+
Get all agents in a team.
|
|
48
|
+
|
|
49
|
+
This activity fetches agents belonging to the team from the database.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
input: Activity input with team details
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Dict with agents list
|
|
56
|
+
"""
|
|
57
|
+
print(f"\n\n=== GET_TEAM_AGENTS START ===")
|
|
58
|
+
print(f"team_id: {input.team_id} (type: {type(input.team_id).__name__})")
|
|
59
|
+
print(f"organization_id: {input.organization_id} (type: {type(input.organization_id).__name__})")
|
|
60
|
+
print(f"================================\n")
|
|
61
|
+
|
|
62
|
+
activity.logger.info(
|
|
63
|
+
f"[DEBUG] Getting team agents START",
|
|
64
|
+
extra={
|
|
65
|
+
"team_id": input.team_id,
|
|
66
|
+
"team_id_type": type(input.team_id).__name__,
|
|
67
|
+
"organization_id": input.organization_id,
|
|
68
|
+
"organization_id_type": type(input.organization_id).__name__,
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
client = get_supabase()
|
|
74
|
+
print(f"Supabase client created successfully")
|
|
75
|
+
|
|
76
|
+
activity.logger.info(
|
|
77
|
+
f"[DEBUG] Supabase client created, fetching team configuration",
|
|
78
|
+
extra={
|
|
79
|
+
"team_id": input.team_id,
|
|
80
|
+
"organization_id": input.organization_id,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# First, get the team configuration to get member_ids (source of truth)
|
|
85
|
+
print(f"Fetching team configuration for team_id={input.team_id}")
|
|
86
|
+
team_result = (
|
|
87
|
+
client.table("teams")
|
|
88
|
+
.select("configuration")
|
|
89
|
+
.eq("id", input.team_id)
|
|
90
|
+
.eq("organization_id", input.organization_id)
|
|
91
|
+
.single()
|
|
92
|
+
.execute()
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if not team_result.data:
|
|
96
|
+
print(f"Team not found!")
|
|
97
|
+
activity.logger.error(
|
|
98
|
+
f"[DEBUG] Team not found",
|
|
99
|
+
extra={
|
|
100
|
+
"team_id": input.team_id,
|
|
101
|
+
"organization_id": input.organization_id,
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
return {"agents": [], "count": 0}
|
|
105
|
+
|
|
106
|
+
team_config = team_result.data.get("configuration", {})
|
|
107
|
+
member_ids = team_config.get("member_ids", [])
|
|
108
|
+
print(f"Team configuration member_ids: {member_ids}")
|
|
109
|
+
|
|
110
|
+
activity.logger.info(
|
|
111
|
+
f"[DEBUG] Team configuration loaded",
|
|
112
|
+
extra={
|
|
113
|
+
"team_id": input.team_id,
|
|
114
|
+
"member_ids": member_ids,
|
|
115
|
+
"member_count": len(member_ids),
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Get agents by member_ids (not by FK relationship)
|
|
120
|
+
agents = []
|
|
121
|
+
if member_ids:
|
|
122
|
+
print(f"Fetching agents with IDs: {member_ids}")
|
|
123
|
+
result = (
|
|
124
|
+
client.table("agents")
|
|
125
|
+
.select("*")
|
|
126
|
+
.in_("id", member_ids)
|
|
127
|
+
.eq("organization_id", input.organization_id)
|
|
128
|
+
.execute()
|
|
129
|
+
)
|
|
130
|
+
agents = result.data or []
|
|
131
|
+
print(f"Query executed. Result data length: {len(agents)}")
|
|
132
|
+
|
|
133
|
+
activity.logger.info(
|
|
134
|
+
f"[DEBUG] Query executed, processing results",
|
|
135
|
+
extra={
|
|
136
|
+
"agents_found": len(agents),
|
|
137
|
+
"agent_ids": [a.get("id") for a in agents],
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
print(f"Agents found: {len(agents)}")
|
|
142
|
+
if agents:
|
|
143
|
+
for agent in agents:
|
|
144
|
+
print(f" - {agent.get('name')} (ID: {agent.get('id')})")
|
|
145
|
+
|
|
146
|
+
activity.logger.info(
|
|
147
|
+
f"[DEBUG] Retrieved team agents",
|
|
148
|
+
extra={
|
|
149
|
+
"team_id": input.team_id,
|
|
150
|
+
"agent_count": len(agents),
|
|
151
|
+
"agent_names": [a.get("name") for a in agents],
|
|
152
|
+
"agent_ids": [a.get("id") for a in agents],
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if not agents:
|
|
157
|
+
print(f"\n!!! NO AGENTS FOUND - Running verification query !!!")
|
|
158
|
+
activity.logger.warning(
|
|
159
|
+
f"[DEBUG] WARNING: No agents found for team - running verification query",
|
|
160
|
+
extra={
|
|
161
|
+
"team_id": input.team_id,
|
|
162
|
+
"organization_id": input.organization_id,
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Try query without org filter to debug
|
|
167
|
+
result_no_org = (
|
|
168
|
+
client.table("agents")
|
|
169
|
+
.select("id, name, team_id, organization_id")
|
|
170
|
+
.eq("team_id", input.team_id)
|
|
171
|
+
.execute()
|
|
172
|
+
)
|
|
173
|
+
print(f"Query without org filter returned {len(result_no_org.data or [])} agents")
|
|
174
|
+
if result_no_org.data:
|
|
175
|
+
for agent in result_no_org.data:
|
|
176
|
+
print(f" - {agent.get('name')} (team_id: {agent.get('team_id')}, org_id: {agent.get('organization_id')})")
|
|
177
|
+
|
|
178
|
+
activity.logger.warning(
|
|
179
|
+
f"[DEBUG] Query without org filter returned {len(result_no_org.data or [])} agents",
|
|
180
|
+
extra={
|
|
181
|
+
"agents_found": result_no_org.data if result_no_org.data else [],
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
print(f"\n=== GET_TEAM_AGENTS END: Returning {len(agents)} agents ===\n\n")
|
|
186
|
+
return {
|
|
187
|
+
"agents": agents,
|
|
188
|
+
"count": len(agents),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
print(f"\n!!! EXCEPTION in get_team_agents: {type(e).__name__}: {str(e)} !!!\n")
|
|
193
|
+
activity.logger.error(
|
|
194
|
+
f"[DEBUG] EXCEPTION in get_team_agents",
|
|
195
|
+
extra={
|
|
196
|
+
"team_id": input.team_id,
|
|
197
|
+
"organization_id": input.organization_id,
|
|
198
|
+
"error": str(e),
|
|
199
|
+
"error_type": type(e).__name__,
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
raise
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@activity.defn
|
|
206
|
+
async def execute_team_coordination(input: ActivityExecuteTeamInput) -> dict:
|
|
207
|
+
"""
|
|
208
|
+
Execute team coordination using Agno Teams.
|
|
209
|
+
|
|
210
|
+
This activity creates an Agno Team with member Agents and executes
|
|
211
|
+
the team run, allowing Agno to handle coordination.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
input: Activity input with team execution details
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Dict with aggregated response, usage, success flag
|
|
218
|
+
"""
|
|
219
|
+
activity.logger.info(
|
|
220
|
+
f"Executing team coordination with Agno Teams",
|
|
221
|
+
extra={
|
|
222
|
+
"execution_id": input.execution_id,
|
|
223
|
+
"team_id": input.team_id,
|
|
224
|
+
"agent_count": len(input.agents),
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
# Create Agno Agent objects for each team member
|
|
230
|
+
member_agents = []
|
|
231
|
+
for agent_data in input.agents:
|
|
232
|
+
# Get model ID (default to kubiya/claude-sonnet-4 if not specified)
|
|
233
|
+
model_id = agent_data.get("model_id") or "kubiya/claude-sonnet-4"
|
|
234
|
+
|
|
235
|
+
# Get LiteLLM configuration from environment
|
|
236
|
+
litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
|
|
237
|
+
litellm_api_key = os.getenv("LITELLM_API_KEY")
|
|
238
|
+
|
|
239
|
+
# Create Agno Agent with explicit LiteLLM proxy configuration
|
|
240
|
+
# IMPORTANT: Use openai/ prefix for custom proxy compatibility (same as agno_service)
|
|
241
|
+
member_agent = Agent(
|
|
242
|
+
name=agent_data["name"],
|
|
243
|
+
role=agent_data.get("description", agent_data["name"]),
|
|
244
|
+
model=LiteLLM(
|
|
245
|
+
id=f"openai/{model_id}", # e.g., "openai/kubiya/claude-sonnet-4"
|
|
246
|
+
api_base=litellm_api_base,
|
|
247
|
+
api_key=litellm_api_key,
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
member_agents.append(member_agent)
|
|
251
|
+
|
|
252
|
+
activity.logger.info(
|
|
253
|
+
f"Created Agno Agent",
|
|
254
|
+
extra={
|
|
255
|
+
"agent_name": agent_data["name"],
|
|
256
|
+
"model": model_id,
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Create Agno Team with member agents and LiteLLM model for coordination
|
|
261
|
+
litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
|
|
262
|
+
litellm_api_key = os.getenv("LITELLM_API_KEY")
|
|
263
|
+
|
|
264
|
+
# Get coordinator model from team configuration (if specified by user in UI)
|
|
265
|
+
# Falls back to default if not configured
|
|
266
|
+
team_model = (
|
|
267
|
+
input.team_config.get("llm", {}).get("model")
|
|
268
|
+
or "kubiya/claude-sonnet-4" # Default coordinator model
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Create Team with openai/ prefix for custom proxy compatibility (same as agno_service)
|
|
272
|
+
team = Team(
|
|
273
|
+
members=member_agents,
|
|
274
|
+
name=f"Team {input.team_id}",
|
|
275
|
+
model=LiteLLM(
|
|
276
|
+
id=f"openai/{team_model}", # e.g., "openai/kubiya/claude-sonnet-4"
|
|
277
|
+
api_base=litellm_api_base,
|
|
278
|
+
api_key=litellm_api_key,
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
activity.logger.info(
|
|
283
|
+
f"Created Agno Team with {len(member_agents)} members",
|
|
284
|
+
extra={
|
|
285
|
+
"coordinator_model": team_model,
|
|
286
|
+
"member_count": len(member_agents),
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Execute team run in a thread pool since team.run() is synchronous
|
|
291
|
+
# This prevents blocking the async event loop in Temporal
|
|
292
|
+
import asyncio
|
|
293
|
+
result = await asyncio.to_thread(team.run, input.prompt)
|
|
294
|
+
|
|
295
|
+
activity.logger.info(
|
|
296
|
+
f"Agno Team execution completed",
|
|
297
|
+
extra={
|
|
298
|
+
"execution_id": input.execution_id,
|
|
299
|
+
"has_content": bool(result.content),
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Extract response content
|
|
304
|
+
response_content = result.content if hasattr(result, "content") else str(result)
|
|
305
|
+
|
|
306
|
+
# Extract usage metrics if available
|
|
307
|
+
usage = {}
|
|
308
|
+
if hasattr(result, "metrics") and result.metrics:
|
|
309
|
+
metrics = result.metrics
|
|
310
|
+
usage = {
|
|
311
|
+
"input_tokens": getattr(metrics, "input_tokens", 0),
|
|
312
|
+
"output_tokens": getattr(metrics, "output_tokens", 0),
|
|
313
|
+
"total_tokens": getattr(metrics, "total_tokens", 0),
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
"success": True,
|
|
318
|
+
"response": response_content,
|
|
319
|
+
"usage": usage,
|
|
320
|
+
"coordination_type": "agno_team",
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
activity.logger.error(
|
|
325
|
+
f"Team coordination failed",
|
|
326
|
+
extra={
|
|
327
|
+
"execution_id": input.execution_id,
|
|
328
|
+
"error": str(e),
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
return {
|
|
332
|
+
"success": False,
|
|
333
|
+
"error": str(e),
|
|
334
|
+
"coordination_type": "agno_team",
|
|
335
|
+
"usage": {},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
async def _execute_sequential(input: ActivityExecuteTeamInput) -> dict:
|
|
340
|
+
"""Execute agents sequentially"""
|
|
341
|
+
activity.logger.info("Executing team sequentially")
|
|
342
|
+
|
|
343
|
+
responses = []
|
|
344
|
+
total_usage = {}
|
|
345
|
+
|
|
346
|
+
for agent in input.agents:
|
|
347
|
+
try:
|
|
348
|
+
# Import here to avoid circular dependency
|
|
349
|
+
from control_plane_api.app.services.litellm_service import litellm_service
|
|
350
|
+
|
|
351
|
+
model = agent.get("model_id") or "kubiya/claude-sonnet-4"
|
|
352
|
+
model_config = agent.get("model_config", {})
|
|
353
|
+
|
|
354
|
+
# Execute agent
|
|
355
|
+
result = litellm_service.execute_agent(
|
|
356
|
+
prompt=f"{input.prompt}\n\nAgent role: {agent.get('description', agent['name'])}",
|
|
357
|
+
model=model,
|
|
358
|
+
system_prompt=input.system_prompt,
|
|
359
|
+
**model_config
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if result.get("success"):
|
|
363
|
+
responses.append({
|
|
364
|
+
"agent": agent["name"],
|
|
365
|
+
"response": result.get("response"),
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
# Aggregate usage
|
|
369
|
+
usage = result.get("usage", {})
|
|
370
|
+
for key, value in usage.items():
|
|
371
|
+
total_usage[key] = total_usage.get(key, 0) + value
|
|
372
|
+
|
|
373
|
+
except Exception as e:
|
|
374
|
+
activity.logger.error(
|
|
375
|
+
f"Agent execution failed",
|
|
376
|
+
extra={"agent_id": agent["id"], "error": str(e)}
|
|
377
|
+
)
|
|
378
|
+
responses.append({
|
|
379
|
+
"agent": agent["name"],
|
|
380
|
+
"error": str(e),
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
# Combine responses
|
|
384
|
+
combined_response = "\n\n".join([
|
|
385
|
+
f"**{r['agent']}**: {r.get('response', r.get('error'))}"
|
|
386
|
+
for r in responses
|
|
387
|
+
])
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
"success": True,
|
|
391
|
+
"response": combined_response,
|
|
392
|
+
"usage": total_usage,
|
|
393
|
+
"coordination_type": "sequential",
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
async def _execute_parallel(input: ActivityExecuteTeamInput) -> dict:
|
|
398
|
+
"""Execute agents in parallel (placeholder - would use asyncio.gather in real impl)"""
|
|
399
|
+
activity.logger.info("Executing team in parallel")
|
|
400
|
+
# For now, fall back to sequential
|
|
401
|
+
# In a real implementation, this would use asyncio.gather or child workflows
|
|
402
|
+
return await _execute_sequential(input)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
async def _execute_hierarchical(input: ActivityExecuteTeamInput) -> dict:
|
|
406
|
+
"""Execute agents hierarchically with a coordinator (placeholder)"""
|
|
407
|
+
activity.logger.info("Executing team hierarchically")
|
|
408
|
+
# For now, fall back to sequential
|
|
409
|
+
# In a real implementation, this would have a coordinator agent that delegates
|
|
410
|
+
return await _execute_sequential(input)
|