yera 0.1.0__py3-none-any.whl → 0.2.0__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.
- infra_mvp/base_client.py +29 -0
- infra_mvp/base_server.py +68 -0
- infra_mvp/monitoring/__init__.py +15 -0
- infra_mvp/monitoring/metrics.py +185 -0
- infra_mvp/stream/README.md +56 -0
- infra_mvp/stream/__init__.py +14 -0
- infra_mvp/stream/__main__.py +101 -0
- infra_mvp/stream/agents/demos/financial/chart_additions_plan.md +170 -0
- infra_mvp/stream/agents/demos/financial/portfolio_assistant_stream.json +1571 -0
- infra_mvp/stream/agents/reference/blocks/action.json +170 -0
- infra_mvp/stream/agents/reference/blocks/button.json +66 -0
- infra_mvp/stream/agents/reference/blocks/date.json +65 -0
- infra_mvp/stream/agents/reference/blocks/input_prompt.json +94 -0
- infra_mvp/stream/agents/reference/blocks/layout.json +288 -0
- infra_mvp/stream/agents/reference/blocks/markdown.json +344 -0
- infra_mvp/stream/agents/reference/blocks/slider.json +67 -0
- infra_mvp/stream/agents/reference/blocks/spinner.json +110 -0
- infra_mvp/stream/agents/reference/blocks/table.json +56 -0
- infra_mvp/stream/agents/reference/chat_dynamics/branching_test_stream.json +145 -0
- infra_mvp/stream/app.py +49 -0
- infra_mvp/stream/container.py +112 -0
- infra_mvp/stream/schemas/__init__.py +16 -0
- infra_mvp/stream/schemas/agent.py +24 -0
- infra_mvp/stream/schemas/interaction.py +28 -0
- infra_mvp/stream/schemas/session.py +30 -0
- infra_mvp/stream/server.py +321 -0
- infra_mvp/stream/services/__init__.py +12 -0
- infra_mvp/stream/services/agent_service.py +40 -0
- infra_mvp/stream/services/event_converter.py +83 -0
- infra_mvp/stream/services/session_service.py +247 -0
- yera/__init__.py +50 -1
- yera/agents/__init__.py +2 -0
- yera/agents/context.py +41 -0
- yera/agents/dataclasses.py +69 -0
- yera/agents/decorator.py +207 -0
- yera/agents/discovery.py +124 -0
- yera/agents/typing/__init__.py +0 -0
- yera/agents/typing/coerce.py +408 -0
- yera/agents/typing/utils.py +19 -0
- yera/agents/typing/validate.py +206 -0
- yera/cli.py +377 -0
- yera/config/__init__.py +1 -0
- yera/config/config_utils.py +164 -0
- yera/config/function_config.py +55 -0
- yera/config/logging.py +18 -0
- yera/config/tool_config.py +8 -0
- yera/config2/__init__.py +8 -0
- yera/config2/dataclasses.py +534 -0
- yera/config2/keyring.py +270 -0
- yera/config2/paths.py +28 -0
- yera/config2/read.py +113 -0
- yera/config2/setup.py +109 -0
- yera/config2/setup_handlers/__init__.py +1 -0
- yera/config2/setup_handlers/anthropic.py +126 -0
- yera/config2/setup_handlers/azure.py +236 -0
- yera/config2/setup_handlers/base.py +125 -0
- yera/config2/setup_handlers/llama_cpp.py +205 -0
- yera/config2/setup_handlers/ollama.py +157 -0
- yera/config2/setup_handlers/openai.py +137 -0
- yera/config2/write.py +87 -0
- yera/dsl/__init__.py +0 -0
- yera/dsl/functions.py +94 -0
- yera/dsl/struct.py +20 -0
- yera/dsl/workspace.py +79 -0
- yera/events/__init__.py +57 -0
- yera/events/blocks/__init__.py +68 -0
- yera/events/blocks/action.py +57 -0
- yera/events/blocks/bar_chart.py +92 -0
- yera/events/blocks/base/__init__.py +20 -0
- yera/events/blocks/base/base.py +166 -0
- yera/events/blocks/base/chart.py +288 -0
- yera/events/blocks/base/layout.py +111 -0
- yera/events/blocks/buttons.py +37 -0
- yera/events/blocks/columns.py +26 -0
- yera/events/blocks/container.py +24 -0
- yera/events/blocks/date_picker.py +50 -0
- yera/events/blocks/exit.py +39 -0
- yera/events/blocks/form.py +24 -0
- yera/events/blocks/input_echo.py +22 -0
- yera/events/blocks/input_request.py +31 -0
- yera/events/blocks/line_chart.py +97 -0
- yera/events/blocks/markdown.py +67 -0
- yera/events/blocks/slider.py +54 -0
- yera/events/blocks/spinner.py +55 -0
- yera/events/blocks/system_prompt.py +22 -0
- yera/events/blocks/table.py +291 -0
- yera/events/models/__init__.py +39 -0
- yera/events/models/block_data.py +112 -0
- yera/events/models/in_event.py +7 -0
- yera/events/models/out_event.py +75 -0
- yera/events/runtime.py +187 -0
- yera/events/stream.py +91 -0
- yera/models/__init__.py +0 -0
- yera/models/data_classes.py +20 -0
- yera/models/llm_atlas_proxy.py +44 -0
- yera/models/llm_context.py +99 -0
- yera/models/llm_interfaces/__init__.py +0 -0
- yera/models/llm_interfaces/anthropic.py +153 -0
- yera/models/llm_interfaces/aws_bedrock.py +14 -0
- yera/models/llm_interfaces/azure_openai.py +143 -0
- yera/models/llm_interfaces/base.py +26 -0
- yera/models/llm_interfaces/interface_registry.py +74 -0
- yera/models/llm_interfaces/llama_cpp.py +136 -0
- yera/models/llm_interfaces/mock.py +29 -0
- yera/models/llm_interfaces/ollama_interface.py +118 -0
- yera/models/llm_interfaces/open_ai.py +150 -0
- yera/models/llm_workspace.py +19 -0
- yera/models/model_atlas.py +139 -0
- yera/models/model_definition.py +38 -0
- yera/models/model_factory.py +33 -0
- yera/opaque/__init__.py +9 -0
- yera/opaque/base.py +20 -0
- yera/opaque/decorator.py +8 -0
- yera/opaque/markdown.py +57 -0
- yera/opaque/opaque_function.py +25 -0
- yera/tools/__init__.py +29 -0
- yera/tools/atlas_tool.py +20 -0
- yera/tools/base.py +24 -0
- yera/tools/decorated_tool.py +18 -0
- yera/tools/decorator.py +35 -0
- yera/tools/tool_atlas.py +51 -0
- yera/tools/tool_utils.py +361 -0
- yera/ui/dist/404.html +1 -0
- yera/ui/dist/__next.__PAGE__.txt +10 -0
- yera/ui/dist/__next._full.txt +23 -0
- yera/ui/dist/__next._head.txt +6 -0
- yera/ui/dist/__next._index.txt +5 -0
- yera/ui/dist/__next._tree.txt +7 -0
- yera/ui/dist/_next/static/chunks/4c4688e1ff21ad98.js +1 -0
- yera/ui/dist/_next/static/chunks/652cd53c27924d50.js +4 -0
- yera/ui/dist/_next/static/chunks/786d2107b51e8499.css +1 -0
- yera/ui/dist/_next/static/chunks/7de9141b1af425c3.js +1 -0
- yera/ui/dist/_next/static/chunks/87ef65064d3524c1.js +2 -0
- yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- yera/ui/dist/_next/static/chunks/c4c79d5d0b280aeb.js +1 -0
- yera/ui/dist/_next/static/chunks/dc2d2a247505d66f.css +5 -0
- yera/ui/dist/_next/static/chunks/f773f714b55ec620.js +37 -0
- yera/ui/dist/_next/static/chunks/turbopack-98b3031e1b1dbc33.js +4 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_buildManifest.js +11 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_clientMiddlewareManifest.json +1 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_ssgManifest.js +1 -0
- yera/ui/dist/_next/static/media/14e23f9b59180572-s.9c448f3c.woff2 +0 -0
- yera/ui/dist/_next/static/media/2a65768255d6b625-s.p.d19752fb.woff2 +0 -0
- yera/ui/dist/_next/static/media/2b2eb4836d2dad95-s.f36de3af.woff2 +0 -0
- yera/ui/dist/_next/static/media/31183d9fd602dc89-s.c4ff9b73.woff2 +0 -0
- yera/ui/dist/_next/static/media/3fcb63a1ac6a562e-s.2f77a576.woff2 +0 -0
- yera/ui/dist/_next/static/media/45ec8de98929b0f6-s.81056204.woff2 +0 -0
- yera/ui/dist/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- yera/ui/dist/_next/static/media/65c558afe41e89d6-s.e2c8389a.woff2 +0 -0
- yera/ui/dist/_next/static/media/67add6cc0f54b8cf-s.8ce53448.woff2 +0 -0
- yera/ui/dist/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- yera/ui/dist/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- yera/ui/dist/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- yera/ui/dist/_next/static/media/a8ff2d5d0ccb0d12-s.fc5b72a7.woff2 +0 -0
- yera/ui/dist/_next/static/media/aae5f0be330e13db-s.p.853e26d6.woff2 +0 -0
- yera/ui/dist/_next/static/media/b11a6ccf4a3edec7-s.2113d282.woff2 +0 -0
- yera/ui/dist/_next/static/media/b49b0d9b851e4899-s.4f3fa681.woff2 +0 -0
- yera/ui/dist/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- yera/ui/dist/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- yera/ui/dist/_next/static/media/favicon.0b3bf435.ico +0 -0
- yera/ui/dist/_not-found/__next._full.txt +14 -0
- yera/ui/dist/_not-found/__next._head.txt +6 -0
- yera/ui/dist/_not-found/__next._index.txt +5 -0
- yera/ui/dist/_not-found/__next._not-found.__PAGE__.txt +5 -0
- yera/ui/dist/_not-found/__next._not-found.txt +4 -0
- yera/ui/dist/_not-found/__next._tree.txt +2 -0
- yera/ui/dist/_not-found.html +1 -0
- yera/ui/dist/_not-found.txt +14 -0
- yera/ui/dist/agent-icon.svg +3 -0
- yera/ui/dist/favicon.ico +0 -0
- yera/ui/dist/file.svg +1 -0
- yera/ui/dist/globe.svg +1 -0
- yera/ui/dist/index.html +1 -0
- yera/ui/dist/index.txt +23 -0
- yera/ui/dist/logo/full_logo.png +0 -0
- yera/ui/dist/logo/rune_logo.png +0 -0
- yera/ui/dist/logo/rune_logo_borderless.png +0 -0
- yera/ui/dist/logo/text_logo.png +0 -0
- yera/ui/dist/next.svg +1 -0
- yera/ui/dist/send.png +0 -0
- yera/ui/dist/send_single.png +0 -0
- yera/ui/dist/vercel.svg +1 -0
- yera/ui/dist/window.svg +1 -0
- yera/utils/__init__.py +1 -0
- yera/utils/path_utils.py +38 -0
- yera-0.2.0.dist-info/METADATA +65 -0
- yera-0.2.0.dist-info/RECORD +190 -0
- {yera-0.1.0.dist-info → yera-0.2.0.dist-info}/WHEEL +1 -1
- yera-0.2.0.dist-info/entry_points.txt +2 -0
- yera-0.1.0.dist-info/METADATA +0 -11
- yera-0.1.0.dist-info/RECORD +0 -4
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from yera.models.model_factory import LLMContextFactory
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _id_to_attr(id_: str) -> str:
|
|
5
|
+
return id_.replace("-", "_").replace(":", "_")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# noinspection PyUnresolvedReferences
|
|
9
|
+
class ModelAtlas:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def from_yera_config(yera_config) -> "ModelAtlas":
|
|
12
|
+
root = ModelAtlas()
|
|
13
|
+
|
|
14
|
+
for path, cfg in yera_config.models.llm.items():
|
|
15
|
+
parts = path.split(".")
|
|
16
|
+
current = root
|
|
17
|
+
for part in parts[:-1]:
|
|
18
|
+
if not hasattr(current, part):
|
|
19
|
+
setattr(current, part, ModelAtlas())
|
|
20
|
+
current = getattr(current, part)
|
|
21
|
+
|
|
22
|
+
attr_name = parts[-1].replace("-", "_").replace(":", "_")
|
|
23
|
+
factory = LLMContextFactory(
|
|
24
|
+
cfg, yera_config.credentials.providers[cfg.credentials]
|
|
25
|
+
)
|
|
26
|
+
setattr(current, attr_name, factory)
|
|
27
|
+
|
|
28
|
+
root.default = yera_config.defaults.models.llm
|
|
29
|
+
return root
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.default = None
|
|
33
|
+
|
|
34
|
+
def __dir__(self):
|
|
35
|
+
# Prioritise instance attributes (the dynamic model paths)
|
|
36
|
+
instance_attrs = list(self.__dict__.keys())
|
|
37
|
+
|
|
38
|
+
# Add essential class attributes but filter out private and dunder
|
|
39
|
+
class_attrs = [
|
|
40
|
+
attr
|
|
41
|
+
for attr in object.__dir__(self)
|
|
42
|
+
if not attr.startswith("_") and attr not in instance_attrs
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Instance attributes first for better tab completion
|
|
46
|
+
return instance_attrs + class_attrs
|
|
47
|
+
|
|
48
|
+
def _ipython_key_completions_(self):
|
|
49
|
+
return [k for k in self.__dict__ if not k.startswith("_")]
|
|
50
|
+
|
|
51
|
+
def __getitem__(self, key: str):
|
|
52
|
+
current = self
|
|
53
|
+
for part in key.split("."):
|
|
54
|
+
current = getattr(current, _id_to_attr(part), None)
|
|
55
|
+
if current is None:
|
|
56
|
+
raise KeyError(key)
|
|
57
|
+
return current
|
|
58
|
+
|
|
59
|
+
def list_models(self):
|
|
60
|
+
attrs = [getattr(self, n) for n in dir(self)]
|
|
61
|
+
attrs = [a for a in attrs if isinstance(a, ModelAtlas | LLMContextFactory)]
|
|
62
|
+
|
|
63
|
+
models = []
|
|
64
|
+
for attr in attrs:
|
|
65
|
+
if isinstance(attr, ModelAtlas):
|
|
66
|
+
models += attr.list_models()
|
|
67
|
+
if isinstance(attr, LLMContextFactory):
|
|
68
|
+
models.append(attr)
|
|
69
|
+
|
|
70
|
+
return models
|
|
71
|
+
|
|
72
|
+
def _repr_html_(self):
|
|
73
|
+
"""Rich HTML representation for Jupyter notebooks"""
|
|
74
|
+
|
|
75
|
+
def build_tree_html(atlas, prefix=""):
|
|
76
|
+
items = []
|
|
77
|
+
for key in sorted(atlas.__dict__.keys()):
|
|
78
|
+
if key.startswith("_"):
|
|
79
|
+
continue
|
|
80
|
+
value = getattr(atlas, key)
|
|
81
|
+
if isinstance(value, ModelAtlas):
|
|
82
|
+
items.append(
|
|
83
|
+
f"<li><b>{key}/</b><ul>{build_tree_html(value, prefix + ' ')}</ul></li>"
|
|
84
|
+
)
|
|
85
|
+
elif isinstance(value, LLMContextFactory):
|
|
86
|
+
# Show model info if available
|
|
87
|
+
model_info = f" <span style='color: #666; font-size: 0.9em;'>({value.config.provider if hasattr(value, 'config') else 'model'})</span>"
|
|
88
|
+
items.append(f"<li>{key}{model_info}</li>")
|
|
89
|
+
return "".join(items)
|
|
90
|
+
|
|
91
|
+
html = f"""
|
|
92
|
+
<div style="font-family: monospace; line-height: 1.6;">
|
|
93
|
+
<b>ModelAtlas</b>
|
|
94
|
+
<ul style="list-style-type: none; padding-left: 20px;">
|
|
95
|
+
{build_tree_html(self)}
|
|
96
|
+
</ul>
|
|
97
|
+
</div>
|
|
98
|
+
"""
|
|
99
|
+
return html
|
|
100
|
+
|
|
101
|
+
def _build_text_tree(self, prefix: str = "") -> str:
|
|
102
|
+
lines = []
|
|
103
|
+
|
|
104
|
+
names = sorted(k for k in self.__dict__ if not k.startswith("_"))
|
|
105
|
+
names = [
|
|
106
|
+
n
|
|
107
|
+
for n in names
|
|
108
|
+
if isinstance(getattr(self, n), ModelAtlas | LLMContextFactory)
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
for idx, name in enumerate(names):
|
|
112
|
+
child = getattr(self, name)
|
|
113
|
+
connector = "└── " if idx == len(names) - 1 else "├── "
|
|
114
|
+
child_prefix = prefix + (" " if idx == len(names) - 1 else "│ ")
|
|
115
|
+
|
|
116
|
+
if isinstance(child, ModelAtlas):
|
|
117
|
+
lines.append(f"{prefix}{connector}{name}/")
|
|
118
|
+
lines.append(child._build_text_tree(child_prefix))
|
|
119
|
+
elif isinstance(child, LLMContextFactory):
|
|
120
|
+
lines.append(f"{prefix}{connector}{name}")
|
|
121
|
+
|
|
122
|
+
if self.default:
|
|
123
|
+
lines.append("")
|
|
124
|
+
|
|
125
|
+
return "\n".join(lines)
|
|
126
|
+
|
|
127
|
+
def __str__(self) -> str:
|
|
128
|
+
return (
|
|
129
|
+
f"┌─ [LLMs]\n├─ Default: {_id_to_attr(self.default)}\n│\n"
|
|
130
|
+
+ self._build_text_tree()
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def set_default(self, path):
|
|
134
|
+
self.default = path
|
|
135
|
+
|
|
136
|
+
def get_default(self):
|
|
137
|
+
if self.default:
|
|
138
|
+
return self[self.default]()
|
|
139
|
+
return None
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ModelDefinition(BaseModel):
|
|
8
|
+
name: str
|
|
9
|
+
type: Literal["LLM", "TTS", "STS", "VLM"] = "LLM"
|
|
10
|
+
security_rank: int
|
|
11
|
+
interface_cls: str
|
|
12
|
+
args: dict[str, Any] = Field(default_factory=dict)
|
|
13
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
14
|
+
default_quant: str | None = None
|
|
15
|
+
quant_files: dict[str, str] = Field(default_factory=dict)
|
|
16
|
+
|
|
17
|
+
def build_kwargs(self, **kwargs) -> dict:
|
|
18
|
+
new_quant = kwargs.pop("quant", self.default_quant)
|
|
19
|
+
|
|
20
|
+
if new_quant and len(self.quant_files) == 0:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
f"Cannot set a quant for {self.name}. "
|
|
23
|
+
f"Its definition has no model files - is this an API-based model?"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if new_quant and new_quant not in self.quant_files:
|
|
27
|
+
available = ", ".join(self.quant_files.keys())
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"The quant '{new_quant}' was not found for model {self.name}. "
|
|
30
|
+
f"Available quants are {available}."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
kws = {**self.args, **kwargs}
|
|
34
|
+
|
|
35
|
+
if new_quant:
|
|
36
|
+
kws["model_path"] = Path(self.quant_files[new_quant])
|
|
37
|
+
|
|
38
|
+
return kws
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from yera.models.llm_context import LLMContext
|
|
4
|
+
from yera.models.llm_interfaces.interface_registry import get_interface
|
|
5
|
+
from yera.models.llm_workspace import LLMWorkspace
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from yera.config2.dataclasses import LLMConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LLMContextFactory:
|
|
12
|
+
def __init__(self, model_cfg: "LLMConfig", creds_map: dict[str, str]):
|
|
13
|
+
from yera.config2.dataclasses import LLMConfig
|
|
14
|
+
|
|
15
|
+
if not isinstance(model_cfg, LLMConfig):
|
|
16
|
+
raise TypeError(
|
|
17
|
+
f"model_cfg must be type LLMConfig. Was {type(model_cfg).__name__}."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
self.model_cfg = model_cfg
|
|
21
|
+
self.creds_map = creds_map
|
|
22
|
+
|
|
23
|
+
self.llm_cls = get_interface(self.model_cfg.interface)
|
|
24
|
+
if self.llm_cls is None:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"Could not find llm interface class '{self.model_cfg.interface}'"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def __call__(self, **kwargs) -> LLMContext:
|
|
30
|
+
# todo: metaclass magic for runtime signature generation from config
|
|
31
|
+
interface = self.llm_cls(self.model_cfg, kwargs, creds_map=self.creds_map)
|
|
32
|
+
# todo: workspace inheritance goes here
|
|
33
|
+
return LLMContext(interface=interface, workspace=LLMWorkspace())
|
yera/opaque/__init__.py
ADDED
yera/opaque/base.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OpaqueCallable(ABC):
|
|
6
|
+
"""Base class for callables that should appear as single opaque nodes in the graph."""
|
|
7
|
+
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
10
|
+
"""Execute the opaque callable with the given arguments."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def name(self) -> str:
|
|
16
|
+
"""Get the name of the opaque callable."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def __repr__(self) -> str:
|
|
20
|
+
return f"{self.__class__.__name__}({self.name})"
|
yera/opaque/decorator.py
ADDED
yera/opaque/markdown.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from yera.opaque.base import OpaqueCallable
|
|
6
|
+
from yera.yil.tracing.markdown_tracing import MarkdownTracingContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Markdown(OpaqueCallable):
|
|
10
|
+
"""Trace-only opaque callable for markdown blocks.
|
|
11
|
+
|
|
12
|
+
This is a YIL-first API surface for markdown. It is intended to be used
|
|
13
|
+
*only* inside a tracing context. Calling it directly in normal Python
|
|
14
|
+
execution is an error.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
"""Get the name of the opaque callable."""
|
|
20
|
+
return "markdown"
|
|
21
|
+
|
|
22
|
+
def __call__(self, content: Any | None = None) -> MarkdownTracingContext:
|
|
23
|
+
"""Call markdown during tracing to create MarkdownInit operations.
|
|
24
|
+
|
|
25
|
+
When called inside a tracing context:
|
|
26
|
+
- Delegates to tracing module to create MarkdownInit operation
|
|
27
|
+
- Returns a MarkdownTracingContext representing the markdown block
|
|
28
|
+
|
|
29
|
+
When called outside a tracing context:
|
|
30
|
+
- Raises RuntimeError (trace-only contract)
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
content: Content for the markdown block (can be Tracer, literal, or None).
|
|
34
|
+
If None, creates a markdown block with no initial content.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
MarkdownTracingContext representing the markdown block (in tracing context)
|
|
38
|
+
or raises RuntimeError (outside tracing context)
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
from yera.yil.tracing.markdown_tracing import trace_markdown
|
|
42
|
+
|
|
43
|
+
return trace_markdown(content)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Trace-only markdown implementation for OpGraph.
|
|
47
|
+
#
|
|
48
|
+
# This is used during tracing to create MarkdownInit/MarkdownAppend operations.
|
|
49
|
+
# In the future, this will be the only markdown implementation (chat/blocks/markdown.py
|
|
50
|
+
# will be removed), and execution will happen via OpGraph executor.
|
|
51
|
+
#
|
|
52
|
+
# For now, this exists alongside yera.chat.markdown which provides direct execution
|
|
53
|
+
# for MVP demos. This implementation ONLY works in a tracing context.
|
|
54
|
+
markdown = Markdown()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = ["Markdown", "markdown"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from yera.config.config_utils import extract_function_config
|
|
8
|
+
from yera.opaque.base import OpaqueCallable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OpaqueFunction(OpaqueCallable):
|
|
12
|
+
"""Wrapper for functions marked with @yk.opaque decorator."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, func: Callable):
|
|
15
|
+
self._func = func
|
|
16
|
+
self._function_config = extract_function_config(func)
|
|
17
|
+
functools.update_wrapper(self, func)
|
|
18
|
+
|
|
19
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
20
|
+
return self._func(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def name(self) -> str:
|
|
24
|
+
"""Get the name of the opaque function."""
|
|
25
|
+
return self._function_config.name
|
yera/tools/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .atlas_tool import AtlasTool as AtlasTool
|
|
2
|
+
from .base import Tool as Tool
|
|
3
|
+
from .decorated_tool import DecoratedTool as DecoratedTool
|
|
4
|
+
from .decorator import tool as tool
|
|
5
|
+
from .tool_atlas import get_tool_atlas as get_tool_atlas
|
|
6
|
+
from .tool_utils import (
|
|
7
|
+
extract_tool_config as extract_tool_config,
|
|
8
|
+
)
|
|
9
|
+
from .tool_utils import (
|
|
10
|
+
load_tools_config as load_tools_config,
|
|
11
|
+
)
|
|
12
|
+
from .tool_utils import (
|
|
13
|
+
reconstruct_tool_function as reconstruct_tool_function,
|
|
14
|
+
)
|
|
15
|
+
from .tool_utils import (
|
|
16
|
+
save_tool as save_tool,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"AtlasTool",
|
|
21
|
+
"DecoratedTool",
|
|
22
|
+
"Tool",
|
|
23
|
+
"extract_tool_config",
|
|
24
|
+
"get_tool_atlas",
|
|
25
|
+
"load_tools_config",
|
|
26
|
+
"reconstruct_tool_function",
|
|
27
|
+
"save_tool",
|
|
28
|
+
"tool",
|
|
29
|
+
]
|
yera/tools/atlas_tool.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from yera.config.tool_config import ToolConfig
|
|
7
|
+
from yera.tools.base import Tool
|
|
8
|
+
from yera.tools.tool_utils import reconstruct_tool_function
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AtlasTool(Tool):
|
|
12
|
+
def __init__(self, tool_config: ToolConfig):
|
|
13
|
+
super().__init__(tool_config)
|
|
14
|
+
self._callable: Callable | None = None
|
|
15
|
+
|
|
16
|
+
def __call__(self, *args, **kwargs) -> Any:
|
|
17
|
+
# Tool is reconstructed lazily from tool_config the first time it is called, and cached for subsequent calls
|
|
18
|
+
if self._callable is None:
|
|
19
|
+
self._callable = reconstruct_tool_function(self.tool_config)
|
|
20
|
+
return self._callable(*args, **kwargs)
|
yera/tools/base.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from yera.config.tool_config import ToolConfig
|
|
7
|
+
from yera.opaque import OpaqueCallable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tool(OpaqueCallable, ABC):
|
|
11
|
+
"""Abstract base class for all Yera tools. Tools are a specialisation of opaque callables."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, tool_config: ToolConfig):
|
|
14
|
+
self.tool_config = tool_config
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
18
|
+
"""Execute the tool with the given arguments."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def name(self) -> str:
|
|
23
|
+
"""Get the name of the tool."""
|
|
24
|
+
return self.tool_config.function_config.name
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from yera.config.tool_config import ToolConfig
|
|
8
|
+
from yera.tools.base import Tool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DecoratedTool(Tool):
|
|
12
|
+
def __init__(self, func: Callable, tool_config: ToolConfig):
|
|
13
|
+
super().__init__(tool_config)
|
|
14
|
+
self._func = func
|
|
15
|
+
functools.update_wrapper(self, func)
|
|
16
|
+
|
|
17
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
18
|
+
return self._func(*args, **kwargs)
|
yera/tools/decorator.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from yera.config.tool_config import ToolConfig
|
|
8
|
+
from yera.tools import tool_utils
|
|
9
|
+
from yera.tools.decorated_tool import DecoratedTool
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def tool(security_rank: int, save: bool = False, cfg_path: Path | str | None = None):
|
|
15
|
+
if not isinstance(security_rank, int) or security_rank < 0:
|
|
16
|
+
raise ValueError("security_rank must be a non-negative integer")
|
|
17
|
+
|
|
18
|
+
def decorator(func: Callable) -> DecoratedTool:
|
|
19
|
+
tool_config: ToolConfig = tool_utils.extract_tool_config(
|
|
20
|
+
func, security_rank=security_rank
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
wrapper = DecoratedTool(func, tool_config)
|
|
24
|
+
|
|
25
|
+
if save:
|
|
26
|
+
try:
|
|
27
|
+
tool_utils.save_tool(
|
|
28
|
+
func, cfg_path=cfg_path, security_rank=security_rank
|
|
29
|
+
)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.warning("Failed to save tool '%s': %s", func.__name__, e)
|
|
32
|
+
|
|
33
|
+
return wrapper
|
|
34
|
+
|
|
35
|
+
return decorator
|
yera/tools/tool_atlas.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from yera.config.tool_config import ToolConfig
|
|
6
|
+
from yera.tools.atlas_tool import AtlasTool
|
|
7
|
+
from yera.tools.tool_utils import load_tools_config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolAtlas:
|
|
11
|
+
@staticmethod
|
|
12
|
+
def from_config_map(config_map: dict[str, ToolConfig]) -> ToolAtlas:
|
|
13
|
+
root = ToolAtlas()
|
|
14
|
+
|
|
15
|
+
for path, cfg in config_map.items():
|
|
16
|
+
parts = path.split(".")
|
|
17
|
+
current = root
|
|
18
|
+
for part in parts[:-1]:
|
|
19
|
+
if not hasattr(current, part):
|
|
20
|
+
setattr(current, part, ToolAtlas())
|
|
21
|
+
current = getattr(current, part)
|
|
22
|
+
|
|
23
|
+
setattr(current, parts[-1].replace("-", "_"), AtlasTool(cfg))
|
|
24
|
+
|
|
25
|
+
return root
|
|
26
|
+
|
|
27
|
+
def __getitem__(self, key: str):
|
|
28
|
+
current = self
|
|
29
|
+
for part in key.split("."):
|
|
30
|
+
current = getattr(current, part, None)
|
|
31
|
+
if current is None:
|
|
32
|
+
raise KeyError(key)
|
|
33
|
+
return current
|
|
34
|
+
|
|
35
|
+
def list_tools(self):
|
|
36
|
+
attrs = [getattr(self, n) for n in dir(self)]
|
|
37
|
+
attrs = [a for a in attrs if isinstance(a, ToolAtlas | AtlasTool)]
|
|
38
|
+
|
|
39
|
+
tools = []
|
|
40
|
+
for attr in attrs:
|
|
41
|
+
if isinstance(attr, ToolAtlas):
|
|
42
|
+
tools += attr.list_tools()
|
|
43
|
+
if isinstance(attr, AtlasTool):
|
|
44
|
+
tools.append(attr)
|
|
45
|
+
|
|
46
|
+
return tools
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_tool_atlas(cfg_path: Path | str | None = None) -> ToolAtlas:
|
|
50
|
+
raw_cfg_map = load_tools_config(cfg_path)
|
|
51
|
+
return ToolAtlas.from_config_map(raw_cfg_map)
|