aegra-api 0.1.0__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.
- aegra_api/__init__.py +3 -0
- aegra_api/api/__init__.py +1 -0
- aegra_api/api/assistants.py +235 -0
- aegra_api/api/runs.py +1110 -0
- aegra_api/api/store.py +200 -0
- aegra_api/api/threads.py +761 -0
- aegra_api/config.py +204 -0
- aegra_api/constants.py +5 -0
- aegra_api/core/__init__.py +0 -0
- aegra_api/core/app_loader.py +91 -0
- aegra_api/core/auth_ctx.py +65 -0
- aegra_api/core/auth_deps.py +186 -0
- aegra_api/core/auth_handlers.py +248 -0
- aegra_api/core/auth_middleware.py +331 -0
- aegra_api/core/database.py +123 -0
- aegra_api/core/health.py +131 -0
- aegra_api/core/orm.py +165 -0
- aegra_api/core/route_merger.py +69 -0
- aegra_api/core/serializers/__init__.py +7 -0
- aegra_api/core/serializers/base.py +22 -0
- aegra_api/core/serializers/general.py +54 -0
- aegra_api/core/serializers/langgraph.py +102 -0
- aegra_api/core/sse.py +178 -0
- aegra_api/main.py +303 -0
- aegra_api/middleware/__init__.py +4 -0
- aegra_api/middleware/double_encoded_json.py +74 -0
- aegra_api/middleware/logger_middleware.py +95 -0
- aegra_api/models/__init__.py +76 -0
- aegra_api/models/assistants.py +81 -0
- aegra_api/models/auth.py +62 -0
- aegra_api/models/enums.py +29 -0
- aegra_api/models/errors.py +29 -0
- aegra_api/models/runs.py +124 -0
- aegra_api/models/store.py +67 -0
- aegra_api/models/threads.py +152 -0
- aegra_api/observability/__init__.py +1 -0
- aegra_api/observability/base.py +88 -0
- aegra_api/observability/otel.py +133 -0
- aegra_api/observability/setup.py +27 -0
- aegra_api/observability/targets/__init__.py +11 -0
- aegra_api/observability/targets/base.py +18 -0
- aegra_api/observability/targets/langfuse.py +33 -0
- aegra_api/observability/targets/otlp.py +38 -0
- aegra_api/observability/targets/phoenix.py +24 -0
- aegra_api/services/__init__.py +0 -0
- aegra_api/services/assistant_service.py +569 -0
- aegra_api/services/base_broker.py +59 -0
- aegra_api/services/broker.py +141 -0
- aegra_api/services/event_converter.py +157 -0
- aegra_api/services/event_store.py +196 -0
- aegra_api/services/graph_streaming.py +433 -0
- aegra_api/services/langgraph_service.py +456 -0
- aegra_api/services/streaming_service.py +362 -0
- aegra_api/services/thread_state_service.py +128 -0
- aegra_api/settings.py +124 -0
- aegra_api/utils/__init__.py +3 -0
- aegra_api/utils/assistants.py +23 -0
- aegra_api/utils/run_utils.py +60 -0
- aegra_api/utils/setup_logging.py +122 -0
- aegra_api/utils/sse_utils.py +26 -0
- aegra_api/utils/status_compat.py +57 -0
- aegra_api-0.1.0.dist-info/METADATA +244 -0
- aegra_api-0.1.0.dist-info/RECORD +64 -0
- aegra_api-0.1.0.dist-info/WHEEL +4 -0
aegra_api/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent Protocol API endpoints"""
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Assistant endpoints for Agent Protocol
|
|
2
|
+
|
|
3
|
+
NOTE: This API follows a layered architecture pattern with business logic
|
|
4
|
+
separated into a service layer (assistant_service.py). This was the first
|
|
5
|
+
API to be refactored, and the plan is to gradually refactor all other APIs
|
|
6
|
+
(runs, threads, etc.) to follow this same pattern for better code
|
|
7
|
+
organization, testability, and maintainability.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
- API Layer (this file): Thin FastAPI route handlers, request/response handling
|
|
11
|
+
- Service Layer (assistant_service.py): Business logic, validation, orchestration
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from fastapi import APIRouter, Body, Depends
|
|
15
|
+
|
|
16
|
+
from aegra_api.core.auth_deps import get_current_user
|
|
17
|
+
from aegra_api.core.auth_handlers import build_auth_context, handle_event
|
|
18
|
+
from aegra_api.models import (
|
|
19
|
+
Assistant,
|
|
20
|
+
AssistantCreate,
|
|
21
|
+
AssistantList,
|
|
22
|
+
AssistantSearchRequest,
|
|
23
|
+
AssistantUpdate,
|
|
24
|
+
User,
|
|
25
|
+
)
|
|
26
|
+
from aegra_api.services.assistant_service import AssistantService, get_assistant_service
|
|
27
|
+
|
|
28
|
+
router = APIRouter(tags=["Assistants"])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.post("/assistants", response_model=Assistant, response_model_by_alias=False)
|
|
32
|
+
async def create_assistant(
|
|
33
|
+
request: AssistantCreate,
|
|
34
|
+
user: User = Depends(get_current_user),
|
|
35
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
36
|
+
):
|
|
37
|
+
"""Create a new assistant"""
|
|
38
|
+
# Authorization check
|
|
39
|
+
ctx = build_auth_context(user, "assistants", "create")
|
|
40
|
+
value = request.model_dump()
|
|
41
|
+
filters = await handle_event(ctx, value)
|
|
42
|
+
|
|
43
|
+
# If handler modified metadata, update request
|
|
44
|
+
if filters and "metadata" in filters:
|
|
45
|
+
request.metadata = {**(request.metadata or {}), **filters["metadata"]}
|
|
46
|
+
elif value.get("metadata"):
|
|
47
|
+
request.metadata = {**(request.metadata or {}), **value["metadata"]}
|
|
48
|
+
|
|
49
|
+
return await service.create_assistant(request, user.identity)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.get("/assistants", response_model=AssistantList, response_model_by_alias=False)
|
|
53
|
+
async def list_assistants(
|
|
54
|
+
user: User = Depends(get_current_user),
|
|
55
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
56
|
+
):
|
|
57
|
+
"""List user's assistants"""
|
|
58
|
+
# Authorization check (search action for listing)
|
|
59
|
+
ctx = build_auth_context(user, "assistants", "search")
|
|
60
|
+
value = {}
|
|
61
|
+
filters = await handle_event(ctx, value)
|
|
62
|
+
|
|
63
|
+
# Apply filters if provided by handler
|
|
64
|
+
if filters:
|
|
65
|
+
# Convert filters to search request format
|
|
66
|
+
search_request = AssistantSearchRequest(filters=filters)
|
|
67
|
+
assistants = await service.search_assistants(search_request, user.identity)
|
|
68
|
+
else:
|
|
69
|
+
assistants = await service.list_assistants(user.identity)
|
|
70
|
+
|
|
71
|
+
return AssistantList(assistants=assistants, total=len(assistants))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@router.post("/assistants/search", response_model=list[Assistant], response_model_by_alias=False)
|
|
75
|
+
async def search_assistants(
|
|
76
|
+
request: AssistantSearchRequest,
|
|
77
|
+
user: User = Depends(get_current_user),
|
|
78
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
79
|
+
):
|
|
80
|
+
"""Search assistants with filters"""
|
|
81
|
+
# Authorization check
|
|
82
|
+
ctx = build_auth_context(user, "assistants", "search")
|
|
83
|
+
value = request.model_dump()
|
|
84
|
+
filters = await handle_event(ctx, value)
|
|
85
|
+
|
|
86
|
+
# Merge handler filters with request filters
|
|
87
|
+
if filters:
|
|
88
|
+
request_filters = request.filters or {}
|
|
89
|
+
request.filters = {**request_filters, **filters}
|
|
90
|
+
|
|
91
|
+
return await service.search_assistants(request, user.identity)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.post("/assistants/count", response_model=int)
|
|
95
|
+
async def count_assistants(
|
|
96
|
+
request: AssistantSearchRequest,
|
|
97
|
+
user: User = Depends(get_current_user),
|
|
98
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
99
|
+
):
|
|
100
|
+
"""Count assistants with filters"""
|
|
101
|
+
# Authorization check (search action for counting)
|
|
102
|
+
ctx = build_auth_context(user, "assistants", "search")
|
|
103
|
+
value = request.model_dump()
|
|
104
|
+
filters = await handle_event(ctx, value)
|
|
105
|
+
|
|
106
|
+
# Merge handler filters with request filters
|
|
107
|
+
if filters:
|
|
108
|
+
request_filters = request.filters or {}
|
|
109
|
+
request.filters = {**request_filters, **filters}
|
|
110
|
+
|
|
111
|
+
return await service.count_assistants(request, user.identity)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@router.get(
|
|
115
|
+
"/assistants/{assistant_id}",
|
|
116
|
+
response_model=Assistant,
|
|
117
|
+
response_model_by_alias=False,
|
|
118
|
+
)
|
|
119
|
+
async def get_assistant(
|
|
120
|
+
assistant_id: str,
|
|
121
|
+
user: User = Depends(get_current_user),
|
|
122
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
123
|
+
):
|
|
124
|
+
"""Get assistant by ID"""
|
|
125
|
+
# Authorization check
|
|
126
|
+
ctx = build_auth_context(user, "assistants", "read")
|
|
127
|
+
value = {"assistant_id": assistant_id}
|
|
128
|
+
await handle_event(ctx, value)
|
|
129
|
+
|
|
130
|
+
return await service.get_assistant(assistant_id, user.identity)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@router.patch(
|
|
134
|
+
"/assistants/{assistant_id}",
|
|
135
|
+
response_model=Assistant,
|
|
136
|
+
response_model_by_alias=False,
|
|
137
|
+
)
|
|
138
|
+
async def update_assistant(
|
|
139
|
+
assistant_id: str,
|
|
140
|
+
request: AssistantUpdate,
|
|
141
|
+
user: User = Depends(get_current_user),
|
|
142
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
143
|
+
):
|
|
144
|
+
"""Update assistant by ID"""
|
|
145
|
+
# Authorization check
|
|
146
|
+
ctx = build_auth_context(user, "assistants", "update")
|
|
147
|
+
value = {**request.model_dump(), "assistant_id": assistant_id}
|
|
148
|
+
filters = await handle_event(ctx, value)
|
|
149
|
+
|
|
150
|
+
# If handler modified metadata, update request
|
|
151
|
+
if filters and "metadata" in filters:
|
|
152
|
+
request.metadata = {**(request.metadata or {}), **filters["metadata"]}
|
|
153
|
+
elif value.get("metadata"):
|
|
154
|
+
request.metadata = {**(request.metadata or {}), **value["metadata"]}
|
|
155
|
+
|
|
156
|
+
return await service.update_assistant(assistant_id, request, user.identity)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@router.delete("/assistants/{assistant_id}")
|
|
160
|
+
async def delete_assistant(
|
|
161
|
+
assistant_id: str,
|
|
162
|
+
user: User = Depends(get_current_user),
|
|
163
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
164
|
+
):
|
|
165
|
+
"""Delete assistant by ID"""
|
|
166
|
+
# Authorization check
|
|
167
|
+
ctx = build_auth_context(user, "assistants", "delete")
|
|
168
|
+
value = {"assistant_id": assistant_id}
|
|
169
|
+
await handle_event(ctx, value)
|
|
170
|
+
|
|
171
|
+
return await service.delete_assistant(assistant_id, user.identity)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@router.post(
|
|
175
|
+
"/assistants/{assistant_id}/latest",
|
|
176
|
+
response_model=Assistant,
|
|
177
|
+
response_model_by_alias=False,
|
|
178
|
+
)
|
|
179
|
+
async def set_assistant_latest(
|
|
180
|
+
assistant_id: str,
|
|
181
|
+
version: int = Body(..., embed=True, description="The version number to set as latest"),
|
|
182
|
+
user: User = Depends(get_current_user),
|
|
183
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
184
|
+
):
|
|
185
|
+
"""Set the given version as the latest version of an assistant"""
|
|
186
|
+
return await service.set_assistant_latest(assistant_id, version, user.identity)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@router.post(
|
|
190
|
+
"/assistants/{assistant_id}/versions",
|
|
191
|
+
response_model=list[Assistant],
|
|
192
|
+
response_model_by_alias=False,
|
|
193
|
+
)
|
|
194
|
+
async def list_assistant_versions(
|
|
195
|
+
assistant_id: str,
|
|
196
|
+
user: User = Depends(get_current_user),
|
|
197
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
198
|
+
):
|
|
199
|
+
"""List all versions of an assistant"""
|
|
200
|
+
return await service.list_assistant_versions(assistant_id, user.identity)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@router.get("/assistants/{assistant_id}/schemas")
|
|
204
|
+
async def get_assistant_schemas(
|
|
205
|
+
assistant_id: str,
|
|
206
|
+
user: User = Depends(get_current_user),
|
|
207
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
208
|
+
):
|
|
209
|
+
"""Get input, output, state, config and context schemas for an assistant"""
|
|
210
|
+
return await service.get_assistant_schemas(assistant_id, user.identity)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@router.get("/assistants/{assistant_id}/graph")
|
|
214
|
+
async def get_assistant_graph(
|
|
215
|
+
assistant_id: str,
|
|
216
|
+
xray: bool | int | None = None,
|
|
217
|
+
user: User = Depends(get_current_user),
|
|
218
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
219
|
+
):
|
|
220
|
+
"""Get the graph structure for visualization"""
|
|
221
|
+
# Default to False if not provided
|
|
222
|
+
xray_value = xray if xray is not None else False
|
|
223
|
+
return await service.get_assistant_graph(assistant_id, xray_value, user.identity)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@router.get("/assistants/{assistant_id}/subgraphs")
|
|
227
|
+
async def get_assistant_subgraphs(
|
|
228
|
+
assistant_id: str,
|
|
229
|
+
recurse: bool = False,
|
|
230
|
+
namespace: str | None = None,
|
|
231
|
+
user: User = Depends(get_current_user),
|
|
232
|
+
service: AssistantService = Depends(get_assistant_service),
|
|
233
|
+
):
|
|
234
|
+
"""Get subgraphs of an assistant"""
|
|
235
|
+
return await service.get_assistant_subgraphs(assistant_id, namespace, recurse, user.identity)
|