fred-runtime 0.1.0__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.
- fred_runtime-0.1.0/PKG-INFO +51 -0
- fred_runtime-0.1.0/README.md +21 -0
- fred_runtime-0.1.0/fred_runtime/__init__.py +34 -0
- fred_runtime-0.1.0/fred_runtime/app/__init__.py +23 -0
- fred_runtime-0.1.0/fred_runtime/app/agent_app.py +368 -0
- fred_runtime-0.1.0/fred_runtime/common/__init__.py +16 -0
- fred_runtime-0.1.0/fred_runtime/common/context_aware_tool.py +370 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_base_client.py +238 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_fast_text_client.py +156 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_http_client.py +130 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_logs_client.py +72 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_markdown_media_client.py +46 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_vectorsearch_client.py +160 -0
- fred_runtime-0.1.0/fred_runtime/common/kf_workspace_client.py +535 -0
- fred_runtime-0.1.0/fred_runtime/common/mcp_interceptors.py +81 -0
- fred_runtime-0.1.0/fred_runtime/common/mcp_runtime.py +449 -0
- fred_runtime-0.1.0/fred_runtime/common/mcp_toolkit.py +145 -0
- fred_runtime-0.1.0/fred_runtime/common/mcp_utils.py +333 -0
- fred_runtime-0.1.0/fred_runtime/common/structures.py +46 -0
- fred_runtime-0.1.0/fred_runtime/common/token_expiry.py +106 -0
- fred_runtime-0.1.0/fred_runtime/common/tool_node_utils.py +121 -0
- fred_runtime-0.1.0/fred_runtime/integrations/__init__.py +16 -0
- fred_runtime-0.1.0/fred_runtime/integrations/v2_runtime/__init__.py +16 -0
- fred_runtime-0.1.0/fred_runtime/integrations/v2_runtime/adapters.py +1797 -0
- fred_runtime-0.1.0/fred_runtime/runtime_context.py +242 -0
- fred_runtime-0.1.0/fred_runtime/runtime_support/__init__.py +54 -0
- fred_runtime-0.1.0/fred_runtime/runtime_support/request_context_helpers.py +236 -0
- fred_runtime-0.1.0/fred_runtime/runtime_support/sql_checkpointer.py +597 -0
- fred_runtime-0.1.0/fred_runtime/runtime_support/user_token_refresher.py +59 -0
- fred_runtime-0.1.0/fred_runtime.egg-info/PKG-INFO +51 -0
- fred_runtime-0.1.0/fred_runtime.egg-info/SOURCES.txt +35 -0
- fred_runtime-0.1.0/fred_runtime.egg-info/dependency_links.txt +1 -0
- fred_runtime-0.1.0/fred_runtime.egg-info/requires.txt +17 -0
- fred_runtime-0.1.0/fred_runtime.egg-info/top_level.txt +1 -0
- fred_runtime-0.1.0/pyproject.toml +68 -0
- fred_runtime-0.1.0/setup.cfg +4 -0
- fred_runtime-0.1.0/tests/test_smoke.py +19 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fred-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime adapters and infrastructure wiring for Fred v2 agents.
|
|
5
|
+
Author-email: Thales <noreply@thalesgroup.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://fredk8.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/ThalesGroup/fred
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: <3.13,>=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: fred-core>=1.3.1
|
|
16
|
+
Requires-Dist: fred-sdk>=0.1.1
|
|
17
|
+
Requires-Dist: fred-portable>=0.1.1
|
|
18
|
+
Requires-Dist: langchain-mcp-adapters>=0.2.1
|
|
19
|
+
Requires-Dist: langfuse>=3.0.0
|
|
20
|
+
Requires-Dist: requests<3,>=2.32
|
|
21
|
+
Provides-Extra: app
|
|
22
|
+
Requires-Dist: fastapi>=0.116; extra == "app"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: bandit>=1.8.6; extra == "dev"
|
|
25
|
+
Requires-Dist: basedpyright==1.31.0; extra == "dev"
|
|
26
|
+
Requires-Dist: detect-secrets>=1.5.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest>=8.4.2; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.12.5; extra == "dev"
|
|
30
|
+
|
|
31
|
+
# Fred Runtime
|
|
32
|
+
|
|
33
|
+
`fred-runtime` provides platform adapters and infrastructure wiring for Fred v2 agents.
|
|
34
|
+
|
|
35
|
+
It is the missing middle layer between:
|
|
36
|
+
- `fred-sdk` (authoring + runtime contracts), and
|
|
37
|
+
- backend implementations (databases, Knowledge Flow, MCP, tracing, checkpointers).
|
|
38
|
+
|
|
39
|
+
## Why this package exists
|
|
40
|
+
|
|
41
|
+
- Keep `fred-sdk` pure and portable.
|
|
42
|
+
- Share runtime adapters across agentic backends and standalone agent apps.
|
|
43
|
+
- Make it easy for external agent repos (like `rags-v2`) to run v2 agents with
|
|
44
|
+
consistent checkpointing and tool infrastructure.
|
|
45
|
+
|
|
46
|
+
## Scope (initial)
|
|
47
|
+
|
|
48
|
+
- SQL-backed checkpointer for LangGraph runtimes.
|
|
49
|
+
- Runtime helper utilities extracted from agentic-backend.
|
|
50
|
+
|
|
51
|
+
More runtime adapters will move here as the migration continues.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Fred Runtime
|
|
2
|
+
|
|
3
|
+
`fred-runtime` provides platform adapters and infrastructure wiring for Fred v2 agents.
|
|
4
|
+
|
|
5
|
+
It is the missing middle layer between:
|
|
6
|
+
- `fred-sdk` (authoring + runtime contracts), and
|
|
7
|
+
- backend implementations (databases, Knowledge Flow, MCP, tracing, checkpointers).
|
|
8
|
+
|
|
9
|
+
## Why this package exists
|
|
10
|
+
|
|
11
|
+
- Keep `fred-sdk` pure and portable.
|
|
12
|
+
- Share runtime adapters across agentic backends and standalone agent apps.
|
|
13
|
+
- Make it easy for external agent repos (like `rags-v2`) to run v2 agents with
|
|
14
|
+
consistent checkpointing and tool infrastructure.
|
|
15
|
+
|
|
16
|
+
## Scope (initial)
|
|
17
|
+
|
|
18
|
+
- SQL-backed checkpointer for LangGraph runtimes.
|
|
19
|
+
- Runtime helper utilities extracted from agentic-backend.
|
|
20
|
+
|
|
21
|
+
More runtime adapters will move here as the migration continues.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
fred-runtime public surface.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .runtime_context import (
|
|
19
|
+
RuntimeConfig,
|
|
20
|
+
RuntimeContext,
|
|
21
|
+
RuntimeTimeouts,
|
|
22
|
+
get_runtime_context,
|
|
23
|
+
set_runtime_context,
|
|
24
|
+
)
|
|
25
|
+
from .runtime_support import FredSqlCheckpointer
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"FredSqlCheckpointer",
|
|
29
|
+
"RuntimeConfig",
|
|
30
|
+
"RuntimeContext",
|
|
31
|
+
"RuntimeTimeouts",
|
|
32
|
+
"get_runtime_context",
|
|
33
|
+
"set_runtime_context",
|
|
34
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Agent app factory — reusable FastAPI wiring for agent pods.
|
|
16
|
+
|
|
17
|
+
Import from here:
|
|
18
|
+
from fred_runtime.app import create_agent_app, AgentAppSettings
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .agent_app import AgentAppSettings, create_agent_app
|
|
22
|
+
|
|
23
|
+
__all__ = ["AgentAppSettings", "create_agent_app"]
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Reusable FastAPI app factory for Fred agent pods.
|
|
16
|
+
|
|
17
|
+
Why this module exists:
|
|
18
|
+
- every agent pod (rags-v2, future pods) needs identical SSE streaming plumbing:
|
|
19
|
+
RuntimeConfig wiring, BoundRuntimeContext construction, ReActRuntime lifecycle,
|
|
20
|
+
and a `POST /agents/execute/stream` endpoint
|
|
21
|
+
- duplicating that across pods causes divergence bugs and maintenance overhead
|
|
22
|
+
|
|
23
|
+
How to use it:
|
|
24
|
+
- call `create_agent_app(registry, settings)` in your pod's `main.py`
|
|
25
|
+
- the returned FastAPI app already exposes:
|
|
26
|
+
POST /api/v1/agents/execute/stream — stream RuntimeEvent JSON over SSE
|
|
27
|
+
GET /api/v1/agents — list registered agent IDs
|
|
28
|
+
- pass `extra_routers` to mount additional domain-specific routers under /api/v1
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
from fred_runtime.app import create_agent_app, AgentAppSettings
|
|
32
|
+
from myapp.agents.registry import REGISTRY
|
|
33
|
+
from myapp.settings import get_settings
|
|
34
|
+
|
|
35
|
+
app = create_agent_app(registry=REGISTRY, settings=get_settings())
|
|
36
|
+
|
|
37
|
+
AgentAppSettings:
|
|
38
|
+
- `AgentAppSettings` is a Protocol — any object (including pydantic BaseSettings
|
|
39
|
+
subclasses) that carries the required attributes satisfies it.
|
|
40
|
+
- Required fields: knowledge_flow_url, chat_model_provider, chat_model_name.
|
|
41
|
+
- Optional fields: google_api_key, openai_api_key, app_title, app_version.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import json
|
|
47
|
+
import logging
|
|
48
|
+
from collections.abc import AsyncIterator
|
|
49
|
+
from contextlib import asynccontextmanager
|
|
50
|
+
from typing import TYPE_CHECKING, Any
|
|
51
|
+
from uuid import uuid4
|
|
52
|
+
|
|
53
|
+
from fastapi import APIRouter, FastAPI, HTTPException, status
|
|
54
|
+
from fastapi.responses import StreamingResponse
|
|
55
|
+
from fred_sdk.contracts.context import (
|
|
56
|
+
BoundRuntimeContext,
|
|
57
|
+
PortableContext,
|
|
58
|
+
PortableEnvironment,
|
|
59
|
+
RuntimeContext,
|
|
60
|
+
)
|
|
61
|
+
from fred_sdk.contracts.models import ReActAgentDefinition
|
|
62
|
+
from fred_sdk.contracts.react_contract import ReActInput, ReActMessage, ReActMessageRole
|
|
63
|
+
from fred_sdk.contracts.runtime import ExecutionConfig, RuntimeServices
|
|
64
|
+
from fred_sdk.react.react_runtime import ReActRuntime
|
|
65
|
+
from pydantic import BaseModel, Field
|
|
66
|
+
|
|
67
|
+
from fred_runtime.integrations.v2_runtime.adapters import DefaultFredChatModelFactory
|
|
68
|
+
from fred_runtime.runtime_context import (
|
|
69
|
+
RuntimeConfig,
|
|
70
|
+
RuntimeTimeouts,
|
|
71
|
+
set_runtime_context,
|
|
72
|
+
)
|
|
73
|
+
from fred_runtime.runtime_context import RuntimeContext as FredRuntimeContext
|
|
74
|
+
|
|
75
|
+
if TYPE_CHECKING:
|
|
76
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
77
|
+
|
|
78
|
+
logger = logging.getLogger(__name__)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Settings contract — structural Protocol so any pydantic-settings subclass
|
|
83
|
+
# satisfies it without inheriting from this class.
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AgentAppSettings:
|
|
88
|
+
"""
|
|
89
|
+
Default values for the common agent pod settings fields.
|
|
90
|
+
|
|
91
|
+
Why a plain class rather than BaseSettings:
|
|
92
|
+
- fred-runtime must not pull in pydantic-settings as a required dependency
|
|
93
|
+
- pods supply their own Settings class (pydantic-settings, dataclass, etc.)
|
|
94
|
+
and it is accepted as long as it carries these attributes
|
|
95
|
+
|
|
96
|
+
How to use it:
|
|
97
|
+
- when using pydantic-settings, do NOT subclass AgentAppSettings directly —
|
|
98
|
+
just ensure your Settings class declares the same fields:
|
|
99
|
+
|
|
100
|
+
class Settings(BaseSettings):
|
|
101
|
+
knowledge_flow_url: str = "http://localhost:3000"
|
|
102
|
+
chat_model_provider: str = "google"
|
|
103
|
+
chat_model_name: str = "gemini-2.0-flash"
|
|
104
|
+
google_api_key: str = ""
|
|
105
|
+
openai_api_key: str = ""
|
|
106
|
+
app_title: str = "My Agent Pod"
|
|
107
|
+
app_version: str = "0.1.0"
|
|
108
|
+
|
|
109
|
+
- `create_agent_app` accepts any object with those attributes; the type
|
|
110
|
+
annotation is `AgentAppSettings` for documentation purposes only.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
knowledge_flow_url: str = "http://localhost:3000"
|
|
114
|
+
chat_model_provider: str = "google"
|
|
115
|
+
chat_model_name: str = "gemini-2.0-flash"
|
|
116
|
+
google_api_key: str = ""
|
|
117
|
+
openai_api_key: str = ""
|
|
118
|
+
app_title: str = "Fred Agent Pod"
|
|
119
|
+
app_version: str = "0.1.0"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Chat model provider builder
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _build_chat_model_provider(settings: Any):
|
|
128
|
+
"""
|
|
129
|
+
Return a zero-argument factory for the configured chat model.
|
|
130
|
+
|
|
131
|
+
Why a callable rather than a direct instance:
|
|
132
|
+
- RuntimeConfig.chat_model_provider is lazy so the model is created on first
|
|
133
|
+
use and can be swapped per-environment via env vars
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
if getattr(settings, "chat_model_provider", "google") == "openai":
|
|
137
|
+
|
|
138
|
+
def _openai() -> BaseChatModel:
|
|
139
|
+
from langchain_openai import ChatOpenAI # type: ignore[import-untyped]
|
|
140
|
+
|
|
141
|
+
return ChatOpenAI(
|
|
142
|
+
model=settings.chat_model_name,
|
|
143
|
+
api_key=settings.openai_api_key or None,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return _openai
|
|
147
|
+
|
|
148
|
+
def _google() -> BaseChatModel:
|
|
149
|
+
from langchain_google_genai import (
|
|
150
|
+
ChatGoogleGenerativeAI, # type: ignore[import-untyped]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return ChatGoogleGenerativeAI(
|
|
154
|
+
model=settings.chat_model_name,
|
|
155
|
+
google_api_key=settings.google_api_key or None,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return _google
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# SSE streaming helpers
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class _AgentExecuteRequest(BaseModel):
|
|
167
|
+
"""
|
|
168
|
+
Input payload for the SSE execution endpoint.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
```json
|
|
172
|
+
{"agent_id": "rags.sample.echo", "message": "hello"}
|
|
173
|
+
```
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
agent_id: str = Field(..., min_length=1)
|
|
177
|
+
message: str = Field(..., min_length=1)
|
|
178
|
+
context: dict[str, Any] | None = None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _sse(payload: str) -> str:
|
|
182
|
+
return f"data: {payload}\n\n"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def _stream(
|
|
186
|
+
definition: ReActAgentDefinition,
|
|
187
|
+
request: _AgentExecuteRequest,
|
|
188
|
+
) -> AsyncIterator[str]:
|
|
189
|
+
"""
|
|
190
|
+
Execute a ReAct agent and yield SSE-framed RuntimeEvent JSON.
|
|
191
|
+
|
|
192
|
+
Why this is a standalone async generator:
|
|
193
|
+
- keeps the FastAPI endpoint thin (just returns StreamingResponse)
|
|
194
|
+
- raises no HTTPException — the endpoint already validated agent_id before
|
|
195
|
+
passing the definition in
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
request_id = str(uuid4())
|
|
199
|
+
ctx = request.context or {}
|
|
200
|
+
correlation_id = ctx.get("correlation_id", request_id)
|
|
201
|
+
|
|
202
|
+
portable_context = PortableContext(
|
|
203
|
+
request_id=request_id,
|
|
204
|
+
correlation_id=correlation_id,
|
|
205
|
+
actor=ctx.get("user_id", "anonymous"),
|
|
206
|
+
tenant=ctx.get("tenant", "default"),
|
|
207
|
+
environment=PortableEnvironment.DEV,
|
|
208
|
+
agent_id=definition.agent_id,
|
|
209
|
+
agent_name=definition.agent_id,
|
|
210
|
+
session_id=ctx.get("session_id"),
|
|
211
|
+
user_id=ctx.get("user_id"),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
runtime_context = RuntimeContext(
|
|
215
|
+
session_id=ctx.get("session_id"),
|
|
216
|
+
user_id=ctx.get("user_id"),
|
|
217
|
+
language=ctx.get("language"),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
binding = BoundRuntimeContext(
|
|
221
|
+
runtime_context=runtime_context,
|
|
222
|
+
portable_context=portable_context,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
services = RuntimeServices(
|
|
226
|
+
chat_model_factory=DefaultFredChatModelFactory(),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
runtime = ReActRuntime(definition=definition, services=services)
|
|
230
|
+
runtime.bind(binding)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
await runtime.activate()
|
|
234
|
+
executor = await runtime.get_executor()
|
|
235
|
+
react_input = ReActInput(
|
|
236
|
+
messages=(
|
|
237
|
+
ReActMessage(role=ReActMessageRole.USER, content=request.message),
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
async for event in executor.stream(react_input, ExecutionConfig()):
|
|
241
|
+
yield _sse(event.model_dump_json())
|
|
242
|
+
except Exception as exc:
|
|
243
|
+
logger.exception(
|
|
244
|
+
"[fred-runtime] agent execution error agent_id=%s", definition.agent_id
|
|
245
|
+
)
|
|
246
|
+
yield _sse(json.dumps({"error": str(exc)}))
|
|
247
|
+
finally:
|
|
248
|
+
await runtime.dispose()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ---------------------------------------------------------------------------
|
|
252
|
+
# Router builder
|
|
253
|
+
# ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _build_agent_router(registry: dict[str, ReActAgentDefinition]) -> APIRouter:
|
|
257
|
+
"""
|
|
258
|
+
Build the FastAPI router for agent execution.
|
|
259
|
+
|
|
260
|
+
Why this is a function rather than a module-level router:
|
|
261
|
+
- the registry is provided at app-creation time, not import time
|
|
262
|
+
- each call produces an isolated router instance bound to that registry
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
router = APIRouter(prefix="/agents", tags=["Agents"])
|
|
266
|
+
|
|
267
|
+
@router.get("")
|
|
268
|
+
async def list_agents() -> list[str]:
|
|
269
|
+
"""Return the agent IDs registered in this pod."""
|
|
270
|
+
return list(registry.keys())
|
|
271
|
+
|
|
272
|
+
@router.post("/execute/stream")
|
|
273
|
+
async def execute_stream(request: _AgentExecuteRequest) -> StreamingResponse:
|
|
274
|
+
"""
|
|
275
|
+
Stream RuntimeEvent JSON over SSE for a single agent invocation.
|
|
276
|
+
|
|
277
|
+
POST /api/v1/agents/execute/stream
|
|
278
|
+
Body: {"agent_id": "...", "message": "...", "context": {...}}
|
|
279
|
+
Response: text/event-stream, each `data:` line is a RuntimeEvent JSON
|
|
280
|
+
"""
|
|
281
|
+
definition = registry.get(request.agent_id)
|
|
282
|
+
if definition is None:
|
|
283
|
+
raise HTTPException(
|
|
284
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
285
|
+
detail=f"Unknown agent_id: {request.agent_id!r}. "
|
|
286
|
+
f"Known agents: {list(registry.keys())}",
|
|
287
|
+
)
|
|
288
|
+
return StreamingResponse(
|
|
289
|
+
_stream(definition, request),
|
|
290
|
+
media_type="text/event-stream",
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return router
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ---------------------------------------------------------------------------
|
|
297
|
+
# Public factory
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def create_agent_app(
|
|
302
|
+
registry: dict[str, ReActAgentDefinition],
|
|
303
|
+
settings: Any,
|
|
304
|
+
extra_routers: list[APIRouter] | None = None,
|
|
305
|
+
) -> FastAPI:
|
|
306
|
+
"""
|
|
307
|
+
Create a ready-to-serve FastAPI app for a Fred agent pod.
|
|
308
|
+
|
|
309
|
+
Why this exists:
|
|
310
|
+
- every agent pod needs the same SSE endpoint, RuntimeConfig wiring, and
|
|
311
|
+
lifespan hook — this factory provides all of it so pods contain zero
|
|
312
|
+
infrastructure code
|
|
313
|
+
|
|
314
|
+
How to use it:
|
|
315
|
+
- call from your pod's `main.py`, passing your agent registry and settings
|
|
316
|
+
- add `extra_routers` for domain-specific endpoints (e.g. information-system)
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
```python
|
|
320
|
+
from fred_runtime.app import create_agent_app
|
|
321
|
+
from myapp.agents.registry import REGISTRY
|
|
322
|
+
from myapp.settings import get_settings
|
|
323
|
+
from myapp.api.router import domain_router
|
|
324
|
+
|
|
325
|
+
app = create_agent_app(
|
|
326
|
+
registry=REGISTRY,
|
|
327
|
+
settings=get_settings(),
|
|
328
|
+
extra_routers=[domain_router],
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Parameters:
|
|
333
|
+
- registry: maps agent_id → ReActAgentDefinition; built at startup, read-only at runtime
|
|
334
|
+
- settings: any object satisfying AgentAppSettings attributes
|
|
335
|
+
- extra_routers: additional APIRouter instances mounted under /api/v1
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
@asynccontextmanager
|
|
339
|
+
async def lifespan(app: FastAPI):
|
|
340
|
+
config = RuntimeConfig(
|
|
341
|
+
knowledge_flow_url=settings.knowledge_flow_url,
|
|
342
|
+
timeouts=RuntimeTimeouts(),
|
|
343
|
+
chat_model_provider=_build_chat_model_provider(settings),
|
|
344
|
+
)
|
|
345
|
+
set_runtime_context(FredRuntimeContext(config))
|
|
346
|
+
logger.info(
|
|
347
|
+
"Fred agent pod started — kf=%s model=%s/%s agents=%s",
|
|
348
|
+
settings.knowledge_flow_url,
|
|
349
|
+
getattr(settings, "chat_model_provider", "google"),
|
|
350
|
+
getattr(settings, "chat_model_name", "?"),
|
|
351
|
+
list(registry.keys()),
|
|
352
|
+
)
|
|
353
|
+
yield
|
|
354
|
+
|
|
355
|
+
app = FastAPI(
|
|
356
|
+
title=getattr(settings, "app_title", "Fred Agent Pod"),
|
|
357
|
+
version=getattr(settings, "app_version", "0.1.0"),
|
|
358
|
+
lifespan=lifespan,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
api_router = APIRouter(prefix="/api/v1")
|
|
362
|
+
api_router.include_router(_build_agent_router(registry))
|
|
363
|
+
|
|
364
|
+
for extra in extra_routers or []:
|
|
365
|
+
api_router.include_router(extra)
|
|
366
|
+
|
|
367
|
+
app.include_router(api_router)
|
|
368
|
+
return app
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Shared runtime helpers that depend on platform adapters.
|
|
16
|
+
"""
|