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.
Files changed (192) hide show
  1. infra_mvp/base_client.py +29 -0
  2. infra_mvp/base_server.py +68 -0
  3. infra_mvp/monitoring/__init__.py +15 -0
  4. infra_mvp/monitoring/metrics.py +185 -0
  5. infra_mvp/stream/README.md +56 -0
  6. infra_mvp/stream/__init__.py +14 -0
  7. infra_mvp/stream/__main__.py +101 -0
  8. infra_mvp/stream/agents/demos/financial/chart_additions_plan.md +170 -0
  9. infra_mvp/stream/agents/demos/financial/portfolio_assistant_stream.json +1571 -0
  10. infra_mvp/stream/agents/reference/blocks/action.json +170 -0
  11. infra_mvp/stream/agents/reference/blocks/button.json +66 -0
  12. infra_mvp/stream/agents/reference/blocks/date.json +65 -0
  13. infra_mvp/stream/agents/reference/blocks/input_prompt.json +94 -0
  14. infra_mvp/stream/agents/reference/blocks/layout.json +288 -0
  15. infra_mvp/stream/agents/reference/blocks/markdown.json +344 -0
  16. infra_mvp/stream/agents/reference/blocks/slider.json +67 -0
  17. infra_mvp/stream/agents/reference/blocks/spinner.json +110 -0
  18. infra_mvp/stream/agents/reference/blocks/table.json +56 -0
  19. infra_mvp/stream/agents/reference/chat_dynamics/branching_test_stream.json +145 -0
  20. infra_mvp/stream/app.py +49 -0
  21. infra_mvp/stream/container.py +112 -0
  22. infra_mvp/stream/schemas/__init__.py +16 -0
  23. infra_mvp/stream/schemas/agent.py +24 -0
  24. infra_mvp/stream/schemas/interaction.py +28 -0
  25. infra_mvp/stream/schemas/session.py +30 -0
  26. infra_mvp/stream/server.py +321 -0
  27. infra_mvp/stream/services/__init__.py +12 -0
  28. infra_mvp/stream/services/agent_service.py +40 -0
  29. infra_mvp/stream/services/event_converter.py +83 -0
  30. infra_mvp/stream/services/session_service.py +247 -0
  31. yera/__init__.py +50 -1
  32. yera/agents/__init__.py +2 -0
  33. yera/agents/context.py +41 -0
  34. yera/agents/dataclasses.py +69 -0
  35. yera/agents/decorator.py +207 -0
  36. yera/agents/discovery.py +124 -0
  37. yera/agents/typing/__init__.py +0 -0
  38. yera/agents/typing/coerce.py +408 -0
  39. yera/agents/typing/utils.py +19 -0
  40. yera/agents/typing/validate.py +206 -0
  41. yera/cli.py +377 -0
  42. yera/config/__init__.py +1 -0
  43. yera/config/config_utils.py +164 -0
  44. yera/config/function_config.py +55 -0
  45. yera/config/logging.py +18 -0
  46. yera/config/tool_config.py +8 -0
  47. yera/config2/__init__.py +8 -0
  48. yera/config2/dataclasses.py +534 -0
  49. yera/config2/keyring.py +270 -0
  50. yera/config2/paths.py +28 -0
  51. yera/config2/read.py +113 -0
  52. yera/config2/setup.py +109 -0
  53. yera/config2/setup_handlers/__init__.py +1 -0
  54. yera/config2/setup_handlers/anthropic.py +126 -0
  55. yera/config2/setup_handlers/azure.py +236 -0
  56. yera/config2/setup_handlers/base.py +125 -0
  57. yera/config2/setup_handlers/llama_cpp.py +205 -0
  58. yera/config2/setup_handlers/ollama.py +157 -0
  59. yera/config2/setup_handlers/openai.py +137 -0
  60. yera/config2/write.py +87 -0
  61. yera/dsl/__init__.py +0 -0
  62. yera/dsl/functions.py +94 -0
  63. yera/dsl/struct.py +20 -0
  64. yera/dsl/workspace.py +79 -0
  65. yera/events/__init__.py +57 -0
  66. yera/events/blocks/__init__.py +68 -0
  67. yera/events/blocks/action.py +57 -0
  68. yera/events/blocks/bar_chart.py +92 -0
  69. yera/events/blocks/base/__init__.py +20 -0
  70. yera/events/blocks/base/base.py +166 -0
  71. yera/events/blocks/base/chart.py +288 -0
  72. yera/events/blocks/base/layout.py +111 -0
  73. yera/events/blocks/buttons.py +37 -0
  74. yera/events/blocks/columns.py +26 -0
  75. yera/events/blocks/container.py +24 -0
  76. yera/events/blocks/date_picker.py +50 -0
  77. yera/events/blocks/exit.py +39 -0
  78. yera/events/blocks/form.py +24 -0
  79. yera/events/blocks/input_echo.py +22 -0
  80. yera/events/blocks/input_request.py +31 -0
  81. yera/events/blocks/line_chart.py +97 -0
  82. yera/events/blocks/markdown.py +67 -0
  83. yera/events/blocks/slider.py +54 -0
  84. yera/events/blocks/spinner.py +55 -0
  85. yera/events/blocks/system_prompt.py +22 -0
  86. yera/events/blocks/table.py +291 -0
  87. yera/events/models/__init__.py +39 -0
  88. yera/events/models/block_data.py +112 -0
  89. yera/events/models/in_event.py +7 -0
  90. yera/events/models/out_event.py +75 -0
  91. yera/events/runtime.py +187 -0
  92. yera/events/stream.py +91 -0
  93. yera/models/__init__.py +0 -0
  94. yera/models/data_classes.py +20 -0
  95. yera/models/llm_atlas_proxy.py +44 -0
  96. yera/models/llm_context.py +99 -0
  97. yera/models/llm_interfaces/__init__.py +0 -0
  98. yera/models/llm_interfaces/anthropic.py +153 -0
  99. yera/models/llm_interfaces/aws_bedrock.py +14 -0
  100. yera/models/llm_interfaces/azure_openai.py +143 -0
  101. yera/models/llm_interfaces/base.py +26 -0
  102. yera/models/llm_interfaces/interface_registry.py +74 -0
  103. yera/models/llm_interfaces/llama_cpp.py +136 -0
  104. yera/models/llm_interfaces/mock.py +29 -0
  105. yera/models/llm_interfaces/ollama_interface.py +118 -0
  106. yera/models/llm_interfaces/open_ai.py +150 -0
  107. yera/models/llm_workspace.py +19 -0
  108. yera/models/model_atlas.py +139 -0
  109. yera/models/model_definition.py +38 -0
  110. yera/models/model_factory.py +33 -0
  111. yera/opaque/__init__.py +9 -0
  112. yera/opaque/base.py +20 -0
  113. yera/opaque/decorator.py +8 -0
  114. yera/opaque/markdown.py +57 -0
  115. yera/opaque/opaque_function.py +25 -0
  116. yera/tools/__init__.py +29 -0
  117. yera/tools/atlas_tool.py +20 -0
  118. yera/tools/base.py +24 -0
  119. yera/tools/decorated_tool.py +18 -0
  120. yera/tools/decorator.py +35 -0
  121. yera/tools/tool_atlas.py +51 -0
  122. yera/tools/tool_utils.py +361 -0
  123. yera/ui/dist/404.html +1 -0
  124. yera/ui/dist/__next.__PAGE__.txt +10 -0
  125. yera/ui/dist/__next._full.txt +23 -0
  126. yera/ui/dist/__next._head.txt +6 -0
  127. yera/ui/dist/__next._index.txt +5 -0
  128. yera/ui/dist/__next._tree.txt +7 -0
  129. yera/ui/dist/_next/static/chunks/4c4688e1ff21ad98.js +1 -0
  130. yera/ui/dist/_next/static/chunks/652cd53c27924d50.js +4 -0
  131. yera/ui/dist/_next/static/chunks/786d2107b51e8499.css +1 -0
  132. yera/ui/dist/_next/static/chunks/7de9141b1af425c3.js +1 -0
  133. yera/ui/dist/_next/static/chunks/87ef65064d3524c1.js +2 -0
  134. yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  135. yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
  136. yera/ui/dist/_next/static/chunks/c4c79d5d0b280aeb.js +1 -0
  137. yera/ui/dist/_next/static/chunks/dc2d2a247505d66f.css +5 -0
  138. yera/ui/dist/_next/static/chunks/f773f714b55ec620.js +37 -0
  139. yera/ui/dist/_next/static/chunks/turbopack-98b3031e1b1dbc33.js +4 -0
  140. yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_buildManifest.js +11 -0
  141. yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_clientMiddlewareManifest.json +1 -0
  142. yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_ssgManifest.js +1 -0
  143. yera/ui/dist/_next/static/media/14e23f9b59180572-s.9c448f3c.woff2 +0 -0
  144. yera/ui/dist/_next/static/media/2a65768255d6b625-s.p.d19752fb.woff2 +0 -0
  145. yera/ui/dist/_next/static/media/2b2eb4836d2dad95-s.f36de3af.woff2 +0 -0
  146. yera/ui/dist/_next/static/media/31183d9fd602dc89-s.c4ff9b73.woff2 +0 -0
  147. yera/ui/dist/_next/static/media/3fcb63a1ac6a562e-s.2f77a576.woff2 +0 -0
  148. yera/ui/dist/_next/static/media/45ec8de98929b0f6-s.81056204.woff2 +0 -0
  149. yera/ui/dist/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  150. yera/ui/dist/_next/static/media/65c558afe41e89d6-s.e2c8389a.woff2 +0 -0
  151. yera/ui/dist/_next/static/media/67add6cc0f54b8cf-s.8ce53448.woff2 +0 -0
  152. yera/ui/dist/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  153. yera/ui/dist/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  154. yera/ui/dist/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  155. yera/ui/dist/_next/static/media/a8ff2d5d0ccb0d12-s.fc5b72a7.woff2 +0 -0
  156. yera/ui/dist/_next/static/media/aae5f0be330e13db-s.p.853e26d6.woff2 +0 -0
  157. yera/ui/dist/_next/static/media/b11a6ccf4a3edec7-s.2113d282.woff2 +0 -0
  158. yera/ui/dist/_next/static/media/b49b0d9b851e4899-s.4f3fa681.woff2 +0 -0
  159. yera/ui/dist/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  160. yera/ui/dist/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  161. yera/ui/dist/_next/static/media/favicon.0b3bf435.ico +0 -0
  162. yera/ui/dist/_not-found/__next._full.txt +14 -0
  163. yera/ui/dist/_not-found/__next._head.txt +6 -0
  164. yera/ui/dist/_not-found/__next._index.txt +5 -0
  165. yera/ui/dist/_not-found/__next._not-found.__PAGE__.txt +5 -0
  166. yera/ui/dist/_not-found/__next._not-found.txt +4 -0
  167. yera/ui/dist/_not-found/__next._tree.txt +2 -0
  168. yera/ui/dist/_not-found.html +1 -0
  169. yera/ui/dist/_not-found.txt +14 -0
  170. yera/ui/dist/agent-icon.svg +3 -0
  171. yera/ui/dist/favicon.ico +0 -0
  172. yera/ui/dist/file.svg +1 -0
  173. yera/ui/dist/globe.svg +1 -0
  174. yera/ui/dist/index.html +1 -0
  175. yera/ui/dist/index.txt +23 -0
  176. yera/ui/dist/logo/full_logo.png +0 -0
  177. yera/ui/dist/logo/rune_logo.png +0 -0
  178. yera/ui/dist/logo/rune_logo_borderless.png +0 -0
  179. yera/ui/dist/logo/text_logo.png +0 -0
  180. yera/ui/dist/next.svg +1 -0
  181. yera/ui/dist/send.png +0 -0
  182. yera/ui/dist/send_single.png +0 -0
  183. yera/ui/dist/vercel.svg +1 -0
  184. yera/ui/dist/window.svg +1 -0
  185. yera/utils/__init__.py +1 -0
  186. yera/utils/path_utils.py +38 -0
  187. yera-0.2.0.dist-info/METADATA +65 -0
  188. yera-0.2.0.dist-info/RECORD +190 -0
  189. {yera-0.1.0.dist-info → yera-0.2.0.dist-info}/WHEEL +1 -1
  190. yera-0.2.0.dist-info/entry_points.txt +2 -0
  191. yera-0.1.0.dist-info/METADATA +0 -11
  192. 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())
@@ -0,0 +1,9 @@
1
+ from .base import OpaqueCallable as OpaqueCallable
2
+ from .decorator import opaque as opaque
3
+ from .opaque_function import OpaqueFunction as OpaqueFunction
4
+
5
+ __all__ = [
6
+ "OpaqueCallable",
7
+ "OpaqueFunction",
8
+ "opaque",
9
+ ]
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})"
@@ -0,0 +1,8 @@
1
+ from collections.abc import Callable
2
+
3
+ from yera.opaque.opaque_function import OpaqueFunction
4
+
5
+
6
+ def opaque(func: Callable) -> OpaqueFunction:
7
+ """Decorator to mark a function as opaque to the tracer."""
8
+ return OpaqueFunction(func)
@@ -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
+ ]
@@ -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)
@@ -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
@@ -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)