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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
logger = structlog.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _should_skip_event(raw_event: Any) -> bool:
|
|
10
|
+
"""Check if an event should be skipped based on langsmith:nostream tag"""
|
|
11
|
+
try:
|
|
12
|
+
# Check if the event has metadata with tags containing 'langsmith:nostream'
|
|
13
|
+
if isinstance(raw_event, tuple) and len(raw_event) >= 2:
|
|
14
|
+
# For tuple events, check the third element (metadata tuple)
|
|
15
|
+
metadata_tuple = raw_event[len(raw_event) - 1]
|
|
16
|
+
if isinstance(metadata_tuple, tuple) and len(metadata_tuple) >= 2:
|
|
17
|
+
# Get the second item in the metadata tuple
|
|
18
|
+
metadata = metadata_tuple[1]
|
|
19
|
+
if isinstance(metadata, dict) and "tags" in metadata:
|
|
20
|
+
tags = metadata["tags"]
|
|
21
|
+
if isinstance(tags, list) and "langsmith:nostream" in tags:
|
|
22
|
+
return True
|
|
23
|
+
return False
|
|
24
|
+
except Exception:
|
|
25
|
+
# If we can't parse the event structure, don't skip it
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _merge_jsonb(*objects: dict) -> dict:
|
|
30
|
+
"""Mimics PostgreSQL's JSONB merge behavior"""
|
|
31
|
+
result = {}
|
|
32
|
+
for obj in objects:
|
|
33
|
+
if obj is not None:
|
|
34
|
+
result.update(copy.deepcopy(obj))
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _filter_context_by_schema(context: dict[str, Any], context_schema: dict | None) -> dict[str, Any]:
|
|
39
|
+
"""Filter context parameters based on the context schema."""
|
|
40
|
+
if not context_schema or not context:
|
|
41
|
+
return context
|
|
42
|
+
|
|
43
|
+
# Extract valid properties from the schema
|
|
44
|
+
properties = context_schema.get("properties", {})
|
|
45
|
+
if not properties:
|
|
46
|
+
return context
|
|
47
|
+
|
|
48
|
+
# Filter context to only include parameters defined in the schema
|
|
49
|
+
filtered_context = {}
|
|
50
|
+
for key, value in context.items():
|
|
51
|
+
if key in properties:
|
|
52
|
+
filtered_context[key] = value
|
|
53
|
+
else:
|
|
54
|
+
await logger.adebug(
|
|
55
|
+
f"Filtering out context parameter '{key}' not found in context schema",
|
|
56
|
+
context_key=key,
|
|
57
|
+
available_keys=list(properties.keys()),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return filtered_context
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import logging.config
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
from aegra_api.settings import settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_logging_config() -> dict[str, Any]:
|
|
11
|
+
"""
|
|
12
|
+
Returns a unified logging configuration dictionary that uses structlog
|
|
13
|
+
for consistent, structured logging across the application and Uvicorn.
|
|
14
|
+
|
|
15
|
+
This configuration solves the multiprocessing "pickling" error on Windows
|
|
16
|
+
by using string references for streams (e.g., "ext://sys.stdout").
|
|
17
|
+
"""
|
|
18
|
+
# Determine log level from environment or set a default
|
|
19
|
+
env_mode = settings.app.ENV_MODE
|
|
20
|
+
log_level = settings.app.LOG_LEVEL
|
|
21
|
+
|
|
22
|
+
# These processors will be used by BOTH structlog and standard logging
|
|
23
|
+
# to ensure consistent output for all logs.
|
|
24
|
+
shared_processors: list[Any] = [
|
|
25
|
+
structlog.stdlib.add_log_level,
|
|
26
|
+
structlog.stdlib.add_logger_name,
|
|
27
|
+
structlog.processors.CallsiteParameterAdder(
|
|
28
|
+
{
|
|
29
|
+
structlog.processors.CallsiteParameter.FILENAME,
|
|
30
|
+
structlog.processors.CallsiteParameter.FUNC_NAME,
|
|
31
|
+
structlog.processors.CallsiteParameter.LINENO,
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
35
|
+
# This processor must be last in the shared chain to format positional args.
|
|
36
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Determine the final renderer based on the environment
|
|
40
|
+
# Use a colorful console renderer for local development, and JSON for production.
|
|
41
|
+
if env_mode in ("LOCAL", "DEVELOPMENT"):
|
|
42
|
+
final_renderer = structlog.dev.ConsoleRenderer(colors=True, pad_level=True)
|
|
43
|
+
else:
|
|
44
|
+
final_renderer = structlog.processors.JSONRenderer()
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
"version": 1,
|
|
48
|
+
"disable_existing_loggers": False, # Important for library logging
|
|
49
|
+
"formatters": {
|
|
50
|
+
"default": {
|
|
51
|
+
# Use structlog's formatter as the bridge
|
|
52
|
+
"()": "structlog.stdlib.ProcessorFormatter",
|
|
53
|
+
# The final processor is the renderer.
|
|
54
|
+
"processor": final_renderer,
|
|
55
|
+
# These processors are run on ANY log record, including those from Uvicorn.
|
|
56
|
+
"foreign_pre_chain": shared_processors,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"handlers": {
|
|
60
|
+
"default": {
|
|
61
|
+
"level": log_level,
|
|
62
|
+
"class": "logging.StreamHandler",
|
|
63
|
+
"formatter": "default",
|
|
64
|
+
# IMPORTANT: Use the string reference to avoid the pickling error.
|
|
65
|
+
# This defers the lookup of sys.stdout until the config is loaded
|
|
66
|
+
# in the child process.
|
|
67
|
+
"stream": "ext://sys.stdout",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"loggers": {
|
|
71
|
+
# Configure the root logger to catch everything
|
|
72
|
+
"": {
|
|
73
|
+
"handlers": ["default"],
|
|
74
|
+
"level": log_level,
|
|
75
|
+
"propagate": False, # Don't pass to other handlers
|
|
76
|
+
},
|
|
77
|
+
# Uvicorn's loggers will now inherit the root logger's settings,
|
|
78
|
+
# ensuring they use the same handler and formatter.
|
|
79
|
+
# We explicitly set their level here.
|
|
80
|
+
"uvicorn.error": {
|
|
81
|
+
"level": "INFO",
|
|
82
|
+
},
|
|
83
|
+
"uvicorn.access": {
|
|
84
|
+
"level": "WARNING",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def setup_logging():
|
|
91
|
+
"""
|
|
92
|
+
Configures both standard logging and structlog based on the
|
|
93
|
+
dictionary from get_logging_config(). This should be called
|
|
94
|
+
once at application startup.
|
|
95
|
+
"""
|
|
96
|
+
config = get_logging_config()
|
|
97
|
+
|
|
98
|
+
# Configure the standard logging module
|
|
99
|
+
logging.config.dictConfig(config)
|
|
100
|
+
# Propagate uvicorn logs instead of letting uvicorn configure the format
|
|
101
|
+
for name in ["uvicorn", "uvicorn.access", "uvicorn.error"]:
|
|
102
|
+
logging.getLogger(name).handlers.clear()
|
|
103
|
+
logging.getLogger(name).propagate = True
|
|
104
|
+
|
|
105
|
+
# Reconfigure log levels for some overly chatty libraries
|
|
106
|
+
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
|
107
|
+
logging.getLogger("urllib3.connectionpool").setLevel(logging.ERROR)
|
|
108
|
+
|
|
109
|
+
# Configure structlog to route its logs through the standard logging
|
|
110
|
+
# system that we just configured.
|
|
111
|
+
structlog.configure(
|
|
112
|
+
processors=[
|
|
113
|
+
structlog.stdlib.filter_by_level,
|
|
114
|
+
# Add shared processors to structlog's pipeline
|
|
115
|
+
*config["formatters"]["default"]["foreign_pre_chain"],
|
|
116
|
+
# Prepare the log record for the standard library's formatter
|
|
117
|
+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
|
118
|
+
],
|
|
119
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
120
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
121
|
+
cache_logger_on_first_use=True,
|
|
122
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
def generate_event_id(run_id: str, sequence: int) -> str:
|
|
2
|
+
"""Generate SSE event ID in the format: {run_id}_event_{sequence}
|
|
3
|
+
|
|
4
|
+
Args:
|
|
5
|
+
run_id: The run identifier
|
|
6
|
+
sequence: The event sequence number
|
|
7
|
+
|
|
8
|
+
Returns:
|
|
9
|
+
Formatted event ID string
|
|
10
|
+
"""
|
|
11
|
+
return f"{run_id}_event_{sequence}"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_event_sequence(event_id: str) -> int:
|
|
15
|
+
"""Extract numeric sequence from event_id format: {run_id}_event_{sequence}
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
event_id: The event ID string
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The sequence number, or 0 if extraction fails
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
return int(event_id.split("_event_")[-1])
|
|
25
|
+
except (ValueError, IndexError):
|
|
26
|
+
return 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Status validation utilities.
|
|
2
|
+
|
|
3
|
+
After migration, all database records have standard status values.
|
|
4
|
+
This module validates that statuses conform to the API specification.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from aegra_api.models.enums import RunStatus, ThreadStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def validate_run_status(status: str) -> RunStatus:
|
|
11
|
+
"""Validate that run status conforms to API specification.
|
|
12
|
+
|
|
13
|
+
After migration, all statuses should be standard values.
|
|
14
|
+
This function validates and rejects any invalid statuses.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
status: Status string to validate
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Validated RunStatus value
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
ValueError: If status is not a valid RunStatus
|
|
24
|
+
"""
|
|
25
|
+
valid_statuses: list[RunStatus] = [
|
|
26
|
+
"pending",
|
|
27
|
+
"running",
|
|
28
|
+
"error",
|
|
29
|
+
"success",
|
|
30
|
+
"timeout",
|
|
31
|
+
"interrupted",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
if status not in valid_statuses:
|
|
35
|
+
raise ValueError(f"Invalid run status: {status}. Must be one of: {valid_statuses}")
|
|
36
|
+
|
|
37
|
+
return status # type: ignore
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def validate_thread_status(status: str) -> ThreadStatus:
|
|
41
|
+
"""Validate that thread status conforms to API specification.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
status: Thread status string to validate
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Validated ThreadStatus value
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If status is not a valid ThreadStatus
|
|
51
|
+
"""
|
|
52
|
+
valid_statuses: list[ThreadStatus] = ["idle", "busy", "interrupted", "error"]
|
|
53
|
+
|
|
54
|
+
if status not in valid_statuses:
|
|
55
|
+
raise ValueError(f"Invalid thread status: {status}. Must be one of: {valid_statuses}")
|
|
56
|
+
|
|
57
|
+
return status # type: ignore
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aegra-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Aegra core API - Self-hosted Agent Protocol server
|
|
5
|
+
Project-URL: Homepage, https://github.com/ibbybuilds/aegra
|
|
6
|
+
Project-URL: Documentation, https://github.com/ibbybuilds/aegra#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/ibbybuilds/aegra
|
|
8
|
+
Project-URL: Issues, https://github.com/ibbybuilds/aegra/issues
|
|
9
|
+
Author-email: Muhammad Ibrahim <mibrahim37612@gmail.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: agent-protocol,agents,fastapi,langgraph,llm
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: alembic>=1.16.4
|
|
21
|
+
Requires-Dist: asgi-correlation-id>=4.3.4
|
|
22
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
23
|
+
Requires-Dist: fastapi>=0.116.1
|
|
24
|
+
Requires-Dist: greenlet>=3.2.3
|
|
25
|
+
Requires-Dist: langchain-openai>=1.0.3
|
|
26
|
+
Requires-Dist: langchain>=1.0.8
|
|
27
|
+
Requires-Dist: langgraph-checkpoint-postgres>=2.0.23
|
|
28
|
+
Requires-Dist: langgraph>=1.0.3
|
|
29
|
+
Requires-Dist: openinference-instrumentation-langchain>=0.1.58
|
|
30
|
+
Requires-Dist: opentelemetry-api>=1.39.1
|
|
31
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.39.1
|
|
32
|
+
Requires-Dist: opentelemetry-sdk>=1.39.1
|
|
33
|
+
Requires-Dist: psycopg[binary]>=3.2.9
|
|
34
|
+
Requires-Dist: pydantic-settings>=2.12.0
|
|
35
|
+
Requires-Dist: pydantic>=2.11.7
|
|
36
|
+
Requires-Dist: pyjwt>=2.10.1
|
|
37
|
+
Requires-Dist: python-dotenv>=1.1.1
|
|
38
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
39
|
+
Requires-Dist: structlog>=25.4.0
|
|
40
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# aegra-api
|
|
44
|
+
|
|
45
|
+
Aegra API - Self-hosted Agent Protocol server.
|
|
46
|
+
|
|
47
|
+
Aegra is an open-source, self-hosted alternative to LangGraph Platform. This package provides the core API server that implements the Agent Protocol, allowing you to run AI agents on your own infrastructure without vendor lock-in.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- **Agent Protocol Compliant**: Works with Agent Chat UI, LangGraph Studio, CopilotKit
|
|
52
|
+
- **Drop-in Replacement**: Compatible with the LangGraph SDK
|
|
53
|
+
- **Self-Hosted**: Run on your own PostgreSQL database
|
|
54
|
+
- **Streaming Support**: Real-time streaming of agent responses
|
|
55
|
+
- **Human-in-the-Loop**: Built-in support for human approval workflows
|
|
56
|
+
- **Vector Store**: Semantic search capabilities with PostgreSQL
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install aegra-api
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
The easiest way to get started is with the [aegra-cli](../aegra-cli/README.md):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Install the CLI
|
|
70
|
+
pip install aegra-cli
|
|
71
|
+
|
|
72
|
+
# Initialize a new project
|
|
73
|
+
aegra init --docker
|
|
74
|
+
|
|
75
|
+
# Start services
|
|
76
|
+
aegra up
|
|
77
|
+
|
|
78
|
+
# Apply migrations
|
|
79
|
+
aegra db upgrade
|
|
80
|
+
|
|
81
|
+
# Start development server
|
|
82
|
+
aegra dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Manual Setup
|
|
86
|
+
|
|
87
|
+
If you prefer manual setup:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Install dependencies
|
|
91
|
+
pip install aegra-api
|
|
92
|
+
|
|
93
|
+
# Set environment variables
|
|
94
|
+
export POSTGRES_USER=aegra
|
|
95
|
+
export POSTGRES_PASSWORD=aegra_secret
|
|
96
|
+
export POSTGRES_HOST=localhost
|
|
97
|
+
export POSTGRES_DB=aegra
|
|
98
|
+
|
|
99
|
+
# Run migrations
|
|
100
|
+
alembic upgrade head
|
|
101
|
+
|
|
102
|
+
# Start server
|
|
103
|
+
uvicorn aegra_api.main:app --reload
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
### aegra.json
|
|
109
|
+
|
|
110
|
+
Define your agent graphs in `aegra.json`:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"graphs": {
|
|
115
|
+
"agent": "./graphs/my_agent/graph.py:graph",
|
|
116
|
+
"assistant": "./graphs/assistant/graph.py:graph"
|
|
117
|
+
},
|
|
118
|
+
"http": {
|
|
119
|
+
"app": "./custom_routes.py:app"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Environment Variables
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Database
|
|
128
|
+
POSTGRES_USER=aegra
|
|
129
|
+
POSTGRES_PASSWORD=aegra_secret
|
|
130
|
+
POSTGRES_HOST=localhost
|
|
131
|
+
POSTGRES_DB=aegra
|
|
132
|
+
|
|
133
|
+
# Authentication
|
|
134
|
+
AUTH_TYPE=noop # Options: noop, custom
|
|
135
|
+
|
|
136
|
+
# Server
|
|
137
|
+
HOST=0.0.0.0
|
|
138
|
+
PORT=8000
|
|
139
|
+
|
|
140
|
+
# Configuration
|
|
141
|
+
AEGRA_CONFIG=aegra.json
|
|
142
|
+
|
|
143
|
+
# LLM (for example agents)
|
|
144
|
+
OPENAI_API_KEY=sk-...
|
|
145
|
+
|
|
146
|
+
# Observability (optional)
|
|
147
|
+
OTEL_TARGETS=LANGFUSE,PHOENIX
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API Endpoints
|
|
151
|
+
|
|
152
|
+
| Endpoint | Method | Description |
|
|
153
|
+
|----------|--------|-------------|
|
|
154
|
+
| `/assistants` | POST | Create assistant from graph_id |
|
|
155
|
+
| `/assistants` | GET | List user's assistants |
|
|
156
|
+
| `/assistants/{id}` | GET | Get assistant details |
|
|
157
|
+
| `/threads` | POST | Create conversation thread |
|
|
158
|
+
| `/threads/{id}/state` | GET | Get thread state |
|
|
159
|
+
| `/threads/{id}/runs` | POST | Execute graph (streaming/background) |
|
|
160
|
+
| `/runs/{id}/stream` | POST | Stream run events |
|
|
161
|
+
| `/store` | PUT | Save to vector store |
|
|
162
|
+
| `/store/search` | POST | Semantic search |
|
|
163
|
+
| `/health` | GET | Health check |
|
|
164
|
+
|
|
165
|
+
## Creating Graphs
|
|
166
|
+
|
|
167
|
+
Agents are Python modules exporting a compiled `graph` variable:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
# graphs/my_agent/graph.py
|
|
171
|
+
from typing import TypedDict
|
|
172
|
+
from langgraph.graph import StateGraph, START, END
|
|
173
|
+
|
|
174
|
+
class State(TypedDict):
|
|
175
|
+
messages: list[str]
|
|
176
|
+
|
|
177
|
+
def process_node(state: State) -> State:
|
|
178
|
+
messages = state.get("messages", [])
|
|
179
|
+
messages.append("Processed!")
|
|
180
|
+
return {"messages": messages}
|
|
181
|
+
|
|
182
|
+
# Build the graph
|
|
183
|
+
builder = StateGraph(State)
|
|
184
|
+
builder.add_node("process", process_node)
|
|
185
|
+
builder.add_edge(START, "process")
|
|
186
|
+
builder.add_edge("process", END)
|
|
187
|
+
|
|
188
|
+
# Export as 'graph'
|
|
189
|
+
graph = builder.compile()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Architecture
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
+---------------------------------------------------------+
|
|
196
|
+
| FastAPI HTTP Layer (Agent Protocol API) |
|
|
197
|
+
| - /assistants, /threads, /runs, /store endpoints |
|
|
198
|
+
+---------------------------------------------------------+
|
|
199
|
+
| Middleware Stack |
|
|
200
|
+
| - Auth, CORS, Structured Logging, Correlation ID |
|
|
201
|
+
+---------------------------------------------------------+
|
|
202
|
+
| Service Layer (Business Logic) |
|
|
203
|
+
| - LangGraphService, AssistantService, StreamingService |
|
|
204
|
+
+---------------------------------------------------------+
|
|
205
|
+
| LangGraph Runtime |
|
|
206
|
+
| - Graph execution, state management, tool execution |
|
|
207
|
+
+---------------------------------------------------------+
|
|
208
|
+
| Database Layer (PostgreSQL) |
|
|
209
|
+
| - AsyncPostgresSaver (checkpoints), AsyncPostgresStore |
|
|
210
|
+
+---------------------------------------------------------+
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Package Structure
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
libs/aegra-api/
|
|
217
|
+
├── src/aegra_api/
|
|
218
|
+
│ ├── api/ # Agent Protocol endpoints
|
|
219
|
+
│ │ ├── assistants.py # /assistants CRUD
|
|
220
|
+
│ │ ├── threads.py # /threads and state management
|
|
221
|
+
│ │ ├── runs.py # /runs execution and streaming
|
|
222
|
+
│ │ └── store.py # /store vector storage
|
|
223
|
+
│ ├── services/ # Business logic layer
|
|
224
|
+
│ ├── core/ # Infrastructure (database, auth, orm)
|
|
225
|
+
│ ├── models/ # Pydantic request/response schemas
|
|
226
|
+
│ ├── middleware/ # ASGI middleware
|
|
227
|
+
│ ├── observability/ # OpenTelemetry tracing
|
|
228
|
+
│ ├── utils/ # Helper functions
|
|
229
|
+
│ ├── main.py # FastAPI app entry point
|
|
230
|
+
│ ├── config.py # HTTP/store config loading
|
|
231
|
+
│ └── settings.py # Environment settings
|
|
232
|
+
├── tests/ # Test suite
|
|
233
|
+
├── alembic/ # Database migrations
|
|
234
|
+
└── pyproject.toml
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Related Packages
|
|
238
|
+
|
|
239
|
+
- **aegra-cli**: Command-line interface for project management
|
|
240
|
+
- **aegra**: Meta-package that installs both aegra-api and aegra-cli
|
|
241
|
+
|
|
242
|
+
## Documentation
|
|
243
|
+
|
|
244
|
+
For full documentation, see the [CLAUDE.md](../../CLAUDE.md) file in the repository root.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
aegra_api/__init__.py,sha256=EJeLj3vj2Jga09BGNNPx-VcPRK-fUCLvTnkSeT3UntY,76
|
|
2
|
+
aegra_api/config.py,sha256=llBQGVeAnlERmqZE4_mW5mBr3dHWrtxrmdl-O4uMADc,5657
|
|
3
|
+
aegra_api/constants.py,sha256=64ESEhwXCYjug4AXc8bR46fHrQI7pI0L0paWet1ECCI,260
|
|
4
|
+
aegra_api/main.py,sha256=8JVRfsc20QrcJyiosP75aDepxSb9k5UgbKfW7VBEf1o,10905
|
|
5
|
+
aegra_api/settings.py,sha256=eOQkyjgw4aJEwMn1FN_V7p4kcI9iv7NOlq5cEpjREvo,3487
|
|
6
|
+
aegra_api/api/__init__.py,sha256=W-8xMdTikfj8XRYLwnyjy2I54FoCCOXplhJPrARSi00,35
|
|
7
|
+
aegra_api/api/assistants.py,sha256=vnO-lDNjioqBiiwOIYCgToKZMX-iLKX7k6VjlR3IFxc,8231
|
|
8
|
+
aegra_api/api/runs.py,sha256=ikLSKwh3spEtw1nCwqRZ5aoy1N-TOrsY-soPnkXJevQ,42652
|
|
9
|
+
aegra_api/api/store.py,sha256=YbVWuasUJo2aoOSr-kPDK4a1vJrmAqBATe7yScU_6bI,6314
|
|
10
|
+
aegra_api/api/threads.py,sha256=98Y8hrzh1pZToeWcC0eCeZBg6VAvKCq9tXxKCptPzwY,28712
|
|
11
|
+
aegra_api/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
aegra_api/core/app_loader.py,sha256=mnVDWdNF140TFrW6mm3NZjIArHXJd1mIbNQ5rPEHBSk,3463
|
|
13
|
+
aegra_api/core/auth_ctx.py,sha256=bG5RhMFaglQglOVRIu5cWo9ybPfSwV4_lBTumBiQjmo,2188
|
|
14
|
+
aegra_api/core/auth_deps.py,sha256=1z-p54Av_XSlDh3HA02k4glCUXZOqLbK3wGxP6hqpys,5775
|
|
15
|
+
aegra_api/core/auth_handlers.py,sha256=tAyqqj_WwFK_P2C_fbQzWt7Phtxcq7Vzx2sGOcjne5k,8687
|
|
16
|
+
aegra_api/core/auth_middleware.py,sha256=1w72AuiypmCOmI4j5EgMJIzuI8nRyd40gT6Wp40trSE,11811
|
|
17
|
+
aegra_api/core/database.py,sha256=-vIg-m5xeAgGRXLIO94Il2snshyiFwpUsJGYY2YQ3d4,4599
|
|
18
|
+
aegra_api/core/health.py,sha256=bqfsFFuVia2WPTF2TxO8y8vNcoqaW1Qm9z-Q_c39xBU,4215
|
|
19
|
+
aegra_api/core/orm.py,sha256=yaS48OzWClD9dxbOItrEZuIJjo49BkW5puM9pJq6K54,7189
|
|
20
|
+
aegra_api/core/route_merger.py,sha256=cp_EBPmQujHkYq_G4V9DWsbDO8R0EIy-w7F6-mjuYaA,2396
|
|
21
|
+
aegra_api/core/sse.py,sha256=22S5v_Afo4BXZcVTrIe7UTUk5EDQYlhVZhLqZoiUEhM,6539
|
|
22
|
+
aegra_api/core/serializers/__init__.py,sha256=h1HrAcZoPNc8LC6lQS_c3mq1mCUdMS8TuSsERdQUDTg,320
|
|
23
|
+
aegra_api/core/serializers/base.py,sha256=DU3D8Y8YBBg-wBYngfKNQcWRexDbviLKTSg6DUARtGI,601
|
|
24
|
+
aegra_api/core/serializers/general.py,sha256=6z7UVLub4Or4jTlMiCD2QvvXTtVgxx2AdG6aV6ScaGE,2153
|
|
25
|
+
aegra_api/core/serializers/langgraph.py,sha256=rn6TVbd0_uiPIdBzp2RgQPq8JaXctaP-ZQx_yNduTnE,3960
|
|
26
|
+
aegra_api/middleware/__init__.py,sha256=Al0DGup5VcvUpnyy5R15bjjCuWbITcvuqFBWvavTF0w,218
|
|
27
|
+
aegra_api/middleware/double_encoded_json.py,sha256=c1HWcETWSXKAVdikxMEbvbQSjoSH0dLeMQVSY0L_jtM,2871
|
|
28
|
+
aegra_api/middleware/logger_middleware.py,sha256=KmiE2MPeKsed_TBhR5eGrQB6y3p9tryF_tuKp1P2ZZg,3904
|
|
29
|
+
aegra_api/models/__init__.py,sha256=1veRmyR-lHa8XMbUPdmjToyuXmR5ho9X5__Y7SUT2w4,1684
|
|
30
|
+
aegra_api/models/assistants.py,sha256=caPK028uVwcJXRQrg_wr_1qdcexSBgtOHdumCDVgImI,3547
|
|
31
|
+
aegra_api/models/auth.py,sha256=Ce7zdYqLe_TPsSLklPuoAr43ifhjkTAIHrtEvZCo1Ng,1659
|
|
32
|
+
aegra_api/models/enums.py,sha256=ukmJ1eaxmS8BtCQJ6TIfed2ZEypc_4M2ws9u9ivCBfE,434
|
|
33
|
+
aegra_api/models/errors.py,sha256=GhzORUu90i3iBwXLrirsXeLs4vbrSKd-Hh6X6sYG180,855
|
|
34
|
+
aegra_api/models/runs.py,sha256=7-ZJ5jdiY2g5HIc6cUHUYEBCyA2zBfLP3tso-cPo55Q,4444
|
|
35
|
+
aegra_api/models/store.py,sha256=jWNS02Hj-vTf6dhlrbTy-BVyKJHvBeCnEtN4Bdia4Jw,1936
|
|
36
|
+
aegra_api/models/threads.py,sha256=EUqSWUuVD9aahk-g9fw8auFY7KrTgN8HRC2mzAu6kVU,5852
|
|
37
|
+
aegra_api/observability/__init__.py,sha256=Ztbfd9zNj8IwSSl3K02DdavIdSXeeS9wTOwsrDolW0o,49
|
|
38
|
+
aegra_api/observability/base.py,sha256=Xthdf9yqSwRFPjpuJRjc8AU5TD222_CuVxDOCYkpN68,3068
|
|
39
|
+
aegra_api/observability/otel.py,sha256=7XjtzivwZtkACY_HM409qX7GpMq-oyDcY7u6S1PT5kE,4485
|
|
40
|
+
aegra_api/observability/setup.py,sha256=yw0-xdH2y1ihCleVz8fYtwQtLVwzeaJxZgm0oqliB9I,886
|
|
41
|
+
aegra_api/observability/targets/__init__.py,sha256=2csgqVutYZhLWyiqJS1LpplS-ViyIjD62z_MSo4K-uw,370
|
|
42
|
+
aegra_api/observability/targets/base.py,sha256=kzvvU5IvuukXnJRAY9qiNeUSprCf_oaj1JGVtlAxzfA,469
|
|
43
|
+
aegra_api/observability/targets/langfuse.py,sha256=RBcVec9WjI9-dnm079qcZfoboXpqd9bNvnPlECw5Cb4,1010
|
|
44
|
+
aegra_api/observability/targets/otlp.py,sha256=U3tPoFJOy_I73DPe-ZuzcxnFOUk0rtVp4KFIPWuyRdo,1222
|
|
45
|
+
aegra_api/observability/targets/phoenix.py,sha256=l5y4qK13DZm3OLX0XEGqf-X2_DUkCXDDv0c3Iq5I4cQ,736
|
|
46
|
+
aegra_api/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
+
aegra_api/services/assistant_service.py,sha256=d1h3r0l3q8HEiux9Hjf_Pj8xWu5WqpMe6PajHuMBX28,21810
|
|
48
|
+
aegra_api/services/base_broker.py,sha256=Tt7ddwxG0kAqgi5Su3ooWXjwCRQ5Sb4-v9Q6S8njf90,1700
|
|
49
|
+
aegra_api/services/broker.py,sha256=yxCE1Nt6ZhA4qBUubqN1_xG9zqb-AY3eZZf8STvuUzs,5226
|
|
50
|
+
aegra_api/services/event_converter.py,sha256=Mo6nmJDQVGEm2n4_mnAOkXGR3Y8LPZzetZaWQq1cDSA,6810
|
|
51
|
+
aegra_api/services/event_store.py,sha256=bQBSsFwhHm717Tus4BpLZkjAW9KXZuR2fa_5iUK8JCY,6878
|
|
52
|
+
aegra_api/services/graph_streaming.py,sha256=nmtOVkPP5HE1qV9PNREY_9YPRngyLyxoWKyeQz4hMoc,16392
|
|
53
|
+
aegra_api/services/langgraph_service.py,sha256=UXTVdgFeGmkBXerJel86MQhd4IVueHEP_c8ShHpDcC0,17812
|
|
54
|
+
aegra_api/services/streaming_service.py,sha256=YPxYqU_-PI84j_4qqz4JcDA9Ee2zNRofRbHLhVtdrnw,13708
|
|
55
|
+
aegra_api/services/thread_state_service.py,sha256=cDfCrkRKonZUhu3ihVjKtSiPepSLNVuOIj02zIUpgy0,5340
|
|
56
|
+
aegra_api/utils/__init__.py,sha256=6NGAF-Drv51Ai6jp0SrlpodO5dCO9PuqAxha3sCMbDY,139
|
|
57
|
+
aegra_api/utils/assistants.py,sha256=gQOQ4B7-Coy4FGekutEj-PxMS2leKjLUa1m66PB-1SM,852
|
|
58
|
+
aegra_api/utils/run_utils.py,sha256=8sx0I3loB7OnJ4e8gXBL21Ykcz4i14yYxWrEJ0H_gHE,2143
|
|
59
|
+
aegra_api/utils/setup_logging.py,sha256=JWx9GYadV30E6xhsMNHzr3OxMSQx-5pm0SuGDreLyCY,4723
|
|
60
|
+
aegra_api/utils/sse_utils.py,sha256=gGUZr5lnK2j9XqKevs0Q2-cN5ks76Us3a8unS0RVw1Y,684
|
|
61
|
+
aegra_api/utils/status_compat.py,sha256=eO-kuug3nr6E2JuARYhFVc3pQm1pNaOPCNVLi-UTmiI,1543
|
|
62
|
+
aegra_api-0.1.0.dist-info/METADATA,sha256=cOPn4v2YuXe2xvU7xJIy49KGd43YtrTzD4AP3jKNvaA,7293
|
|
63
|
+
aegra_api-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
64
|
+
aegra_api-0.1.0.dist-info/RECORD,,
|