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.
Files changed (37) hide show
  1. fred_runtime-0.1.0/PKG-INFO +51 -0
  2. fred_runtime-0.1.0/README.md +21 -0
  3. fred_runtime-0.1.0/fred_runtime/__init__.py +34 -0
  4. fred_runtime-0.1.0/fred_runtime/app/__init__.py +23 -0
  5. fred_runtime-0.1.0/fred_runtime/app/agent_app.py +368 -0
  6. fred_runtime-0.1.0/fred_runtime/common/__init__.py +16 -0
  7. fred_runtime-0.1.0/fred_runtime/common/context_aware_tool.py +370 -0
  8. fred_runtime-0.1.0/fred_runtime/common/kf_base_client.py +238 -0
  9. fred_runtime-0.1.0/fred_runtime/common/kf_fast_text_client.py +156 -0
  10. fred_runtime-0.1.0/fred_runtime/common/kf_http_client.py +130 -0
  11. fred_runtime-0.1.0/fred_runtime/common/kf_logs_client.py +72 -0
  12. fred_runtime-0.1.0/fred_runtime/common/kf_markdown_media_client.py +46 -0
  13. fred_runtime-0.1.0/fred_runtime/common/kf_vectorsearch_client.py +160 -0
  14. fred_runtime-0.1.0/fred_runtime/common/kf_workspace_client.py +535 -0
  15. fred_runtime-0.1.0/fred_runtime/common/mcp_interceptors.py +81 -0
  16. fred_runtime-0.1.0/fred_runtime/common/mcp_runtime.py +449 -0
  17. fred_runtime-0.1.0/fred_runtime/common/mcp_toolkit.py +145 -0
  18. fred_runtime-0.1.0/fred_runtime/common/mcp_utils.py +333 -0
  19. fred_runtime-0.1.0/fred_runtime/common/structures.py +46 -0
  20. fred_runtime-0.1.0/fred_runtime/common/token_expiry.py +106 -0
  21. fred_runtime-0.1.0/fred_runtime/common/tool_node_utils.py +121 -0
  22. fred_runtime-0.1.0/fred_runtime/integrations/__init__.py +16 -0
  23. fred_runtime-0.1.0/fred_runtime/integrations/v2_runtime/__init__.py +16 -0
  24. fred_runtime-0.1.0/fred_runtime/integrations/v2_runtime/adapters.py +1797 -0
  25. fred_runtime-0.1.0/fred_runtime/runtime_context.py +242 -0
  26. fred_runtime-0.1.0/fred_runtime/runtime_support/__init__.py +54 -0
  27. fred_runtime-0.1.0/fred_runtime/runtime_support/request_context_helpers.py +236 -0
  28. fred_runtime-0.1.0/fred_runtime/runtime_support/sql_checkpointer.py +597 -0
  29. fred_runtime-0.1.0/fred_runtime/runtime_support/user_token_refresher.py +59 -0
  30. fred_runtime-0.1.0/fred_runtime.egg-info/PKG-INFO +51 -0
  31. fred_runtime-0.1.0/fred_runtime.egg-info/SOURCES.txt +35 -0
  32. fred_runtime-0.1.0/fred_runtime.egg-info/dependency_links.txt +1 -0
  33. fred_runtime-0.1.0/fred_runtime.egg-info/requires.txt +17 -0
  34. fred_runtime-0.1.0/fred_runtime.egg-info/top_level.txt +1 -0
  35. fred_runtime-0.1.0/pyproject.toml +68 -0
  36. fred_runtime-0.1.0/setup.cfg +4 -0
  37. 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
+ """