hyperforge 1.0.0.post19__tar.gz
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.
- hyperforge-1.0.0.post19/PKG-INFO +95 -0
- hyperforge-1.0.0.post19/README.md +54 -0
- hyperforge-1.0.0.post19/pyproject.toml +85 -0
- hyperforge-1.0.0.post19/setup.cfg +4 -0
- hyperforge-1.0.0.post19/src/hyperforge/__init__.py +16 -0
- hyperforge-1.0.0.post19/src/hyperforge/agent.py +81 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/__init__.py +20 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/app.py +155 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/authentication.py +271 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/commands.py +33 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/internal/__init__.py +4 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/internal/inspect.py +30 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/internal/router.py +3 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/logging.py +18 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/models.py +129 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/session.py +197 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/settings.py +38 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/utils.py +354 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/__init__.py +23 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/agents.py +531 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/interaction.py +430 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/mcp_content.py +311 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/mcp_interaction.py +322 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/oauth.py +60 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/prompt.py +129 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/router.py +3 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/schema.py +56 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/session.py +182 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/utils.py +12 -0
- hyperforge-1.0.0.post19/src/hyperforge/api/v1/workflows.py +643 -0
- hyperforge-1.0.0.post19/src/hyperforge/arag.py +28 -0
- hyperforge-1.0.0.post19/src/hyperforge/broker/__init__.py +52 -0
- hyperforge-1.0.0.post19/src/hyperforge/broker/local.py +116 -0
- hyperforge-1.0.0.post19/src/hyperforge/broker/redis.py +161 -0
- hyperforge-1.0.0.post19/src/hyperforge/configure.py +571 -0
- hyperforge-1.0.0.post19/src/hyperforge/context/__init__.py +0 -0
- hyperforge-1.0.0.post19/src/hyperforge/context/agent.py +377 -0
- hyperforge-1.0.0.post19/src/hyperforge/context/config.py +103 -0
- hyperforge-1.0.0.post19/src/hyperforge/database.py +3 -0
- hyperforge-1.0.0.post19/src/hyperforge/db/__init__.py +6 -0
- hyperforge-1.0.0.post19/src/hyperforge/db/agents.py +1521 -0
- hyperforge-1.0.0.post19/src/hyperforge/db/encryption.py +91 -0
- hyperforge-1.0.0.post19/src/hyperforge/db/exceptions.py +26 -0
- hyperforge-1.0.0.post19/src/hyperforge/db/settings.py +16 -0
- hyperforge-1.0.0.post19/src/hyperforge/db/workflow_cleanup.py +69 -0
- hyperforge-1.0.0.post19/src/hyperforge/definition.py +13 -0
- hyperforge-1.0.0.post19/src/hyperforge/driver.py +31 -0
- hyperforge-1.0.0.post19/src/hyperforge/dummy.py +28 -0
- hyperforge-1.0.0.post19/src/hyperforge/engine.py +189 -0
- hyperforge-1.0.0.post19/src/hyperforge/exceptions.py +14 -0
- hyperforge-1.0.0.post19/src/hyperforge/feature_flag.py +105 -0
- hyperforge-1.0.0.post19/src/hyperforge/fixtures.py +602 -0
- hyperforge-1.0.0.post19/src/hyperforge/interaction.py +116 -0
- hyperforge-1.0.0.post19/src/hyperforge/llm.py +75 -0
- hyperforge-1.0.0.post19/src/hyperforge/manager.py +432 -0
- hyperforge-1.0.0.post19/src/hyperforge/memory/__init__.py +5 -0
- hyperforge-1.0.0.post19/src/hyperforge/memory/memory.py +974 -0
- hyperforge-1.0.0.post19/src/hyperforge/minimal_fixtures.py +75 -0
- hyperforge-1.0.0.post19/src/hyperforge/models.py +336 -0
- hyperforge-1.0.0.post19/src/hyperforge/nua.py +336 -0
- hyperforge-1.0.0.post19/src/hyperforge/openapi.py +63 -0
- hyperforge-1.0.0.post19/src/hyperforge/prompts.py +188 -0
- hyperforge-1.0.0.post19/src/hyperforge/pubsub.py +90 -0
- hyperforge-1.0.0.post19/src/hyperforge/py.typed +0 -0
- hyperforge-1.0.0.post19/src/hyperforge/redis_utils.py +82 -0
- hyperforge-1.0.0.post19/src/hyperforge/retrieval/__init__.py +0 -0
- hyperforge-1.0.0.post19/src/hyperforge/retrieval/agent.py +169 -0
- hyperforge-1.0.0.post19/src/hyperforge/retrieval/config.py +94 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/__init__.py +5 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/cache.py +131 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/run.py +109 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/sandbox.py +60 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/session.py +421 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/settings.py +47 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/utils.py +57 -0
- hyperforge-1.0.0.post19/src/hyperforge/server/web.py +31 -0
- hyperforge-1.0.0.post19/src/hyperforge/settings.py +18 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/__init__.py +5 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/agent.py +189 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/app.py +264 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/config.py +137 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/const.py +1 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/run.py +60 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/settings.py +133 -0
- hyperforge-1.0.0.post19/src/hyperforge/standalone/ui_router.py +241 -0
- hyperforge-1.0.0.post19/src/hyperforge/trace.py +42 -0
- hyperforge-1.0.0.post19/src/hyperforge/utils/__init__.py +112 -0
- hyperforge-1.0.0.post19/src/hyperforge/utils/http.py +48 -0
- hyperforge-1.0.0.post19/src/hyperforge/workflows.py +44 -0
- hyperforge-1.0.0.post19/src/hyperforge.egg-info/PKG-INFO +95 -0
- hyperforge-1.0.0.post19/src/hyperforge.egg-info/SOURCES.txt +96 -0
- hyperforge-1.0.0.post19/src/hyperforge.egg-info/dependency_links.txt +1 -0
- hyperforge-1.0.0.post19/src/hyperforge.egg-info/entry_points.txt +8 -0
- hyperforge-1.0.0.post19/src/hyperforge.egg-info/requires.txt +27 -0
- hyperforge-1.0.0.post19/src/hyperforge.egg-info/top_level.txt +1 -0
- hyperforge-1.0.0.post19/tests/test_convert_arag_answer.py +573 -0
- hyperforge-1.0.0.post19/tests/test_mcp_interaction.py +311 -0
- hyperforge-1.0.0.post19/tests/test_next.py +98 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyperforge
|
|
3
|
+
Version: 1.0.0.post19
|
|
4
|
+
Summary: Agentic Framework for Orchestrated Runtime, Governance, and Execution
|
|
5
|
+
Author-email: AI Data Team <learning@nuclia.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Progress, https://progress.com
|
|
8
|
+
Project-URL: Github, https://github.com/nuclia/forge
|
|
9
|
+
Project-URL: API Reference, https://docs.rag.progress.cloud
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: alembic
|
|
15
|
+
Requires-Dist: pydantic
|
|
16
|
+
Requires-Dist: nuclia>=4.9.25
|
|
17
|
+
Requires-Dist: nuclia-models>=0.52.1
|
|
18
|
+
Requires-Dist: nucliadb-models>=6.13.1.post6414
|
|
19
|
+
Requires-Dist: nucliadb-sdk>=6.13.1.post6414
|
|
20
|
+
Requires-Dist: nucliadb-telemetry[otel]
|
|
21
|
+
Requires-Dist: pydantic_settings
|
|
22
|
+
Requires-Dist: mcp>=1.26.0
|
|
23
|
+
Requires-Dist: prometheus_client
|
|
24
|
+
Requires-Dist: mrflagly>=0.2.11
|
|
25
|
+
Requires-Dist: aiodns
|
|
26
|
+
Requires-Dist: httpx
|
|
27
|
+
Requires-Dist: jinja2
|
|
28
|
+
Requires-Dist: databases[asyncpg]
|
|
29
|
+
Requires-Dist: sqlalchemy
|
|
30
|
+
Requires-Dist: cryptography
|
|
31
|
+
Requires-Dist: mmh3
|
|
32
|
+
Requires-Dist: psycopg2
|
|
33
|
+
Requires-Dist: tenacity
|
|
34
|
+
Requires-Dist: websockets
|
|
35
|
+
Requires-Dist: redis[hiredis]>=6.4.0
|
|
36
|
+
Requires-Dist: types-redis>=4.6.0.20241004
|
|
37
|
+
Requires-Dist: ruff>=0.15.10
|
|
38
|
+
Requires-Dist: lru-dict
|
|
39
|
+
Requires-Dist: sentry_sdk
|
|
40
|
+
Requires-Dist: fastapi
|
|
41
|
+
|
|
42
|
+
# Hyperforge
|
|
43
|
+
|
|
44
|
+
Hyperforge is the core agentic workflow framework in this repository. It
|
|
45
|
+
provides the runtime, HTTP API, workflow orchestration, broker integration,
|
|
46
|
+
persistence layer, and support for loading Hyperforge agent packages.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
From the workspace root:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv sync
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Run
|
|
57
|
+
|
|
58
|
+
Start the API service:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv run hyperforge-api
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Useful endpoints:
|
|
65
|
+
|
|
66
|
+
- `/health/ready`
|
|
67
|
+
- `/health/alive`
|
|
68
|
+
- `/metrics`
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
Runtime configuration is provided through environment variables consumed by
|
|
73
|
+
Pydantic settings. Common settings include:
|
|
74
|
+
|
|
75
|
+
- `HTTP_HOST` and `HTTP_PORT`
|
|
76
|
+
- `MEMORY_READER_NUCLIADB`, `MEMORY_WRITER_NUCLIADB`, and
|
|
77
|
+
`MEMORY_SEARCH_NUCLIADB`
|
|
78
|
+
- `MEMORY_APIKEY_NUCLIADB`
|
|
79
|
+
- `VALKEY_URL`
|
|
80
|
+
- `LOAD_MODULES`
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
Run the package tests from the workspace root:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv run pytest hyperforge
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Format and lint with the root `Makefile`:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
make fmt
|
|
94
|
+
make lint
|
|
95
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Hyperforge
|
|
2
|
+
|
|
3
|
+
Hyperforge is the core agentic workflow framework in this repository. It
|
|
4
|
+
provides the runtime, HTTP API, workflow orchestration, broker integration,
|
|
5
|
+
persistence layer, and support for loading Hyperforge agent packages.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
From the workspace root:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv sync
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Run
|
|
16
|
+
|
|
17
|
+
Start the API service:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run hyperforge-api
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Useful endpoints:
|
|
24
|
+
|
|
25
|
+
- `/health/ready`
|
|
26
|
+
- `/health/alive`
|
|
27
|
+
- `/metrics`
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
Runtime configuration is provided through environment variables consumed by
|
|
32
|
+
Pydantic settings. Common settings include:
|
|
33
|
+
|
|
34
|
+
- `HTTP_HOST` and `HTTP_PORT`
|
|
35
|
+
- `MEMORY_READER_NUCLIADB`, `MEMORY_WRITER_NUCLIADB`, and
|
|
36
|
+
`MEMORY_SEARCH_NUCLIADB`
|
|
37
|
+
- `MEMORY_APIKEY_NUCLIADB`
|
|
38
|
+
- `VALKEY_URL`
|
|
39
|
+
- `LOAD_MODULES`
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
Run the package tests from the workspace root:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run pytest hyperforge
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Format and lint with the root `Makefile`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
make fmt
|
|
53
|
+
make lint
|
|
54
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hyperforge"
|
|
7
|
+
version = "1.0.0.post19"
|
|
8
|
+
authors = [{ name = "AI Data Team", email = "learning@nuclia.com" }]
|
|
9
|
+
description = "Agentic Framework for Orchestrated Runtime, Governance, and Execution"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
]
|
|
16
|
+
license = "Apache-2.0"
|
|
17
|
+
dependencies = [
|
|
18
|
+
"alembic",
|
|
19
|
+
"pydantic",
|
|
20
|
+
"nuclia>=4.9.25",
|
|
21
|
+
"nuclia-models>=0.52.1",
|
|
22
|
+
"nucliadb-models>=6.13.1.post6414",
|
|
23
|
+
"nucliadb-sdk>=6.13.1.post6414",
|
|
24
|
+
"nucliadb-telemetry[otel]",
|
|
25
|
+
"pydantic_settings",
|
|
26
|
+
"mcp>=1.26.0",
|
|
27
|
+
"prometheus_client",
|
|
28
|
+
"mrflagly>=0.2.11",
|
|
29
|
+
"aiodns",
|
|
30
|
+
"httpx",
|
|
31
|
+
"jinja2",
|
|
32
|
+
"databases[asyncpg]",
|
|
33
|
+
"sqlalchemy",
|
|
34
|
+
"cryptography",
|
|
35
|
+
"mmh3",
|
|
36
|
+
"psycopg2",
|
|
37
|
+
"tenacity",
|
|
38
|
+
"websockets",
|
|
39
|
+
"redis[hiredis]>=6.4.0",
|
|
40
|
+
"types-redis>=4.6.0.20241004",
|
|
41
|
+
"ruff>=0.15.10",
|
|
42
|
+
"lru-dict",
|
|
43
|
+
"sentry_sdk",
|
|
44
|
+
"fastapi",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
dev = [
|
|
50
|
+
"pytest",
|
|
51
|
+
"pytest-asyncio",
|
|
52
|
+
"pytest-cov",
|
|
53
|
+
"pytest-mock",
|
|
54
|
+
"pytest-benchmark",
|
|
55
|
+
"pytest-docker-fixtures>=1.4.2",
|
|
56
|
+
"pytest-lazy-fixtures",
|
|
57
|
+
"httpx[http2]",
|
|
58
|
+
"hyperforge_rephrase",
|
|
59
|
+
"inline_snapshot",
|
|
60
|
+
"sqlalchemy_utils",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.packages.find]
|
|
64
|
+
where = ["src/"]
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.package-data]
|
|
67
|
+
hyperforge = ["frontend/dist/*"]
|
|
68
|
+
|
|
69
|
+
[project.urls]
|
|
70
|
+
"Progress" = "https://progress.com"
|
|
71
|
+
"Github" = "https://github.com/nuclia/forge"
|
|
72
|
+
"API Reference" = "https://docs.rag.progress.cloud"
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
asyncio_mode = "auto"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
[project.scripts]
|
|
79
|
+
hyperforge-api = "hyperforge.api:commands.run"
|
|
80
|
+
hyperforge-server = "hyperforge.server:run.run"
|
|
81
|
+
hyperforge-sandbox = "hyperforge.sandbox:run.run"
|
|
82
|
+
hyperforge-extract-openapi = "hyperforge.api.commands:extract_openapi"
|
|
83
|
+
hyperforge-downloads-cronjob = "hyperforge.downloads.command:run"
|
|
84
|
+
hyperforge-workflows-cleanup-cronjob = "hyperforge.agents.workflow_cleanup:run"
|
|
85
|
+
hyperforge-standalone = "hyperforge.standalone:run.run"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import fire # type: ignore
|
|
4
|
+
import jinja2
|
|
5
|
+
|
|
6
|
+
# Jinja vulnerability is not a concern since this is used to format a string prompt, not to render HTML
|
|
7
|
+
PROMPT_ENVIRONMENT = jinja2.Environment() # nosemgrep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("hyperforge")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cli():
|
|
14
|
+
from hyperforge.arag import ARAG
|
|
15
|
+
|
|
16
|
+
fire.Fire(ARAG)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any, Generic, List, Optional, Self, TypeVar
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from hyperforge.manager import Manager
|
|
8
|
+
from hyperforge.memory.memory import QuestionMemory
|
|
9
|
+
from hyperforge.utils import WidgetType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentConfig(BaseModel):
|
|
13
|
+
id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
14
|
+
title: str = "agent"
|
|
15
|
+
rules: Optional[List[str]] = Field(
|
|
16
|
+
default=None,
|
|
17
|
+
title="Agent rules",
|
|
18
|
+
description="List of rules to follow when executing this agent",
|
|
19
|
+
json_schema_extra={
|
|
20
|
+
"widget": WidgetType.NOT_SHOWN,
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
max_retries: int = 1
|
|
24
|
+
module: Any = Field(
|
|
25
|
+
..., title="Agent module", description="Module/type of the agent"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def subagent_configure(self, config: dict):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
T_Config = TypeVar("T_Config", bound=AgentConfig)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Agent(Generic[T_Config]):
|
|
36
|
+
__root_agent__: bool = False
|
|
37
|
+
agent_id: str
|
|
38
|
+
config: T_Config
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: T_Config, agent_id: Optional[str] = None):
|
|
41
|
+
self.config: T_Config = config
|
|
42
|
+
self.agent_id: str = (
|
|
43
|
+
agent_id
|
|
44
|
+
if agent_id is not None
|
|
45
|
+
else config.id
|
|
46
|
+
if hasattr(config, "id") and config.id is not None
|
|
47
|
+
else str(uuid.uuid4())
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@abc.abstractmethod
|
|
51
|
+
async def inner_from_config(self, config: T_Config, agent_id: Optional[str] = None):
|
|
52
|
+
"""Initialize the agent from the config. This is where you should put any
|
|
53
|
+
async initialization code that needs to run when the agent is created.
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
async def from_config(
|
|
59
|
+
cls, config: T_Config, agent_id: Optional[str] = None
|
|
60
|
+
) -> Self:
|
|
61
|
+
instance = cls(config=config, agent_id=agent_id)
|
|
62
|
+
await instance.inner_from_config(config, agent_id)
|
|
63
|
+
return instance
|
|
64
|
+
|
|
65
|
+
def step_title(self, description: str) -> str:
|
|
66
|
+
"""Format a step title as '<agent title>: <description>'.
|
|
67
|
+
|
|
68
|
+
Uses the user-configured instance title if set, otherwise falls back to
|
|
69
|
+
the Pydantic model_config title (e.g. 'MCP', 'SQL query') which describes
|
|
70
|
+
the agent type.
|
|
71
|
+
"""
|
|
72
|
+
config = self.config # type: ignore[attr-defined]
|
|
73
|
+
title = type(config).model_config.get("title") or config.title # type: ignore[attr-defined]
|
|
74
|
+
return f"{title}: {description}"
|
|
75
|
+
|
|
76
|
+
async def __call__(
|
|
77
|
+
self,
|
|
78
|
+
memory: QuestionMemory,
|
|
79
|
+
manager: Manager,
|
|
80
|
+
):
|
|
81
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger("hyperforge.api")
|
|
4
|
+
|
|
5
|
+
SERVICE_NAME = "hyperforge_api"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Define the filter
|
|
9
|
+
class EndpointFilter(logging.Filter):
|
|
10
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
11
|
+
return (
|
|
12
|
+
record.args is not None
|
|
13
|
+
and len(record.args) >= 3
|
|
14
|
+
and record.args[2] # type: ignore
|
|
15
|
+
not in ("/", "/metrics", "/health/alive", "/health/ready")
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Add filter to the logger
|
|
20
|
+
logging.getLogger("uvicorn.access").addFilter(EndpointFilter())
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from typing import Any, Optional, Tuple
|
|
2
|
+
|
|
3
|
+
import prometheus_client # type: ignore
|
|
4
|
+
from fastapi import APIRouter, FastAPI
|
|
5
|
+
from lru import LRU
|
|
6
|
+
from mcp.server.lowlevel.server import Server as MCPServer
|
|
7
|
+
from mcp.server.streamable_http import (
|
|
8
|
+
StreamableHTTPServerTransport,
|
|
9
|
+
)
|
|
10
|
+
from nucliadb_sdk.v2.sdk import NucliaDBAsync
|
|
11
|
+
from nucliadb_telemetry.logs import setup_logging
|
|
12
|
+
from nucliadb_telemetry.settings import LogLevel, LogSettings
|
|
13
|
+
from nucliadb_telemetry.utils import clean_telemetry, setup_telemetry
|
|
14
|
+
from prometheus_client import CONTENT_TYPE_LATEST # type: ignore
|
|
15
|
+
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
16
|
+
from starlette.responses import PlainTextResponse
|
|
17
|
+
|
|
18
|
+
from hyperforge.api import SERVICE_NAME, internal, logger, v1
|
|
19
|
+
from hyperforge.api.authentication import RaoAuthenticationBackend
|
|
20
|
+
from hyperforge.api.logging import set_sentry
|
|
21
|
+
from hyperforge.api.settings import Settings
|
|
22
|
+
from hyperforge.broker import Broker
|
|
23
|
+
from hyperforge.broker.redis import RedisBroker
|
|
24
|
+
from hyperforge.configure import GLOBAL_REGISTRY, load_all_configurations, scan
|
|
25
|
+
from hyperforge.db.agents import AgentManager
|
|
26
|
+
from hyperforge.db.settings import DataManagerSettings
|
|
27
|
+
from hyperforge.feature_flag import get_flag_service
|
|
28
|
+
|
|
29
|
+
router = APIRouter()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@router.get("/metrics")
|
|
33
|
+
async def serve_metrics(): # pragma: no cover
|
|
34
|
+
output = prometheus_client.exposition.generate_latest()
|
|
35
|
+
return PlainTextResponse(
|
|
36
|
+
output.decode("utf8"), headers={"Content-Type": CONTENT_TYPE_LATEST}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get("/health/ready")
|
|
41
|
+
async def health_ready():
|
|
42
|
+
return {"status": "ok"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.get("/health/alive")
|
|
46
|
+
async def health_alive():
|
|
47
|
+
return {"status": "ok"}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class HTTPApplication(FastAPI):
|
|
51
|
+
agent_manager: AgentManager
|
|
52
|
+
arag_search: NucliaDBAsync
|
|
53
|
+
arag_writer: NucliaDBAsync
|
|
54
|
+
arag_reader: NucliaDBAsync
|
|
55
|
+
broker: Broker
|
|
56
|
+
extra_middlewares: Optional[list[Any]] = None
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
settings: Settings,
|
|
61
|
+
data_manager_settings: DataManagerSettings,
|
|
62
|
+
*args,
|
|
63
|
+
**kwargs,
|
|
64
|
+
):
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
self.settings = settings
|
|
67
|
+
self.data_manager_settings = data_manager_settings
|
|
68
|
+
self.include_router(internal.router)
|
|
69
|
+
self.include_router(v1.router)
|
|
70
|
+
self.include_router(router)
|
|
71
|
+
self.add_middleware(
|
|
72
|
+
AuthenticationMiddleware,
|
|
73
|
+
backend=RaoAuthenticationBackend(),
|
|
74
|
+
)
|
|
75
|
+
if self.extra_middlewares is not None:
|
|
76
|
+
for extra_middleware in self.extra_middlewares:
|
|
77
|
+
self.add_middleware(extra_middleware)
|
|
78
|
+
self.add_event_handler("startup", self.startup)
|
|
79
|
+
self.add_event_handler("shutdown", self.shutdown)
|
|
80
|
+
|
|
81
|
+
async def startup(self) -> None:
|
|
82
|
+
GLOBAL_REGISTRY.clear()
|
|
83
|
+
await setup_telemetry(SERVICE_NAME)
|
|
84
|
+
setup_logging(
|
|
85
|
+
settings=LogSettings(
|
|
86
|
+
debug=self.settings.debug,
|
|
87
|
+
log_level=LogLevel(self.settings.log_level),
|
|
88
|
+
logger_levels={
|
|
89
|
+
"uvicorn.error": LogLevel.ERROR,
|
|
90
|
+
"nucliadb_telemetry": LogLevel.ERROR,
|
|
91
|
+
"mcp.client.streamable_http": LogLevel.WARNING,
|
|
92
|
+
"mcp.server.lowlevel.server": LogLevel.WARNING,
|
|
93
|
+
"hyperforge.configure": LogLevel.WARNING,
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
if self.settings.sentry_url is not None:
|
|
98
|
+
set_sentry(
|
|
99
|
+
self.settings.zone,
|
|
100
|
+
self.settings.running_environment,
|
|
101
|
+
self.settings.sentry_url,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
get_flag_service() # precache the flag service
|
|
105
|
+
|
|
106
|
+
if self.settings.memory_apikey_nucliadb is None:
|
|
107
|
+
api_key = None
|
|
108
|
+
headers = {"X-NUCLIADB-ROLES": "WRITER;READER"}
|
|
109
|
+
else:
|
|
110
|
+
api_key = self.settings.memory_apikey_nucliadb
|
|
111
|
+
headers = None
|
|
112
|
+
|
|
113
|
+
self.arag_writer = NucliaDBAsync(
|
|
114
|
+
url=self.settings.memory_writer_nucliadb,
|
|
115
|
+
api_key=api_key,
|
|
116
|
+
headers=headers,
|
|
117
|
+
)
|
|
118
|
+
self.arag_reader = NucliaDBAsync(
|
|
119
|
+
url=self.settings.memory_reader_nucliadb,
|
|
120
|
+
api_key=api_key,
|
|
121
|
+
headers=headers,
|
|
122
|
+
)
|
|
123
|
+
self.arag_search = NucliaDBAsync(
|
|
124
|
+
url=self.settings.memory_search_nucliadb,
|
|
125
|
+
api_key=api_key,
|
|
126
|
+
headers=headers,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.broker = RedisBroker.from_url(
|
|
130
|
+
url=self.settings.valkey_url,
|
|
131
|
+
activate_subject=self.settings.activate_subject,
|
|
132
|
+
keepalive_ms=int(self.settings.pubsub_keepalive_seconds * 1000),
|
|
133
|
+
cluster_mode=self.settings.valkey_cluster_mode,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
self.sses: LRU[Tuple[str, str], StreamableHTTPServerTransport] = LRU(size=100)
|
|
137
|
+
self.mcp_servers: LRU[str, MCPServer] = LRU(size=100)
|
|
138
|
+
|
|
139
|
+
self.agent_manager = await AgentManager.from_settings(
|
|
140
|
+
settings=self.data_manager_settings
|
|
141
|
+
)
|
|
142
|
+
await self.agent_manager.initialize()
|
|
143
|
+
|
|
144
|
+
for load_module in self.settings.load_modules:
|
|
145
|
+
try:
|
|
146
|
+
scan(load_module)
|
|
147
|
+
load_all_configurations(load_module)
|
|
148
|
+
except ImportError:
|
|
149
|
+
logger.error(f"Module {load_module} could not be loaded")
|
|
150
|
+
|
|
151
|
+
async def shutdown(self) -> None:
|
|
152
|
+
await self.agent_manager.finalize()
|
|
153
|
+
await self.broker.finalize()
|
|
154
|
+
await clean_telemetry(SERVICE_NAME)
|
|
155
|
+
GLOBAL_REGISTRY.clear()
|