fred-runtime 2.0.0__tar.gz → 2.0.1__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.0 → fred_runtime-2.0.1}/PKG-INFO +1 -1
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/agent_app.py +159 -69
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime.egg-info/PKG-INFO +1 -1
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/pyproject.toml +1 -1
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_agent_app.py +150 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/README.md +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/config.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/container.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/context.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/history_display.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/pod_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/repl.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_logs_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/kf_workspace_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/deep/deep_runtime.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/graph/graph_runtime.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/integrations/v2_runtime/adapters.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_message_codec.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_prompting.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_runtime.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_stream_adapter.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_tool_binding.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_tool_resolution.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_context.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/support/tool_approval.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime.egg-info/SOURCES.txt +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime.egg-info/requires.txt +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/setup.cfg +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_config_loader.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_context.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_graph_runtime_observability.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_history.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_kf_workspace_client.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_kpi_display.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_mcp_config.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_openai_compat_router.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_smoke.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_url_helpers.py +0 -0
- {fred_runtime-2.0.0 → fred_runtime-2.0.1}/tests/test_user_token_refresher.py +0 -0
|
@@ -558,12 +558,8 @@ class LocalRegistryAgentInvoker(AgentInvokerPort):
|
|
|
558
558
|
is_error=True,
|
|
559
559
|
)
|
|
560
560
|
|
|
561
|
-
execute_request =
|
|
562
|
-
|
|
563
|
-
agent_instance_id=None,
|
|
564
|
-
message=request.message,
|
|
565
|
-
context=request.context.model_dump(mode="json"),
|
|
566
|
-
resume_payload=None,
|
|
561
|
+
execute_request = _to_internal_request(
|
|
562
|
+
_build_runtime_execute_request_from_invocation(request)
|
|
567
563
|
)
|
|
568
564
|
|
|
569
565
|
content_parts: list[str] = []
|
|
@@ -770,6 +766,13 @@ class _AgentExecuteRequest(BaseModel):
|
|
|
770
766
|
return self
|
|
771
767
|
|
|
772
768
|
|
|
769
|
+
@dataclass(slots=True)
|
|
770
|
+
class _PreparedRuntimeExecution:
|
|
771
|
+
runtime: ReActRuntime | GraphRuntime
|
|
772
|
+
execution_config: ExecutionConfig
|
|
773
|
+
executor_input: Any
|
|
774
|
+
|
|
775
|
+
|
|
773
776
|
def _to_internal_request(r: RuntimeExecuteRequest) -> "_AgentExecuteRequest":
|
|
774
777
|
"""
|
|
775
778
|
Bridge a public RuntimeExecuteRequest to the internal execution model.
|
|
@@ -794,6 +797,36 @@ def _to_internal_request(r: RuntimeExecuteRequest) -> "_AgentExecuteRequest":
|
|
|
794
797
|
)
|
|
795
798
|
|
|
796
799
|
|
|
800
|
+
def _build_runtime_execute_request_from_invocation(
|
|
801
|
+
request: AgentInvocationRequest,
|
|
802
|
+
) -> RuntimeExecuteRequest:
|
|
803
|
+
"""
|
|
804
|
+
Project one in-process agent invocation onto the public execute contract.
|
|
805
|
+
|
|
806
|
+
Why this exists:
|
|
807
|
+
- pod-local agent-to-agent calls should follow the same request projection
|
|
808
|
+
path as HTTP execution, rather than hand-constructing a second private
|
|
809
|
+
request shape
|
|
810
|
+
- future continuity fields should therefore land once on the typed runtime
|
|
811
|
+
contract, then flow through both local and remote invocation paths
|
|
812
|
+
|
|
813
|
+
How to use it:
|
|
814
|
+
- call from `LocalRegistryAgentInvoker.invoke(...)`
|
|
815
|
+
- pass the result through `_to_internal_request(...)` until the remaining
|
|
816
|
+
internal helpers consume `RuntimeExecuteRequest` directly
|
|
817
|
+
|
|
818
|
+
Example:
|
|
819
|
+
- `runtime_request = _build_runtime_execute_request_from_invocation(request)`
|
|
820
|
+
"""
|
|
821
|
+
|
|
822
|
+
return RuntimeExecuteRequest(
|
|
823
|
+
agent_id=request.agent_id,
|
|
824
|
+
input=request.message,
|
|
825
|
+
session_id=request.context.session_id,
|
|
826
|
+
runtime_context=request.context.model_dump(mode="json"),
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
|
|
797
830
|
class _AgentTemplateSummary(BaseModel):
|
|
798
831
|
template_agent_id: str
|
|
799
832
|
title: str
|
|
@@ -838,17 +871,23 @@ def _apply_runtime_tuning(
|
|
|
838
871
|
- `definition = _apply_runtime_tuning(template_definition, resolution.tuning)`
|
|
839
872
|
"""
|
|
840
873
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
874
|
+
update: dict[str, Any] = {
|
|
875
|
+
"role": tuning.role,
|
|
876
|
+
"description": tuning.description,
|
|
877
|
+
"tags": tuple(tuning.tags),
|
|
878
|
+
"fields": tuple(field.model_copy(deep=True) for field in tuning.fields),
|
|
879
|
+
"default_mcp_servers": tuple(
|
|
880
|
+
server.model_copy(deep=True) for server in tuning.mcp_servers
|
|
881
|
+
),
|
|
882
|
+
}
|
|
883
|
+
system_prompt = tuning.values.get("prompts.system")
|
|
884
|
+
if (
|
|
885
|
+
isinstance(definition, ReActAgentDefinition)
|
|
886
|
+
and isinstance(system_prompt, str)
|
|
887
|
+
and system_prompt.strip()
|
|
888
|
+
):
|
|
889
|
+
update["system_prompt_template"] = system_prompt
|
|
890
|
+
return definition.model_copy(update=update)
|
|
852
891
|
|
|
853
892
|
|
|
854
893
|
def _available_mcp_servers_for_definition(
|
|
@@ -1565,7 +1604,42 @@ async def _stream(
|
|
|
1565
1604
|
)
|
|
1566
1605
|
|
|
1567
1606
|
|
|
1568
|
-
|
|
1607
|
+
def _build_executor_input(
|
|
1608
|
+
definition: ReActAgentDefinition | GraphAgentDefinition,
|
|
1609
|
+
request: _AgentExecuteRequest,
|
|
1610
|
+
) -> Any:
|
|
1611
|
+
"""
|
|
1612
|
+
Normalize one turn into the executor input expected by the selected runtime.
|
|
1613
|
+
|
|
1614
|
+
Why this exists:
|
|
1615
|
+
- `ReActRuntime` and `GraphRuntime` accept different input shapes
|
|
1616
|
+
- resume turns also bypass normal message validation, so the mapping should
|
|
1617
|
+
live in one helper instead of being repeated inline in the execution loop
|
|
1618
|
+
|
|
1619
|
+
How to use it:
|
|
1620
|
+
- call while assembling one prepared runtime execution
|
|
1621
|
+
- pass the returned object unchanged to `executor.stream(...)`
|
|
1622
|
+
|
|
1623
|
+
Example:
|
|
1624
|
+
- `executor_input = _build_executor_input(definition, request)`
|
|
1625
|
+
"""
|
|
1626
|
+
|
|
1627
|
+
if isinstance(definition, GraphAgentDefinition):
|
|
1628
|
+
input_cls = definition.input_model()
|
|
1629
|
+
if request.resume_payload is not None:
|
|
1630
|
+
return input_cls.model_construct(message="")
|
|
1631
|
+
return input_cls.model_validate({"message": request.message or ""})
|
|
1632
|
+
|
|
1633
|
+
return ReActInput(
|
|
1634
|
+
messages=(
|
|
1635
|
+
()
|
|
1636
|
+
if request.resume_payload is not None
|
|
1637
|
+
else (ReActMessage(role=ReActMessageRole.USER, content=request.message),)
|
|
1638
|
+
),
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
|
|
1642
|
+
def _prepare_runtime_execution(
|
|
1569
1643
|
definition: ReActAgentDefinition | GraphAgentDefinition,
|
|
1570
1644
|
request: _AgentExecuteRequest,
|
|
1571
1645
|
access_token: str | None = None,
|
|
@@ -1573,26 +1647,24 @@ async def _iterate_runtime_event_payloads(
|
|
|
1573
1647
|
team_id: str | None = None,
|
|
1574
1648
|
registry: Mapping[str, ReActAgentDefinition | GraphAgentDefinition] | None = None,
|
|
1575
1649
|
exchange_id: str | None = None,
|
|
1576
|
-
) ->
|
|
1650
|
+
) -> _PreparedRuntimeExecution:
|
|
1577
1651
|
"""
|
|
1578
|
-
|
|
1652
|
+
Build the bound runtime, executor input, and execution config for one turn.
|
|
1579
1653
|
|
|
1580
|
-
Why this
|
|
1581
|
-
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1654
|
+
Why this exists:
|
|
1655
|
+
- `execute`, `execute/stream`, and in-process agent invocation all converge
|
|
1656
|
+
on `_iterate_runtime_event_payloads`, so this is the narrowest place to
|
|
1657
|
+
centralize request projection before memory fields are added
|
|
1658
|
+
- it removes one long block of binding/runtime setup from the event loop and
|
|
1659
|
+
gives future continuity fields a single place to enter the runtime stack
|
|
1585
1660
|
|
|
1586
|
-
|
|
1587
|
-
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
team scope is required
|
|
1661
|
+
How to use it:
|
|
1662
|
+
- call from `_iterate_runtime_event_payloads(...)`
|
|
1663
|
+
- activate the returned runtime, obtain its executor, then stream with the
|
|
1664
|
+
returned `executor_input` and `execution_config`
|
|
1591
1665
|
|
|
1592
|
-
|
|
1593
|
-
-
|
|
1594
|
-
- stored in RuntimeContext so KF tool adapters can use it for outbound calls
|
|
1595
|
-
- None in local dev when security is disabled
|
|
1666
|
+
Example:
|
|
1667
|
+
- `prepared = _prepare_runtime_execution(definition, request, team_id="fredlab")`
|
|
1596
1668
|
"""
|
|
1597
1669
|
|
|
1598
1670
|
request_id = str(uuid4())
|
|
@@ -1651,7 +1723,6 @@ async def _iterate_runtime_event_payloads(
|
|
|
1651
1723
|
runtime_context=runtime_context,
|
|
1652
1724
|
portable_context=portable_context,
|
|
1653
1725
|
)
|
|
1654
|
-
|
|
1655
1726
|
services = _build_runtime_services(
|
|
1656
1727
|
definition,
|
|
1657
1728
|
binding,
|
|
@@ -1659,8 +1730,9 @@ async def _iterate_runtime_event_payloads(
|
|
|
1659
1730
|
registry=registry,
|
|
1660
1731
|
access_token=access_token,
|
|
1661
1732
|
)
|
|
1733
|
+
runtime: ReActRuntime | GraphRuntime
|
|
1662
1734
|
if isinstance(definition, GraphAgentDefinition):
|
|
1663
|
-
runtime
|
|
1735
|
+
runtime = GraphRuntime(
|
|
1664
1736
|
definition=definition,
|
|
1665
1737
|
services=services,
|
|
1666
1738
|
)
|
|
@@ -1679,40 +1751,58 @@ async def _iterate_runtime_event_payloads(
|
|
|
1679
1751
|
checkpoint_id=request.checkpoint_id,
|
|
1680
1752
|
resume_payload=request.resume_payload,
|
|
1681
1753
|
)
|
|
1754
|
+
return _PreparedRuntimeExecution(
|
|
1755
|
+
runtime=runtime,
|
|
1756
|
+
execution_config=execution_config,
|
|
1757
|
+
executor_input=_build_executor_input(definition, request),
|
|
1758
|
+
)
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
async def _iterate_runtime_event_payloads(
|
|
1762
|
+
definition: ReActAgentDefinition | GraphAgentDefinition,
|
|
1763
|
+
request: _AgentExecuteRequest,
|
|
1764
|
+
access_token: str | None = None,
|
|
1765
|
+
*,
|
|
1766
|
+
team_id: str | None = None,
|
|
1767
|
+
registry: Mapping[str, ReActAgentDefinition | GraphAgentDefinition] | None = None,
|
|
1768
|
+
exchange_id: str | None = None,
|
|
1769
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
1770
|
+
"""
|
|
1771
|
+
Execute one agent turn and yield runtime-event payloads as JSON-ready dicts.
|
|
1772
|
+
|
|
1773
|
+
Why this helper exists:
|
|
1774
|
+
- both `/agents/execute` and `/agents/execute/stream` share the same runtime
|
|
1775
|
+
wiring and event production path
|
|
1776
|
+
- keeping the generator payload-oriented lets the HTTP layer choose whether
|
|
1777
|
+
it renders SSE or returns a terminal JSON response
|
|
1778
|
+
|
|
1779
|
+
team_id:
|
|
1780
|
+
- callers are responsible for resolving the effective team before calling this
|
|
1781
|
+
function; see _stream() for the standalone "personal" default logic
|
|
1782
|
+
- None is accepted for agent-to-agent (AgentInvoker) invocations where no
|
|
1783
|
+
team scope is required
|
|
1784
|
+
|
|
1785
|
+
access_token:
|
|
1786
|
+
- the user's JWT forwarded via the Authorization header
|
|
1787
|
+
- stored in RuntimeContext so KF tool adapters can use it for outbound calls
|
|
1788
|
+
- None in local dev when security is disabled
|
|
1789
|
+
"""
|
|
1790
|
+
prepared = _prepare_runtime_execution(
|
|
1791
|
+
definition,
|
|
1792
|
+
request,
|
|
1793
|
+
access_token=access_token,
|
|
1794
|
+
team_id=team_id,
|
|
1795
|
+
registry=registry,
|
|
1796
|
+
exchange_id=exchange_id,
|
|
1797
|
+
)
|
|
1682
1798
|
|
|
1683
1799
|
try:
|
|
1684
|
-
await runtime.activate()
|
|
1685
|
-
executor = await runtime.get_executor()
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
# On a HITL resume the runtime ignores input entirely (state is loaded
|
|
1691
|
-
# from the checkpoint), so bypass validation with model_construct.
|
|
1692
|
-
input_cls = definition.input_model()
|
|
1693
|
-
if request.resume_payload is not None:
|
|
1694
|
-
graph_input = input_cls.model_construct(message="")
|
|
1695
|
-
else:
|
|
1696
|
-
graph_input = input_cls.model_validate(
|
|
1697
|
-
{"message": request.message or ""}
|
|
1698
|
-
)
|
|
1699
|
-
executor_input: ReActInput | object = graph_input
|
|
1700
|
-
else:
|
|
1701
|
-
# On HITL resume, messages are ignored by the codec — the graph
|
|
1702
|
-
# resumes from its checkpointed interrupt via Command(resume=...).
|
|
1703
|
-
# On a normal turn, the user message is the only input.
|
|
1704
|
-
executor_input = ReActInput(
|
|
1705
|
-
messages=(
|
|
1706
|
-
()
|
|
1707
|
-
if request.resume_payload is not None
|
|
1708
|
-
else (
|
|
1709
|
-
ReActMessage(
|
|
1710
|
-
role=ReActMessageRole.USER, content=request.message
|
|
1711
|
-
),
|
|
1712
|
-
)
|
|
1713
|
-
),
|
|
1714
|
-
)
|
|
1715
|
-
async for event in executor.stream(executor_input, execution_config):
|
|
1800
|
+
await prepared.runtime.activate()
|
|
1801
|
+
executor = await prepared.runtime.get_executor()
|
|
1802
|
+
async for event in executor.stream(
|
|
1803
|
+
prepared.executor_input,
|
|
1804
|
+
prepared.execution_config,
|
|
1805
|
+
):
|
|
1716
1806
|
payload = event.model_dump(mode="json")
|
|
1717
1807
|
if not isinstance(payload, dict):
|
|
1718
1808
|
raise RuntimeError(
|
|
@@ -1725,7 +1815,7 @@ async def _iterate_runtime_event_payloads(
|
|
|
1725
1815
|
)
|
|
1726
1816
|
yield RuntimeErrorEvent(message=str(exc)).model_dump(mode="json")
|
|
1727
1817
|
finally:
|
|
1728
|
-
await runtime.dispose()
|
|
1818
|
+
await prepared.runtime.dispose()
|
|
1729
1819
|
|
|
1730
1820
|
|
|
1731
1821
|
def _terminal_execute_payload(
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
import time
|
|
6
6
|
from types import SimpleNamespace
|
|
7
|
+
from typing import cast
|
|
7
8
|
|
|
8
9
|
from conftest import StaticChatModelFactory, ToolFriendlyFakeChatModel
|
|
9
10
|
from fastapi.testclient import TestClient
|
|
@@ -14,6 +15,11 @@ from fred_core.kpi.prometheus_kpi_store import PrometheusKPIStore
|
|
|
14
15
|
from fred_core.users.store import postgres_user_store
|
|
15
16
|
from fred_sdk.authoring import ReActAgent, tool
|
|
16
17
|
from fred_sdk.authoring.api import ToolContext
|
|
18
|
+
from fred_sdk.contracts.context import (
|
|
19
|
+
AgentInvocationRequest,
|
|
20
|
+
PortableContext,
|
|
21
|
+
PortableEnvironment,
|
|
22
|
+
)
|
|
17
23
|
from fred_sdk.contracts.execution import ExecutionGrant, ExecutionGrantAction
|
|
18
24
|
from fred_sdk.contracts.models import ReActAgentDefinition
|
|
19
25
|
from langchain_core.messages import AIMessage
|
|
@@ -884,6 +890,88 @@ def test_execute_route_propagates_checkpoint_and_observability_context(
|
|
|
884
890
|
}
|
|
885
891
|
|
|
886
892
|
|
|
893
|
+
def test_local_registry_invoker_reuses_runtime_execute_projection(monkeypatch) -> None:
|
|
894
|
+
"""
|
|
895
|
+
Ensure local agent invocation flows through the typed runtime request bridge.
|
|
896
|
+
|
|
897
|
+
Why this exists:
|
|
898
|
+
- the multi-agent memory work needs one request-projection path for HTTP and
|
|
899
|
+
in-process agent calls, or new continuity fields will be duplicated again
|
|
900
|
+
- this regression proves `LocalRegistryAgentInvoker` no longer hand-builds a
|
|
901
|
+
separate private request payload
|
|
902
|
+
|
|
903
|
+
How to use it:
|
|
904
|
+
- run in the default offline `fred-runtime` test suite
|
|
905
|
+
|
|
906
|
+
Example:
|
|
907
|
+
- `pytest tests/test_agent_app.py -q`
|
|
908
|
+
"""
|
|
909
|
+
|
|
910
|
+
seen: dict[str, object] = {}
|
|
911
|
+
|
|
912
|
+
async def _fake_iterate_runtime_event_payloads(
|
|
913
|
+
definition,
|
|
914
|
+
request,
|
|
915
|
+
access_token=None,
|
|
916
|
+
*,
|
|
917
|
+
team_id=None,
|
|
918
|
+
registry=None,
|
|
919
|
+
exchange_id=None,
|
|
920
|
+
):
|
|
921
|
+
_ = (definition, access_token, team_id, registry, exchange_id)
|
|
922
|
+
seen["checkpoint_id"] = request.checkpoint_id
|
|
923
|
+
seen["context"] = dict(request.context or {})
|
|
924
|
+
yield {"kind": "final", "sequence": 0, "content": "ok"}
|
|
925
|
+
|
|
926
|
+
monkeypatch.setattr(
|
|
927
|
+
agent_app_module,
|
|
928
|
+
"_iterate_runtime_event_payloads",
|
|
929
|
+
_fake_iterate_runtime_event_payloads,
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
definition = _EchoAgent()
|
|
933
|
+
invoker = agent_app_module.LocalRegistryAgentInvoker(
|
|
934
|
+
registry={definition.agent_id: definition},
|
|
935
|
+
access_token="token-1",
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
result = asyncio.run(
|
|
939
|
+
invoker.invoke(
|
|
940
|
+
AgentInvocationRequest(
|
|
941
|
+
agent_id=definition.agent_id,
|
|
942
|
+
message="hello",
|
|
943
|
+
context=PortableContext(
|
|
944
|
+
request_id="req-1",
|
|
945
|
+
correlation_id="corr-1",
|
|
946
|
+
actor="alice",
|
|
947
|
+
tenant="tenant-a",
|
|
948
|
+
environment=PortableEnvironment.DEV,
|
|
949
|
+
trace_id="trace-1",
|
|
950
|
+
session_id="session-1",
|
|
951
|
+
user_id="alice",
|
|
952
|
+
team_id="fredlab",
|
|
953
|
+
),
|
|
954
|
+
)
|
|
955
|
+
)
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
assert result.content == "ok"
|
|
959
|
+
assert result.is_error is False
|
|
960
|
+
assert seen["checkpoint_id"] is None
|
|
961
|
+
context = seen["context"]
|
|
962
|
+
assert isinstance(context, dict)
|
|
963
|
+
assert context["request_id"] == "req-1"
|
|
964
|
+
assert context["correlation_id"] == "corr-1"
|
|
965
|
+
assert context["actor"] == "alice"
|
|
966
|
+
assert context["tenant"] == "tenant-a"
|
|
967
|
+
assert context["environment"] == "dev"
|
|
968
|
+
assert context["trace_id"] == "trace-1"
|
|
969
|
+
assert context["session_id"] == "session-1"
|
|
970
|
+
assert context["user_id"] == "alice"
|
|
971
|
+
assert context["team_id"] == "fredlab"
|
|
972
|
+
assert context["execution_action"] == "execute"
|
|
973
|
+
|
|
974
|
+
|
|
887
975
|
def test_resume_rejects_non_pending_checkpoint(monkeypatch, tmp_path) -> None:
|
|
888
976
|
"""
|
|
889
977
|
Ensure resume requests fail fast when the checkpoint is not waiting for input.
|
|
@@ -1071,3 +1159,65 @@ def test_no_security_resolves_personal_team_in_portable_context(
|
|
|
1071
1159
|
assert any("team:personal" in e.get("content", "") for e in tool_results), (
|
|
1072
1160
|
f"Expected team:personal in tool_result events, got: {tool_results}"
|
|
1073
1161
|
)
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
def test_apply_runtime_tuning_applies_system_prompt_from_values() -> None:
|
|
1165
|
+
"""
|
|
1166
|
+
Ensure _apply_runtime_tuning writes prompts.system into system_prompt_template.
|
|
1167
|
+
|
|
1168
|
+
Why this exists:
|
|
1169
|
+
- control-plane stores user-set field values in AgentTuning.values; the
|
|
1170
|
+
runtime must apply them at execution time, not silently drop them
|
|
1171
|
+
|
|
1172
|
+
How to use it:
|
|
1173
|
+
- run in the default offline fred-runtime test suite
|
|
1174
|
+
|
|
1175
|
+
Example:
|
|
1176
|
+
- `pytest tests/test_agent_app.py::test_apply_runtime_tuning_applies_system_prompt_from_values -q`
|
|
1177
|
+
"""
|
|
1178
|
+
from fred_sdk.contracts.models import AgentTuning
|
|
1179
|
+
from fred_runtime.app.agent_app import _apply_runtime_tuning
|
|
1180
|
+
|
|
1181
|
+
definition = _EchoAgent()
|
|
1182
|
+
assert (
|
|
1183
|
+
definition.system_prompt_template
|
|
1184
|
+
== "Use the demo_echo tool, then answer briefly."
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
tuning = AgentTuning(
|
|
1188
|
+
role=definition.role,
|
|
1189
|
+
description=definition.description,
|
|
1190
|
+
values={"prompts.system": "Custom override prompt."},
|
|
1191
|
+
)
|
|
1192
|
+
result = cast(_EchoAgent, _apply_runtime_tuning(definition, tuning))
|
|
1193
|
+
assert result.system_prompt_template == "Custom override prompt."
|
|
1194
|
+
assert result.policy().system_prompt_template == "Custom override prompt."
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
def test_apply_runtime_tuning_ignores_blank_system_prompt() -> None:
|
|
1198
|
+
"""
|
|
1199
|
+
Ensure _apply_runtime_tuning does not override when prompts.system is blank.
|
|
1200
|
+
|
|
1201
|
+
Why this exists:
|
|
1202
|
+
- an empty or whitespace-only value means "use the agent default"; the
|
|
1203
|
+
control-plane UI stores an empty string when the field is cleared
|
|
1204
|
+
|
|
1205
|
+
How to use it:
|
|
1206
|
+
- run in the default offline fred-runtime test suite
|
|
1207
|
+
"""
|
|
1208
|
+
from fred_sdk.contracts.models import AgentTuning
|
|
1209
|
+
from fred_runtime.app.agent_app import _apply_runtime_tuning
|
|
1210
|
+
|
|
1211
|
+
definition = _EchoAgent()
|
|
1212
|
+
original = definition.system_prompt_template
|
|
1213
|
+
|
|
1214
|
+
for blank in ("", " "):
|
|
1215
|
+
tuning = AgentTuning(
|
|
1216
|
+
role=definition.role,
|
|
1217
|
+
description=definition.description,
|
|
1218
|
+
values={"prompts.system": blank},
|
|
1219
|
+
)
|
|
1220
|
+
result = cast(_EchoAgent, _apply_runtime_tuning(definition, tuning))
|
|
1221
|
+
assert result.system_prompt_template == original, (
|
|
1222
|
+
f"blank {blank!r} should not override"
|
|
1223
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/request_context_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{fred_runtime-2.0.0 → fred_runtime-2.0.1}/fred_runtime/runtime_support/user_token_refresher.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|