friday-framework-runtime 0.1.0a0__py3-none-any.whl
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.
- friday_framework_runtime-0.1.0a0.dist-info/METADATA +45 -0
- friday_framework_runtime-0.1.0a0.dist-info/RECORD +8 -0
- friday_framework_runtime-0.1.0a0.dist-info/WHEEL +4 -0
- friday_runtime/__init__.py +15 -0
- friday_runtime/config.py +1681 -0
- friday_runtime/di.py +342 -0
- friday_runtime/kernel.py +673 -0
- friday_runtime/tool_loading.py +177 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Runtime tool loading helpers for declarative provider-aware registry assembly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import inspect
|
|
7
|
+
import pkgutil
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from friday_agent import DefaultToolRegistry
|
|
12
|
+
from friday_core.interfaces import ToolDefinition
|
|
13
|
+
|
|
14
|
+
from friday_runtime.config import ToolProviderConfig, ToolsConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToolProviderLoadError(RuntimeError):
|
|
18
|
+
"""Raised when a configured runtime tool provider cannot be loaded."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register_runtime_tools(
|
|
22
|
+
registry: DefaultToolRegistry,
|
|
23
|
+
config: ToolsConfig,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Register tools from configured providers into the runtime registry."""
|
|
26
|
+
for tool in _select_builtin_tools(config.providers.builtin):
|
|
27
|
+
registry.register(tool)
|
|
28
|
+
|
|
29
|
+
for tool in _select_friday_tools(config.providers.friday_tools):
|
|
30
|
+
registry.register(tool)
|
|
31
|
+
|
|
32
|
+
if config.providers.mcp.enabled:
|
|
33
|
+
raise ToolProviderLoadError(
|
|
34
|
+
"tools.providers.mcp is configured, but MCP runtime loading "
|
|
35
|
+
"is not implemented in Phase 5-B."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _select_builtin_tools(provider: ToolProviderConfig) -> list[ToolDefinition]:
|
|
40
|
+
if not provider.enabled:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
catalog = _builtin_tool_catalog()
|
|
44
|
+
return _filter_catalog(catalog, provider)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _select_friday_tools(provider: ToolProviderConfig) -> list[ToolDefinition]:
|
|
48
|
+
if not provider.enabled:
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
catalog = _friday_tools_catalog()
|
|
52
|
+
return _filter_catalog(catalog, provider)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@lru_cache(maxsize=1)
|
|
56
|
+
def _builtin_tool_catalog() -> tuple[ToolDefinition, ...]:
|
|
57
|
+
temp_registry = DefaultToolRegistry()
|
|
58
|
+
temp_registry.load_decorated_tools()
|
|
59
|
+
return tuple(sorted(temp_registry.list_tools(), key=lambda tool: tool.name))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@lru_cache(maxsize=1)
|
|
63
|
+
def _friday_tools_catalog() -> tuple[ToolDefinition, ...]:
|
|
64
|
+
try:
|
|
65
|
+
friday_tools = importlib.import_module("friday_tools")
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
raise ToolProviderLoadError(
|
|
68
|
+
f"Unable to import friday_tools package for runtime discovery: {exc}"
|
|
69
|
+
) from exc
|
|
70
|
+
|
|
71
|
+
modules = [friday_tools]
|
|
72
|
+
if hasattr(friday_tools, "__path__"):
|
|
73
|
+
module_names = sorted(
|
|
74
|
+
module_name
|
|
75
|
+
for _, module_name, _ in pkgutil.walk_packages(
|
|
76
|
+
friday_tools.__path__,
|
|
77
|
+
f"{friday_tools.__name__}.",
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
for module_name in module_names:
|
|
81
|
+
try:
|
|
82
|
+
modules.append(importlib.import_module(module_name))
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
raise ToolProviderLoadError(
|
|
85
|
+
f"Unable to import friday_tools module '{module_name}': {exc}"
|
|
86
|
+
) from exc
|
|
87
|
+
|
|
88
|
+
discovered: dict[str, ToolDefinition] = {}
|
|
89
|
+
for module in modules:
|
|
90
|
+
for value in vars(module).values():
|
|
91
|
+
if inspect.isclass(value) or not isinstance(value, ToolDefinition):
|
|
92
|
+
continue
|
|
93
|
+
if not isinstance(getattr(value, "name", None), str):
|
|
94
|
+
continue
|
|
95
|
+
if not isinstance(getattr(value, "category", None), str):
|
|
96
|
+
continue
|
|
97
|
+
discovered.setdefault(value.name, value)
|
|
98
|
+
|
|
99
|
+
return tuple(sorted(discovered.values(), key=lambda tool: tool.name))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _filter_catalog(
|
|
103
|
+
catalog: tuple[ToolDefinition, ...],
|
|
104
|
+
provider: ToolProviderConfig,
|
|
105
|
+
) -> list[ToolDefinition]:
|
|
106
|
+
by_name = {tool.name: tool for tool in catalog}
|
|
107
|
+
|
|
108
|
+
if provider.mode == "selected":
|
|
109
|
+
selected: dict[str, ToolDefinition] = {}
|
|
110
|
+
if provider.include_categories:
|
|
111
|
+
selected.update(
|
|
112
|
+
{
|
|
113
|
+
tool.name: tool
|
|
114
|
+
for tool in catalog
|
|
115
|
+
if tool.category in provider.include_categories
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
for tool_name in provider.include_tools:
|
|
119
|
+
tool = by_name.get(tool_name)
|
|
120
|
+
if tool is not None:
|
|
121
|
+
selected[tool_name] = tool
|
|
122
|
+
else:
|
|
123
|
+
selected = {tool.name: tool for tool in catalog}
|
|
124
|
+
if provider.include_categories:
|
|
125
|
+
selected = {
|
|
126
|
+
name: tool
|
|
127
|
+
for name, tool in selected.items()
|
|
128
|
+
if tool.category in provider.include_categories
|
|
129
|
+
}
|
|
130
|
+
for tool_name in provider.include_tools:
|
|
131
|
+
tool = by_name.get(tool_name)
|
|
132
|
+
if tool is not None:
|
|
133
|
+
selected[tool_name] = tool
|
|
134
|
+
|
|
135
|
+
if provider.exclude_categories:
|
|
136
|
+
selected = {
|
|
137
|
+
name: tool
|
|
138
|
+
for name, tool in selected.items()
|
|
139
|
+
if tool.category not in provider.exclude_categories
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for tool_name in provider.exclude_tools:
|
|
143
|
+
selected.pop(tool_name, None)
|
|
144
|
+
|
|
145
|
+
return [selected[name] for name in sorted(selected)]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_agent_tool_bindings(
|
|
149
|
+
*,
|
|
150
|
+
agent_name: str,
|
|
151
|
+
declared_tools: list[str],
|
|
152
|
+
available_tool_names: set[str],
|
|
153
|
+
) -> None:
|
|
154
|
+
"""Fail hard when an agent references tools unavailable in the runtime."""
|
|
155
|
+
missing = sorted({tool_name for tool_name in declared_tools if tool_name not in available_tool_names})
|
|
156
|
+
if missing:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"Agent '{agent_name}' references unavailable tool(s): {missing}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def collect_profile_tool_references(profile: Any) -> list[str]:
|
|
163
|
+
"""Collect all tool references declared across an agent profile."""
|
|
164
|
+
ordered: list[str] = []
|
|
165
|
+
seen: set[str] = set()
|
|
166
|
+
for field_name in ("tools", "injected_tools", "available_tools", "required_tools"):
|
|
167
|
+
values = getattr(profile, field_name, [])
|
|
168
|
+
if not isinstance(values, list):
|
|
169
|
+
continue
|
|
170
|
+
for value in values:
|
|
171
|
+
if not isinstance(value, str):
|
|
172
|
+
continue
|
|
173
|
+
stripped = value.strip()
|
|
174
|
+
if stripped and stripped not in seen:
|
|
175
|
+
ordered.append(stripped)
|
|
176
|
+
seen.add(stripped)
|
|
177
|
+
return ordered
|