ellements 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 (130) hide show
  1. ellements/__init__.py +57 -0
  2. ellements/agents/__init__.py +45 -0
  3. ellements/agents/backend.py +100 -0
  4. ellements/agents/builder.py +303 -0
  5. ellements/agents/claude_backend.py +200 -0
  6. ellements/agents/controller.py +237 -0
  7. ellements/agents/openai_backend.py +187 -0
  8. ellements/agents/py.typed +0 -0
  9. ellements/agents/runner.py +358 -0
  10. ellements/agents/tools.py +30 -0
  11. ellements/benchmarking/__init__.py +52 -0
  12. ellements/benchmarking/harness.py +342 -0
  13. ellements/benchmarking/py.typed +0 -0
  14. ellements/benchmarking/results.py +173 -0
  15. ellements/cli/__init__.py +80 -0
  16. ellements/cli/adapters.py +73 -0
  17. ellements/cli/agent_tui.py +1178 -0
  18. ellements/cli/components.py +411 -0
  19. ellements/cli/printer.py +229 -0
  20. ellements/cli/py.typed +0 -0
  21. ellements/core/__init__.py +112 -0
  22. ellements/core/async_utils.py +42 -0
  23. ellements/core/budgeting/__init__.py +43 -0
  24. ellements/core/budgeting/client.py +276 -0
  25. ellements/core/budgeting/protocol.py +51 -0
  26. ellements/core/budgeting/trackers.py +177 -0
  27. ellements/core/caching/__init__.py +51 -0
  28. ellements/core/caching/cache.py +73 -0
  29. ellements/core/caching/client.py +300 -0
  30. ellements/core/caching/disk.py +128 -0
  31. ellements/core/caching/keys.py +97 -0
  32. ellements/core/caching/memory.py +104 -0
  33. ellements/core/chunking.py +262 -0
  34. ellements/core/config.py +180 -0
  35. ellements/core/exceptions.py +145 -0
  36. ellements/core/llm/__init__.py +46 -0
  37. ellements/core/llm/client.py +1124 -0
  38. ellements/core/llm/images.py +226 -0
  39. ellements/core/llm/messages.py +202 -0
  40. ellements/core/llm/model_params.py +66 -0
  41. ellements/core/llm/protocol.py +146 -0
  42. ellements/core/llm/requests.py +100 -0
  43. ellements/core/llm/structured.py +91 -0
  44. ellements/core/llm/wrapper.py +79 -0
  45. ellements/core/observability/__init__.py +39 -0
  46. ellements/core/observability/events.py +169 -0
  47. ellements/core/observability/jsonl_logger.py +244 -0
  48. ellements/core/observability/markdown_formatter.py +197 -0
  49. ellements/core/observability/observer.py +56 -0
  50. ellements/core/prompting/__init__.py +14 -0
  51. ellements/core/prompting/context.py +185 -0
  52. ellements/core/prompting/guideline.py +133 -0
  53. ellements/core/prompting/persona.py +267 -0
  54. ellements/core/prompting/sources.py +92 -0
  55. ellements/core/py.typed +0 -0
  56. ellements/core/rate_limit/__init__.py +44 -0
  57. ellements/core/rate_limit/bucket.py +85 -0
  58. ellements/core/rate_limit/client.py +216 -0
  59. ellements/core/rate_limit/protocol.py +27 -0
  60. ellements/core/templating.py +126 -0
  61. ellements/core/tools/__init__.py +51 -0
  62. ellements/core/tools/dialects.py +136 -0
  63. ellements/core/tools/executor.py +48 -0
  64. ellements/core/tools/protocol.py +36 -0
  65. ellements/core/tools/records.py +28 -0
  66. ellements/core/tools/registry.py +205 -0
  67. ellements/core/tools/schemas.py +80 -0
  68. ellements/core/tools/simple.py +119 -0
  69. ellements/core/tools/spec.py +33 -0
  70. ellements/domain_specific/__init__.py +7 -0
  71. ellements/domain_specific/finance/__init__.py +188 -0
  72. ellements/domain_specific/finance/calculations.py +837 -0
  73. ellements/domain_specific/finance/charts.py +426 -0
  74. ellements/domain_specific/finance/portfolio.py +129 -0
  75. ellements/domain_specific/finance/quant_analysis.py +279 -0
  76. ellements/domain_specific/finance/risk.py +362 -0
  77. ellements/domain_specific/finance/technical_indicators.py +241 -0
  78. ellements/domain_specific/finance/tools.py +483 -0
  79. ellements/domain_specific/finance/valuation.py +312 -0
  80. ellements/domain_specific/finance/yahoo_finance.py +1523 -0
  81. ellements/domain_specific/finance/yahoo_finance_models.py +321 -0
  82. ellements/domain_specific/py.typed +0 -0
  83. ellements/execution/__init__.py +56 -0
  84. ellements/execution/callbacks.py +149 -0
  85. ellements/execution/catalog.py +70 -0
  86. ellements/execution/collaborative.py +191 -0
  87. ellements/execution/config.py +135 -0
  88. ellements/execution/py.typed +0 -0
  89. ellements/execution/reflection.py +156 -0
  90. ellements/execution/self_consistency.py +189 -0
  91. ellements/execution/single_call.py +48 -0
  92. ellements/execution/strategies.py +237 -0
  93. ellements/execution/tree_of_thought.py +541 -0
  94. ellements/fslm/__init__.py +108 -0
  95. ellements/fslm/builtins.py +103 -0
  96. ellements/fslm/cli.py +199 -0
  97. ellements/fslm/context.py +50 -0
  98. ellements/fslm/definition.py +163 -0
  99. ellements/fslm/det.py +173 -0
  100. ellements/fslm/dsl.py +458 -0
  101. ellements/fslm/errors.py +38 -0
  102. ellements/fslm/evaluators.py +141 -0
  103. ellements/fslm/kernel.py +603 -0
  104. ellements/fslm/loading.py +123 -0
  105. ellements/fslm/models.py +623 -0
  106. ellements/fslm/nl.py +87 -0
  107. ellements/fslm/observers.py +107 -0
  108. ellements/fslm/persistence.py +72 -0
  109. ellements/fslm/py.typed +0 -0
  110. ellements/fslm/rendering.py +551 -0
  111. ellements/fslm/visualization.py +25 -0
  112. ellements/reporting/__init__.py +12 -0
  113. ellements/reporting/charts.py +364 -0
  114. ellements/reporting/html_generation.py +132 -0
  115. ellements/reporting/py.typed +0 -0
  116. ellements/reporting/visualization.py +73 -0
  117. ellements/standard_tools/__init__.py +22 -0
  118. ellements/standard_tools/py.typed +0 -0
  119. ellements/standard_tools/terminal.py +205 -0
  120. ellements/standard_tools/web/__init__.py +37 -0
  121. ellements/standard_tools/web/browser_viewer.py +214 -0
  122. ellements/standard_tools/web/crawler.py +454 -0
  123. ellements/standard_tools/web/search.py +399 -0
  124. ellements/standard_tools/web/youtube.py +1297 -0
  125. ellements-0.2.0.dist-info/METADATA +368 -0
  126. ellements-0.2.0.dist-info/RECORD +130 -0
  127. ellements-0.2.0.dist-info/WHEEL +5 -0
  128. ellements-0.2.0.dist-info/entry_points.txt +2 -0
  129. ellements-0.2.0.dist-info/licenses/LICENSE +42 -0
  130. ellements-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,48 @@
1
+ """Single-call strategy — the simplest execution pattern.
2
+
3
+ Makes one LLM call (optionally with tools) using the ``default`` prompt
4
+ and returns the result.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+ from typing import Any
11
+
12
+ from ellements.core import LLMClient
13
+
14
+ from .config import SingleCallConfig, StrategyConfigInput
15
+ from .strategies import BaseStrategy, StepRecord, StrategyResult
16
+
17
+
18
+ class SingleCallStrategy(BaseStrategy):
19
+ """Execute a single LLM call with the ``default`` prompt."""
20
+
21
+ REQUIRED_PROMPTS = ("default",)
22
+
23
+ async def execute(
24
+ self,
25
+ prompts: Mapping[str, str],
26
+ client: LLMClient,
27
+ tools: list[dict[str, Any]] | Mapping[str, Any] | None = None,
28
+ config: StrategyConfigInput = None,
29
+ ) -> StrategyResult:
30
+ typed_config = self._normalize_config(config, SingleCallConfig)
31
+ prompt = self._get_prompt(prompts, "default")
32
+
33
+ temperature = self._stage_temperature(typed_config, "single", default=0.7)
34
+ if typed_config.temperature is not None:
35
+ temperature = typed_config.temperature
36
+
37
+ response = await self._complete(
38
+ client,
39
+ prompt,
40
+ config=typed_config,
41
+ temperature=temperature,
42
+ system=prompts.get("system"),
43
+ tools=tools,
44
+ )
45
+
46
+ step = StepRecord(name="call", prompt_key="default", response=response)
47
+ self._notify_step(typed_config, step)
48
+ return StrategyResult(output=response, steps=[step])
@@ -0,0 +1,237 @@
1
+ """Strategy protocol and shared base utilities.
2
+
3
+ Strategies orchestrate multiple LLM calls in patterns like single-call,
4
+ self-consistency, tree-of-thought, reflection, and collaborative editing.
5
+
6
+ The :class:`Strategy` Protocol is the canonical public type. Any object
7
+ with a matching ``execute`` method satisfies it — no inheritance
8
+ required. :class:`BaseStrategy` is an optional helper that supplies
9
+ shared utilities (config normalization, prompt resolution, step
10
+ notification, runtime-template rendering, tool-aware LLM invocation).
11
+
12
+ Retry and backoff live exclusively on :class:`~ellements.core.LLMClient`.
13
+ Strategies must not retry on top.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import re
20
+ from collections.abc import Callable, Mapping
21
+ from dataclasses import dataclass, field
22
+ from typing import Any, Protocol, TypeVar, runtime_checkable
23
+
24
+ import chevron
25
+ from ellements.core import LLMClient, MessageInput, PromptKeyMissingError
26
+ from pydantic import BaseModel
27
+
28
+ from .config import (
29
+ ExecutionStrategyConfig,
30
+ StrategyConfigInput,
31
+ normalize_strategy_config,
32
+ )
33
+
34
+ _logger = logging.getLogger(__name__)
35
+
36
+ ConfigModelT = TypeVar("ConfigModelT", bound=ExecutionStrategyConfig)
37
+ StructuredT = TypeVar("StructuredT", bound=BaseModel)
38
+
39
+ # Callback invoked after each :class:`StepRecord` is produced.
40
+ OnStepCallback = Callable[["StepRecord"], None]
41
+
42
+
43
+ @dataclass(slots=True)
44
+ class StepRecord:
45
+ """One step in a strategy execution (single LLM call or aggregation)."""
46
+
47
+ name: str
48
+ prompt_key: str
49
+ response: str
50
+ metadata: dict[str, Any] = field(default_factory=dict)
51
+
52
+
53
+ @dataclass(slots=True)
54
+ class StrategyResult:
55
+ """Final result of a strategy execution."""
56
+
57
+ output: str
58
+ steps: list[StepRecord] = field(default_factory=list)
59
+ metadata: dict[str, Any] = field(default_factory=dict)
60
+
61
+
62
+ @runtime_checkable
63
+ class Strategy(Protocol):
64
+ """Protocol for execution strategies.
65
+
66
+ Any object whose ``execute`` method matches this signature is a
67
+ valid strategy — no inheritance required. This is the public type
68
+ used in every signature that accepts a strategy.
69
+ """
70
+
71
+ async def execute(
72
+ self,
73
+ prompts: Mapping[str, str],
74
+ client: LLMClient,
75
+ tools: list[dict[str, Any]] | Mapping[str, Any] | None = None,
76
+ config: StrategyConfigInput = None,
77
+ ) -> StrategyResult: ...
78
+
79
+
80
+ class BaseStrategy:
81
+ """Optional helper base class with utilities shared by built-in strategies."""
82
+
83
+ @staticmethod
84
+ def _notify_step(config: StrategyConfigInput, step: StepRecord) -> None:
85
+ """Invoke the ``on_step`` callback if one is present in *config*."""
86
+ if config is None:
87
+ return
88
+ callback: Any
89
+ if isinstance(config, Mapping):
90
+ callback = config.get("on_step")
91
+ else:
92
+ callback = getattr(config, "on_step", None)
93
+ if callable(callback):
94
+ callback(step)
95
+
96
+ @staticmethod
97
+ def _normalize_config(
98
+ config: StrategyConfigInput,
99
+ config_model: type[ConfigModelT],
100
+ ) -> ConfigModelT:
101
+ """Normalize *config* to the typed model expected by the strategy."""
102
+ return normalize_strategy_config(config, config_model)
103
+
104
+ @staticmethod
105
+ def _get_prompt(
106
+ prompts: Mapping[str, str],
107
+ key: str,
108
+ *,
109
+ required: bool = True,
110
+ ) -> str:
111
+ """Return the prompt for *key*, with a strict required/optional contract.
112
+
113
+ - ``required=True`` and the key is missing → raises
114
+ :class:`PromptKeyMissingError`. There is no silent fallback to
115
+ ``"default"``.
116
+ - ``required=False`` and the key is missing → returns ``""``.
117
+ """
118
+ value = prompts.get(key)
119
+ if value is not None:
120
+ return value
121
+ if not required:
122
+ return ""
123
+ raise PromptKeyMissingError(
124
+ f"Strategy requires a prompt named {key!r}. "
125
+ f"Available: {sorted(prompts.keys())}."
126
+ )
127
+
128
+ @staticmethod
129
+ def _render_template(template: str, context: Mapping[str, Any]) -> str:
130
+ """Render runtime placeholders against *context*.
131
+
132
+ Strategies support regular Mustache placeholders (``{{name}}``) plus
133
+ PromptSpec-style placeholders (``@{name}``) so executable PromptSpec
134
+ files can use the same placeholder form as the rest of the DSL.
135
+ """
136
+ rendered: str = chevron.render(template, dict(context))
137
+
138
+ def replace_promptspec_placeholder(match: re.Match[str]) -> str:
139
+ key = match.group(1)
140
+ if key not in context:
141
+ return match.group(0)
142
+ return str(context[key])
143
+
144
+ return re.sub(
145
+ r"@\{([A-Za-z_][\w.-]*)\}",
146
+ replace_promptspec_placeholder,
147
+ rendered,
148
+ )
149
+
150
+ @staticmethod
151
+ def _stage_temperature(
152
+ config: ExecutionStrategyConfig,
153
+ stage: str,
154
+ default: float,
155
+ ) -> float:
156
+ """Resolve the effective temperature for a named pipeline stage.
157
+
158
+ ``stage_temperatures[stage]`` overrides the per-stage attribute
159
+ on the typed config, which itself defaults to *default*.
160
+ """
161
+ if config.stage_temperatures and stage in config.stage_temperatures:
162
+ return config.stage_temperatures[stage]
163
+ attr = getattr(config, f"{stage}_temperature", None)
164
+ if isinstance(attr, (int, float)):
165
+ return float(attr)
166
+ return default
167
+
168
+ @classmethod
169
+ async def _complete(
170
+ cls,
171
+ client: LLMClient,
172
+ prompt: str,
173
+ *,
174
+ config: ExecutionStrategyConfig,
175
+ temperature: float,
176
+ system: str | None = None,
177
+ tools: list[dict[str, Any]] | Mapping[str, Any] | None = None,
178
+ ) -> str:
179
+ """Make a single LLM call, with tools if any are provided.
180
+
181
+ Retries are handled inside :class:`LLMClient`; strategies do not
182
+ add a second retry layer.
183
+ """
184
+ messages: list[dict[str, Any]] = []
185
+ if system:
186
+ messages.append({"role": "system", "content": system})
187
+ messages.append({"role": "user", "content": prompt})
188
+
189
+ if tools:
190
+ response = await client.complete_with_tools(
191
+ messages,
192
+ tools=tools,
193
+ model=config.model,
194
+ temperature=temperature,
195
+ )
196
+ return response.content
197
+ return await client.complete(
198
+ messages,
199
+ model=config.model,
200
+ temperature=temperature,
201
+ )
202
+
203
+ @staticmethod
204
+ async def _complete_structured(
205
+ client: LLMClient,
206
+ prompt: str,
207
+ response_model: type[StructuredT],
208
+ *,
209
+ config: ExecutionStrategyConfig,
210
+ temperature: float,
211
+ system: str | None = None,
212
+ ) -> StructuredT:
213
+ """Make a structured LLM call returning a Pydantic model instance."""
214
+ messages: list[dict[str, Any]] = []
215
+ if system:
216
+ messages.append({"role": "system", "content": system})
217
+ messages.append({"role": "user", "content": prompt})
218
+ return await client.complete_structured(
219
+ messages,
220
+ response_model=response_model,
221
+ model=config.model,
222
+ temperature=temperature,
223
+ )
224
+
225
+
226
+ __all__ = [
227
+ "BaseStrategy",
228
+ "OnStepCallback",
229
+ "StepRecord",
230
+ "Strategy",
231
+ "StrategyResult",
232
+ ]
233
+
234
+
235
+ # Allow type-checkers to see MessageInput is referenced even though we
236
+ # don't currently expose it through this module.
237
+ _ = MessageInput # noqa: F841