fred-runtime 2.0.10__tar.gz → 3.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-2.0.10 → fred_runtime-3.1.0}/PKG-INFO +3 -3
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/__init__.py +0 -2
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/agent_app.py +46 -15
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/config.py +9 -37
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/context.py +15 -26
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/observability_factory.py +5 -14
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/context_aware_tool.py +50 -16
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_workspace_client.py +81 -177
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/graph/graph_runtime.py +184 -117
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/integrations/v2_runtime/adapters.py +107 -139
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_langchain_adapter.py +4 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_message_codec.py +8 -11
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_runtime.py +63 -10
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_stream_adapter.py +73 -13
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_tool_resolution.py +18 -58
- fred_runtime-3.1.0/fred_runtime/support/thinking.py +211 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/support/tool_loop.py +9 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime.egg-info/PKG-INFO +3 -3
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime.egg-info/SOURCES.txt +4 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime.egg-info/requires.txt +2 -2
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/pyproject.toml +3 -3
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_agent_app.py +100 -3
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_context_aware_tool.py +44 -1
- fred_runtime-3.1.0/tests/test_fred_workspace_fs.py +179 -0
- fred_runtime-3.1.0/tests/test_graph_runtime_invoke_agent.py +154 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_history.py +7 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_kf_workspace_client.py +79 -18
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_openai_compat_router.py +7 -0
- fred_runtime-3.1.0/tests/test_react_thinking.py +472 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/README.md +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/container.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/history_display.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/pod_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/repl.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_logs_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/deep/deep_runtime.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_prompting.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_tool_binding.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_context.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime/support/tool_approval.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/setup.cfg +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_config_loader.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_context.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_conversational_memory.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_eval_collector.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_eval_trace.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_graph_runtime_observability.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_kpi_display.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_mcp_config.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_model_routing.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_pod_client.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_repl_helpers.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_smoke.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_token_expiry.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_url_helpers.py +0 -0
- {fred_runtime-2.0.10 → fred_runtime-3.1.0}/tests/test_user_token_refresher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fred-runtime
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Runtime adapters and infrastructure wiring for Fred v2 agents.
|
|
5
5
|
Author-email: Thales <noreply@thalesgroup.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Requires-Python: <3.13,>=3.12
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: fred-core>=
|
|
16
|
-
Requires-Dist: fred-sdk>=
|
|
15
|
+
Requires-Dist: fred-core>=3.1.0
|
|
16
|
+
Requires-Dist: fred-sdk>=3.1.0
|
|
17
17
|
Requires-Dist: alembic>=1.18.4
|
|
18
18
|
Requires-Dist: deepagents>=0.4.11
|
|
19
19
|
Requires-Dist: httpx>=0.28.1
|
|
@@ -22,7 +22,6 @@ from .agent_app import create_agent_app
|
|
|
22
22
|
from .config import (
|
|
23
23
|
AgentPodConfig,
|
|
24
24
|
LangfuseObservabilityConfig,
|
|
25
|
-
MetricsBackend,
|
|
26
25
|
PodAIConfig,
|
|
27
26
|
PodAppConfig,
|
|
28
27
|
PodObservabilityConfig,
|
|
@@ -40,7 +39,6 @@ from .config_loader import (
|
|
|
40
39
|
__all__ = [
|
|
41
40
|
"AgentPodConfig",
|
|
42
41
|
"LangfuseObservabilityConfig",
|
|
43
|
-
"MetricsBackend",
|
|
44
42
|
"PodAIConfig",
|
|
45
43
|
"PodAppConfig",
|
|
46
44
|
"PodObservabilityConfig",
|
|
@@ -57,6 +57,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
57
57
|
from fastapi.responses import StreamingResponse
|
|
58
58
|
from fred_core.common.config_loader import get_config
|
|
59
59
|
from fred_core.history.history_schema import ChatMessage
|
|
60
|
+
from fred_core.kpi import KPIMiddleware
|
|
60
61
|
from fred_core.kpi.kpi_writer_structures import KPIActor
|
|
61
62
|
from fred_core.logs.log_setup import log_setup
|
|
62
63
|
from fred_core.logs.memory_log_store import RamLogStore
|
|
@@ -110,10 +111,9 @@ from fred_runtime.runtime_support.checkpoints import load_checkpoint
|
|
|
110
111
|
from ..common.structures import AgentSettingsLike
|
|
111
112
|
from ..integrations.v2_runtime.adapters import (
|
|
112
113
|
CompositeToolInvoker,
|
|
113
|
-
FredArtifactPublisher,
|
|
114
114
|
FredKnowledgeSearchToolInvoker,
|
|
115
115
|
FredMcpToolProvider,
|
|
116
|
-
|
|
116
|
+
FredWorkspaceFs,
|
|
117
117
|
KPIWriterMetricsAdapter,
|
|
118
118
|
build_default_tracer,
|
|
119
119
|
)
|
|
@@ -127,7 +127,11 @@ from ..runtime_support import refresh_user_access_token_from_keycloak
|
|
|
127
127
|
from .config import AgentPodConfig
|
|
128
128
|
from .container import build_pod_container
|
|
129
129
|
from .context import AuditEventRecord, KpiTurnRecord, PodApplicationContext
|
|
130
|
-
from .dependencies import
|
|
130
|
+
from .dependencies import (
|
|
131
|
+
attach_pod_container,
|
|
132
|
+
get_pod_container,
|
|
133
|
+
get_pod_container_from_app,
|
|
134
|
+
)
|
|
131
135
|
from .observability_factory import bootstrap_observability
|
|
132
136
|
|
|
133
137
|
logger = logging.getLogger(__name__)
|
|
@@ -563,6 +567,21 @@ class LocalRegistryAgentInvoker(AgentInvokerPort):
|
|
|
563
567
|
|
|
564
568
|
context_dict = request.context.model_dump(mode="json")
|
|
565
569
|
context_dict.setdefault("execution_action", ExecutionGrantAction.EXECUTE.value)
|
|
570
|
+
# RFC AGENT-INVOKE: apply the caller's per-call scope onto the callee's
|
|
571
|
+
# retrieval context. These keys are read back when the callee's
|
|
572
|
+
# RuntimeContext is built, so they narrow its document/library/search world.
|
|
573
|
+
# Scope narrows only; the callee still runs under the delegated identity.
|
|
574
|
+
if request.scope is not None:
|
|
575
|
+
if request.scope.document_uids is not None:
|
|
576
|
+
context_dict["selected_document_uids"] = list(
|
|
577
|
+
request.scope.document_uids
|
|
578
|
+
)
|
|
579
|
+
if request.scope.library_ids is not None:
|
|
580
|
+
context_dict["selected_document_libraries_ids"] = list(
|
|
581
|
+
request.scope.library_ids
|
|
582
|
+
)
|
|
583
|
+
if request.scope.search_policy is not None:
|
|
584
|
+
context_dict["search_policy"] = request.scope.search_policy
|
|
566
585
|
execute_request = _AgentExecuteRequest.model_construct(
|
|
567
586
|
agent_id=request.agent_id,
|
|
568
587
|
agent_instance_id=None,
|
|
@@ -641,11 +660,7 @@ def _build_runtime_services(
|
|
|
641
660
|
binding=binding,
|
|
642
661
|
settings=settings,
|
|
643
662
|
)
|
|
644
|
-
|
|
645
|
-
binding=binding,
|
|
646
|
-
settings=settings,
|
|
647
|
-
)
|
|
648
|
-
resource_reader = FredResourceReader(
|
|
663
|
+
workspace_fs = FredWorkspaceFs(
|
|
649
664
|
binding=binding,
|
|
650
665
|
settings=settings,
|
|
651
666
|
)
|
|
@@ -657,8 +672,7 @@ def _build_runtime_services(
|
|
|
657
672
|
settings=settings,
|
|
658
673
|
ports=AuthoredToolRuntimePorts(
|
|
659
674
|
chat_model_factory=runtime_config.chat_model_factory,
|
|
660
|
-
|
|
661
|
-
resource_reader=resource_reader,
|
|
675
|
+
workspace_fs=workspace_fs,
|
|
662
676
|
fallback_tool_invoker=base_tool_invoker,
|
|
663
677
|
media_fetcher=_build_media_fetcher(binding=binding, settings=settings),
|
|
664
678
|
),
|
|
@@ -688,8 +702,7 @@ def _build_runtime_services(
|
|
|
688
702
|
chat_model_factory=runtime_config.chat_model_factory,
|
|
689
703
|
tool_invoker=tool_invoker,
|
|
690
704
|
tool_provider=tool_provider,
|
|
691
|
-
|
|
692
|
-
resource_reader=resource_reader,
|
|
705
|
+
workspace_fs=workspace_fs,
|
|
693
706
|
checkpointer=runtime_config.checkpointer,
|
|
694
707
|
agent_invoker=agent_invoker,
|
|
695
708
|
)
|
|
@@ -835,6 +848,7 @@ class _McpCatalogResponse(BaseModel):
|
|
|
835
848
|
class _ResolvedAgentInstance(BaseModel):
|
|
836
849
|
agent_instance_id: str
|
|
837
850
|
template_agent_id: str
|
|
851
|
+
display_name: str = ""
|
|
838
852
|
owner_scope: str
|
|
839
853
|
owner_user_id: str | None = None
|
|
840
854
|
owner_team_id: str | None = None
|
|
@@ -847,6 +861,7 @@ class _ResolvedExecutionTarget:
|
|
|
847
861
|
definition: ReActAgentDefinition | GraphAgentDefinition
|
|
848
862
|
effective_agent_id: str
|
|
849
863
|
team_id: str | None = None
|
|
864
|
+
agent_instance_name: str | None = None
|
|
850
865
|
|
|
851
866
|
|
|
852
867
|
def _apply_runtime_tuning(
|
|
@@ -1035,6 +1050,7 @@ async def _resolve_agent_instance(
|
|
|
1035
1050
|
),
|
|
1036
1051
|
effective_agent_id=resolution.agent_instance_id,
|
|
1037
1052
|
team_id=resolution.owner_team_id,
|
|
1053
|
+
agent_instance_name=resolution.display_name or None,
|
|
1038
1054
|
)
|
|
1039
1055
|
|
|
1040
1056
|
|
|
@@ -1578,6 +1594,7 @@ def _emit_turn_completed(
|
|
|
1578
1594
|
user_id: str,
|
|
1579
1595
|
team_id: str | None,
|
|
1580
1596
|
agent_instance_id: str | None,
|
|
1597
|
+
agent_instance_name: str | None,
|
|
1581
1598
|
template_agent_id: str | None,
|
|
1582
1599
|
payloads: list[dict[str, Any]],
|
|
1583
1600
|
turn_start: float,
|
|
@@ -1611,6 +1628,8 @@ def _emit_turn_completed(
|
|
|
1611
1628
|
prom_dims: dict[str, str | None] = {
|
|
1612
1629
|
"team_id": team_id,
|
|
1613
1630
|
"template_agent_id": template_agent_id,
|
|
1631
|
+
"agent_instance_id": agent_instance_id,
|
|
1632
|
+
"agent_instance_name": agent_instance_name,
|
|
1614
1633
|
"runtime_id": runtime_id,
|
|
1615
1634
|
"model_name": outcome.model_name,
|
|
1616
1635
|
"finish_reason": outcome.finish_reason,
|
|
@@ -1627,7 +1646,7 @@ def _emit_turn_completed(
|
|
|
1627
1646
|
"input_tokens": outcome.input_tokens,
|
|
1628
1647
|
"output_tokens": outcome.output_tokens,
|
|
1629
1648
|
},
|
|
1630
|
-
actor=KPIActor(type="
|
|
1649
|
+
actor=KPIActor(type="human", user_id=user_id),
|
|
1631
1650
|
)
|
|
1632
1651
|
|
|
1633
1652
|
if outcome.is_error:
|
|
@@ -1636,7 +1655,7 @@ def _emit_turn_completed(
|
|
|
1636
1655
|
type="counter",
|
|
1637
1656
|
value=1,
|
|
1638
1657
|
dims=prom_dims,
|
|
1639
|
-
actor=KPIActor(type="
|
|
1658
|
+
actor=KPIActor(type="human", user_id=user_id),
|
|
1640
1659
|
)
|
|
1641
1660
|
|
|
1642
1661
|
# Append to container ring buffer (high-cardinality fields safe here).
|
|
@@ -1667,6 +1686,7 @@ async def _stream(
|
|
|
1667
1686
|
access_token: str | None = None,
|
|
1668
1687
|
*,
|
|
1669
1688
|
team_id: str | None = None,
|
|
1689
|
+
agent_instance_name: str | None = None,
|
|
1670
1690
|
registry: Mapping[str, ReActAgentDefinition | GraphAgentDefinition] | None = None,
|
|
1671
1691
|
security_enabled: bool = False,
|
|
1672
1692
|
container: PodApplicationContext,
|
|
@@ -1719,6 +1739,7 @@ async def _stream(
|
|
|
1719
1739
|
user_id=user_id,
|
|
1720
1740
|
team_id=resolved_team_id,
|
|
1721
1741
|
agent_instance_id=request.agent_instance_id,
|
|
1742
|
+
agent_instance_name=agent_instance_name,
|
|
1722
1743
|
template_agent_id=definition.agent_id,
|
|
1723
1744
|
payloads=collected,
|
|
1724
1745
|
turn_start=turn_start,
|
|
@@ -2612,6 +2633,7 @@ def _build_agent_router(
|
|
|
2612
2633
|
user_id=user_id_str,
|
|
2613
2634
|
team_id=target.team_id,
|
|
2614
2635
|
agent_instance_id=request.agent_instance_id,
|
|
2636
|
+
agent_instance_name=target.agent_instance_name,
|
|
2615
2637
|
template_agent_id=target.definition.agent_id,
|
|
2616
2638
|
payloads=payloads,
|
|
2617
2639
|
turn_start=turn_start,
|
|
@@ -2715,6 +2737,7 @@ def _build_agent_router(
|
|
|
2715
2737
|
user_id=user_id_str,
|
|
2716
2738
|
team_id=target.team_id,
|
|
2717
2739
|
agent_instance_id=request.agent_instance_id,
|
|
2740
|
+
agent_instance_name=target.agent_instance_name,
|
|
2718
2741
|
template_agent_id=target.definition.agent_id,
|
|
2719
2742
|
payloads=payloads,
|
|
2720
2743
|
turn_start=turn_start,
|
|
@@ -2825,6 +2848,7 @@ def _build_agent_router(
|
|
|
2825
2848
|
internal_req,
|
|
2826
2849
|
access_token=access_token,
|
|
2827
2850
|
team_id=target.team_id,
|
|
2851
|
+
agent_instance_name=target.agent_instance_name,
|
|
2828
2852
|
registry=registry,
|
|
2829
2853
|
security_enabled=security_enabled,
|
|
2830
2854
|
container=container,
|
|
@@ -2951,7 +2975,7 @@ def create_agent_app(
|
|
|
2951
2975
|
"enabled" if security_enabled else "disabled",
|
|
2952
2976
|
"sql" if container.get_checkpointer() is not None else "none",
|
|
2953
2977
|
"sql" if container.get_history_store() is not None else "none",
|
|
2954
|
-
config.observability.
|
|
2978
|
+
"prometheus" if config.observability.kpi.prometheus.enabled else "logging",
|
|
2955
2979
|
list(registry.keys()),
|
|
2956
2980
|
)
|
|
2957
2981
|
yield
|
|
@@ -2977,6 +3001,13 @@ def create_agent_app(
|
|
|
2977
3001
|
)
|
|
2978
3002
|
logger.debug("[fred-runtime] CORS allow_origins=%s", authorized_origins)
|
|
2979
3003
|
|
|
3004
|
+
# KPI middleware — writer is lazily resolved from app.state because the
|
|
3005
|
+
# container (and its KPI writer) is only initialised during lifespan startup.
|
|
3006
|
+
app.add_middleware(
|
|
3007
|
+
KPIMiddleware,
|
|
3008
|
+
kpi=lambda: get_pod_container_from_app(app).get_kpi_writer(),
|
|
3009
|
+
)
|
|
3010
|
+
|
|
2980
3011
|
api_router = APIRouter(prefix=base_url)
|
|
2981
3012
|
api_router.include_router(
|
|
2982
3013
|
_build_agent_router(registry, security_enabled=security_enabled)
|
|
@@ -70,6 +70,7 @@ if TYPE_CHECKING:
|
|
|
70
70
|
from fred_runtime.runtime_context import McpConfigurationLike
|
|
71
71
|
|
|
72
72
|
from fred_core.common import (
|
|
73
|
+
KpiObservabilityConfig,
|
|
73
74
|
OpenSearchStoreConfig,
|
|
74
75
|
PostgresStoreConfig,
|
|
75
76
|
TemporalSchedulerConfig,
|
|
@@ -120,26 +121,6 @@ class PodAppConfig(BaseModel):
|
|
|
120
121
|
),
|
|
121
122
|
)
|
|
122
123
|
gcu_version: str | None = None
|
|
123
|
-
metrics_address: str = "127.0.0.1"
|
|
124
|
-
metrics_port: int = 9000
|
|
125
|
-
kpi_process_metrics_interval_sec: int = Field(
|
|
126
|
-
default=0,
|
|
127
|
-
description=(
|
|
128
|
-
"Emit process and SQL pool KPIs every N seconds. Set 0 to disable "
|
|
129
|
-
"the background emitters."
|
|
130
|
-
),
|
|
131
|
-
)
|
|
132
|
-
kpi_log_summary_interval_sec: float = Field(
|
|
133
|
-
default=0.0,
|
|
134
|
-
description=(
|
|
135
|
-
"Emit periodic KPI summary logs every N seconds for local benches. "
|
|
136
|
-
"Set 0 to disable."
|
|
137
|
-
),
|
|
138
|
-
)
|
|
139
|
-
kpi_log_summary_top_n: int = Field(
|
|
140
|
-
default=0,
|
|
141
|
-
description="Top-N KPI summary rows to log. 0 means all / disabled.",
|
|
142
|
-
)
|
|
143
124
|
openai_compat: bool = True
|
|
144
125
|
"""
|
|
145
126
|
Enable the OpenAI-compatible /v1/chat/completions and /v1/models endpoints.
|
|
@@ -209,21 +190,6 @@ class TracerBackend(str, Enum):
|
|
|
209
190
|
langfuse = "langfuse"
|
|
210
191
|
|
|
211
192
|
|
|
212
|
-
class MetricsBackend(str, Enum):
|
|
213
|
-
"""
|
|
214
|
-
Metrics emission backend for the agent pod.
|
|
215
|
-
|
|
216
|
-
- null — no metrics, all timer events are dropped
|
|
217
|
-
- logging — each timer is emitted as a structured log entry (default)
|
|
218
|
-
- prometheus — KPI/process metrics are exported in Prometheus format on the
|
|
219
|
-
dedicated metrics port configured under `app`
|
|
220
|
-
"""
|
|
221
|
-
|
|
222
|
-
null = "null"
|
|
223
|
-
logging = "logging"
|
|
224
|
-
prometheus = "prometheus"
|
|
225
|
-
|
|
226
|
-
|
|
227
193
|
class LangfuseObservabilityConfig(BaseModel):
|
|
228
194
|
"""
|
|
229
195
|
Langfuse connection settings.
|
|
@@ -245,14 +211,20 @@ class PodObservabilityConfig(BaseModel):
|
|
|
245
211
|
Example:
|
|
246
212
|
observability:
|
|
247
213
|
tracer: logging # null | logging | langfuse
|
|
248
|
-
|
|
214
|
+
kpi:
|
|
215
|
+
log:
|
|
216
|
+
enabled: true
|
|
217
|
+
prometheus:
|
|
218
|
+
enabled: false
|
|
219
|
+
opensearch:
|
|
220
|
+
enabled: false
|
|
249
221
|
langfuse:
|
|
250
222
|
host: "http://localhost:3001"
|
|
251
223
|
# LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY in .env
|
|
252
224
|
"""
|
|
253
225
|
|
|
254
226
|
tracer: TracerBackend = TracerBackend.logging
|
|
255
|
-
|
|
227
|
+
kpi: KpiObservabilityConfig = Field(default_factory=KpiObservabilityConfig)
|
|
256
228
|
langfuse: LangfuseObservabilityConfig = Field(
|
|
257
229
|
default_factory=LangfuseObservabilityConfig
|
|
258
230
|
)
|
|
@@ -24,16 +24,14 @@ from datetime import datetime, timezone
|
|
|
24
24
|
from typing import TYPE_CHECKING, TypedDict
|
|
25
25
|
|
|
26
26
|
from fred_core.kpi.base_kpi_writer import BaseKPIWriter
|
|
27
|
+
from fred_core.kpi.kpi_factory import build_kpi_writer
|
|
27
28
|
from fred_core.kpi.kpi_process import emit_process_kpis, emit_sql_pool_kpis
|
|
28
|
-
from fred_core.kpi.kpi_writer import KPIDefaults, KPIWriter
|
|
29
|
-
from fred_core.kpi.log_kpi_store import KpiLogStore
|
|
30
29
|
from fred_core.kpi.noop_kpi_writer import NoOpKPIWriter
|
|
31
|
-
from fred_core.kpi.prometheus_kpi_store import PrometheusKPIStore
|
|
32
30
|
from fred_sdk.contracts.runtime import HistoryStorePort
|
|
33
31
|
from prometheus_client import start_http_server
|
|
34
32
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
35
33
|
|
|
36
|
-
from fred_runtime.app.config import AgentPodConfig
|
|
34
|
+
from fred_runtime.app.config import AgentPodConfig
|
|
37
35
|
|
|
38
36
|
if TYPE_CHECKING:
|
|
39
37
|
pass
|
|
@@ -117,18 +115,11 @@ class PodApplicationContext:
|
|
|
117
115
|
def initialize_kpi_writer(self) -> None:
|
|
118
116
|
"""Build the KPI writer from pod observability config."""
|
|
119
117
|
config = self.configuration
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if backend == MetricsBackend.prometheus:
|
|
126
|
-
store = PrometheusKPIStore(delegate=store) # type: ignore[arg-type]
|
|
127
|
-
self._kpi_writer = KPIWriter(
|
|
128
|
-
store=store,
|
|
129
|
-
defaults=KPIDefaults(static_dims={"service": "fred-runtime"}),
|
|
130
|
-
summary_interval_s=config.app.kpi_log_summary_interval_sec,
|
|
131
|
-
summary_top_n=config.app.kpi_log_summary_top_n,
|
|
118
|
+
self._kpi_writer = build_kpi_writer(
|
|
119
|
+
kpi_config=config.observability.kpi,
|
|
120
|
+
opensearch_config=config.storage.opensearch,
|
|
121
|
+
service_name="fred-runtime",
|
|
122
|
+
log_level=config.app.log_level,
|
|
132
123
|
)
|
|
133
124
|
|
|
134
125
|
async def initialize_sql(self) -> None:
|
|
@@ -160,25 +151,23 @@ class PodApplicationContext:
|
|
|
160
151
|
|
|
161
152
|
def start_metrics_exporter(self) -> None:
|
|
162
153
|
"""Start the Prometheus scrape endpoint when configured."""
|
|
163
|
-
|
|
164
|
-
if
|
|
154
|
+
prom_cfg = self.configuration.observability.kpi.prometheus
|
|
155
|
+
if not prom_cfg.enabled:
|
|
165
156
|
return
|
|
166
|
-
result = start_http_server(
|
|
167
|
-
config.app.metrics_port,
|
|
168
|
-
addr=config.app.metrics_address,
|
|
169
|
-
)
|
|
157
|
+
result = start_http_server(prom_cfg.port, addr=prom_cfg.address)
|
|
170
158
|
self._metrics_exporter = result if isinstance(result, tuple) else None
|
|
171
159
|
logger.info(
|
|
172
160
|
"[fred-runtime] Prometheus metrics exporter ready at %s:%s",
|
|
173
|
-
|
|
174
|
-
|
|
161
|
+
prom_cfg.address,
|
|
162
|
+
prom_cfg.port,
|
|
175
163
|
)
|
|
176
164
|
|
|
177
165
|
async def start_kpi_tasks(self) -> None:
|
|
178
166
|
"""Start background KPI flush tasks (process + SQL pool health)."""
|
|
179
167
|
kpi_writer = self.get_kpi_writer()
|
|
180
|
-
|
|
181
|
-
|
|
168
|
+
interval_s = float(
|
|
169
|
+
self.configuration.observability.kpi.process_metrics_interval_sec
|
|
170
|
+
)
|
|
182
171
|
if interval_s <= 0 or isinstance(kpi_writer, NoOpKPIWriter):
|
|
183
172
|
return
|
|
184
173
|
tasks: list[asyncio.Task[None]] = [
|
|
@@ -51,7 +51,7 @@ from fred_core.portable import (
|
|
|
51
51
|
set_tracer,
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
from .config import
|
|
54
|
+
from .config import PodObservabilityConfig, TracerBackend
|
|
55
55
|
|
|
56
56
|
logger = logging.getLogger(__name__)
|
|
57
57
|
|
|
@@ -83,9 +83,9 @@ def bootstrap_observability(
|
|
|
83
83
|
set_tracer(tracer)
|
|
84
84
|
set_metrics_provider(metrics)
|
|
85
85
|
logger.info(
|
|
86
|
-
"[fred-runtime] observability ready — tracer=%s
|
|
86
|
+
"[fred-runtime] observability ready — tracer=%s prometheus=%s",
|
|
87
87
|
config.tracer.value,
|
|
88
|
-
config.
|
|
88
|
+
config.kpi.prometheus.enabled,
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
|
|
@@ -160,15 +160,10 @@ def _build_metrics(
|
|
|
160
160
|
*,
|
|
161
161
|
kpi_writer: BaseKPIWriter | None = None,
|
|
162
162
|
) -> MetricsProvider:
|
|
163
|
-
if config.
|
|
164
|
-
return MetricsProvider()
|
|
165
|
-
if config.metrics == MetricsBackend.logging:
|
|
166
|
-
return LoggingMetricsProvider()
|
|
167
|
-
if config.metrics == MetricsBackend.prometheus:
|
|
163
|
+
if config.kpi.prometheus.enabled:
|
|
168
164
|
if kpi_writer is None:
|
|
169
165
|
logger.warning(
|
|
170
|
-
"[fred-runtime]
|
|
171
|
-
" — falling back to logging"
|
|
166
|
+
"[fred-runtime] prometheus enabled without a KPI writer — falling back to logging"
|
|
172
167
|
)
|
|
173
168
|
return LoggingMetricsProvider()
|
|
174
169
|
from fred_runtime.integrations.v2_runtime.adapters import (
|
|
@@ -176,8 +171,4 @@ def _build_metrics(
|
|
|
176
171
|
)
|
|
177
172
|
|
|
178
173
|
return cast(MetricsProvider, KPIWriterMetricsAdapter(kpi_writer))
|
|
179
|
-
logger.warning(
|
|
180
|
-
"[fred-runtime] Unknown metrics backend '%s' — falling back to logging",
|
|
181
|
-
config.metrics,
|
|
182
|
-
)
|
|
183
174
|
return LoggingMetricsProvider()
|
|
@@ -163,23 +163,57 @@ class ContextAwareTool(BaseTool):
|
|
|
163
163
|
|
|
164
164
|
tool_properties = self._get_tool_properties()
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
# Snapshot what the agent (or LLM) explicitly requested, so every decision
|
|
167
|
+
# below can be logged as "requested -> applied" with no ambiguity. The
|
|
168
|
+
# picker selection (RuntimeContext) is a default scope an agent may NARROW;
|
|
169
|
+
# an explicit per-call scope is respected, never silently overwritten.
|
|
170
|
+
caller_document_uids = kwargs.get("document_uids")
|
|
171
|
+
caller_library_ids = kwargs.get("document_library_tags_ids")
|
|
172
|
+
picker_document_uids = get_document_uids(context)
|
|
173
|
+
picker_library_ids = get_document_library_tags_ids(context)
|
|
174
|
+
|
|
175
|
+
# Document scope is the most specific selector. If the agent passed it,
|
|
176
|
+
# keep it verbatim; otherwise fill from the picker selection.
|
|
177
|
+
if "document_uids" in tool_properties:
|
|
178
|
+
if caller_document_uids:
|
|
179
|
+
logger.info(
|
|
180
|
+
"ContextAwareTool(%s) document_uids: agent-scoped=%s (picker=%s NOT applied)",
|
|
181
|
+
self.name,
|
|
182
|
+
caller_document_uids,
|
|
183
|
+
picker_document_uids,
|
|
184
|
+
)
|
|
185
|
+
elif picker_document_uids:
|
|
186
|
+
kwargs["document_uids"] = picker_document_uids
|
|
187
|
+
logger.info(
|
|
188
|
+
"ContextAwareTool(%s) document_uids: applied picker selection=%s",
|
|
189
|
+
self.name,
|
|
190
|
+
picker_document_uids,
|
|
191
|
+
)
|
|
174
192
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
193
|
+
# Library scope is broader than document scope. Inject the picker libraries
|
|
194
|
+
# only when the agent did not scope by library AND did not scope by document
|
|
195
|
+
# (a per-call document scope must not be widened back to whole libraries).
|
|
196
|
+
if "document_library_tags_ids" in tool_properties:
|
|
197
|
+
if caller_library_ids:
|
|
198
|
+
logger.info(
|
|
199
|
+
"ContextAwareTool(%s) library: agent-scoped=%s (picker=%s NOT applied)",
|
|
200
|
+
self.name,
|
|
201
|
+
caller_library_ids,
|
|
202
|
+
picker_library_ids,
|
|
203
|
+
)
|
|
204
|
+
elif caller_document_uids:
|
|
205
|
+
logger.info(
|
|
206
|
+
"ContextAwareTool(%s) library: NOT injected — agent scoped by document_uids=%s",
|
|
207
|
+
self.name,
|
|
208
|
+
caller_document_uids,
|
|
209
|
+
)
|
|
210
|
+
elif picker_library_ids:
|
|
211
|
+
kwargs["document_library_tags_ids"] = picker_library_ids
|
|
212
|
+
logger.info(
|
|
213
|
+
"ContextAwareTool(%s) library: applied picker selection=%s",
|
|
214
|
+
self.name,
|
|
215
|
+
picker_library_ids,
|
|
216
|
+
)
|
|
183
217
|
|
|
184
218
|
session_id = context.session_id
|
|
185
219
|
if (
|