mantisdk 0.1.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.

Potentially problematic release.


This version of mantisdk might be problematic. Click here for more details.

Files changed (190) hide show
  1. mantisdk/__init__.py +22 -0
  2. mantisdk/adapter/__init__.py +15 -0
  3. mantisdk/adapter/base.py +94 -0
  4. mantisdk/adapter/messages.py +270 -0
  5. mantisdk/adapter/triplet.py +1028 -0
  6. mantisdk/algorithm/__init__.py +39 -0
  7. mantisdk/algorithm/apo/__init__.py +5 -0
  8. mantisdk/algorithm/apo/apo.py +889 -0
  9. mantisdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. mantisdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. mantisdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. mantisdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. mantisdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. mantisdk/algorithm/base.py +162 -0
  15. mantisdk/algorithm/decorator.py +264 -0
  16. mantisdk/algorithm/fast.py +250 -0
  17. mantisdk/algorithm/gepa/__init__.py +59 -0
  18. mantisdk/algorithm/gepa/adapter.py +459 -0
  19. mantisdk/algorithm/gepa/gepa.py +364 -0
  20. mantisdk/algorithm/gepa/lib/__init__.py +18 -0
  21. mantisdk/algorithm/gepa/lib/adapters/README.md +12 -0
  22. mantisdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  23. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  24. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  25. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  26. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  27. mantisdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  28. mantisdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  29. mantisdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  30. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  31. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  32. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  33. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  34. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  35. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +266 -0
  36. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  37. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  38. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  39. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  40. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  41. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  42. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  43. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  44. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  45. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  46. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  47. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  48. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  49. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  50. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +705 -0
  51. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  52. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  53. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  54. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  55. mantisdk/algorithm/gepa/lib/api.py +375 -0
  56. mantisdk/algorithm/gepa/lib/core/__init__.py +0 -0
  57. mantisdk/algorithm/gepa/lib/core/adapter.py +180 -0
  58. mantisdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  59. mantisdk/algorithm/gepa/lib/core/engine.py +356 -0
  60. mantisdk/algorithm/gepa/lib/core/result.py +233 -0
  61. mantisdk/algorithm/gepa/lib/core/state.py +636 -0
  62. mantisdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  63. mantisdk/algorithm/gepa/lib/examples/aime.py +24 -0
  64. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  65. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  66. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  67. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  68. mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  69. mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  70. mantisdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  71. mantisdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +455 -0
  72. mantisdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  73. mantisdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  74. mantisdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +824 -0
  75. mantisdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  76. mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  77. mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  78. mantisdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  79. mantisdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  80. mantisdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  81. mantisdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  82. mantisdk/algorithm/gepa/lib/logging/logger.py +75 -0
  83. mantisdk/algorithm/gepa/lib/logging/utils.py +103 -0
  84. mantisdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  85. mantisdk/algorithm/gepa/lib/proposer/base.py +31 -0
  86. mantisdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  87. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  88. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  89. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  90. mantisdk/algorithm/gepa/lib/py.typed +0 -0
  91. mantisdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  92. mantisdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  93. mantisdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  94. mantisdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  95. mantisdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  96. mantisdk/algorithm/gepa/lib/strategies/instruction_proposal.py +127 -0
  97. mantisdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  98. mantisdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  99. mantisdk/algorithm/gepa/tracing.py +105 -0
  100. mantisdk/algorithm/utils.py +177 -0
  101. mantisdk/algorithm/verl/__init__.py +5 -0
  102. mantisdk/algorithm/verl/interface.py +202 -0
  103. mantisdk/cli/__init__.py +56 -0
  104. mantisdk/cli/prometheus.py +115 -0
  105. mantisdk/cli/store.py +131 -0
  106. mantisdk/cli/vllm.py +29 -0
  107. mantisdk/client.py +408 -0
  108. mantisdk/config.py +348 -0
  109. mantisdk/emitter/__init__.py +43 -0
  110. mantisdk/emitter/annotation.py +370 -0
  111. mantisdk/emitter/exception.py +54 -0
  112. mantisdk/emitter/message.py +61 -0
  113. mantisdk/emitter/object.py +117 -0
  114. mantisdk/emitter/reward.py +320 -0
  115. mantisdk/env_var.py +156 -0
  116. mantisdk/execution/__init__.py +15 -0
  117. mantisdk/execution/base.py +64 -0
  118. mantisdk/execution/client_server.py +443 -0
  119. mantisdk/execution/events.py +69 -0
  120. mantisdk/execution/inter_process.py +16 -0
  121. mantisdk/execution/shared_memory.py +282 -0
  122. mantisdk/instrumentation/__init__.py +119 -0
  123. mantisdk/instrumentation/agentops.py +314 -0
  124. mantisdk/instrumentation/agentops_langchain.py +45 -0
  125. mantisdk/instrumentation/litellm.py +83 -0
  126. mantisdk/instrumentation/vllm.py +81 -0
  127. mantisdk/instrumentation/weave.py +500 -0
  128. mantisdk/litagent/__init__.py +11 -0
  129. mantisdk/litagent/decorator.py +536 -0
  130. mantisdk/litagent/litagent.py +252 -0
  131. mantisdk/llm_proxy.py +1890 -0
  132. mantisdk/logging.py +370 -0
  133. mantisdk/reward.py +7 -0
  134. mantisdk/runner/__init__.py +11 -0
  135. mantisdk/runner/agent.py +845 -0
  136. mantisdk/runner/base.py +182 -0
  137. mantisdk/runner/legacy.py +309 -0
  138. mantisdk/semconv.py +170 -0
  139. mantisdk/server.py +401 -0
  140. mantisdk/store/__init__.py +23 -0
  141. mantisdk/store/base.py +897 -0
  142. mantisdk/store/client_server.py +2092 -0
  143. mantisdk/store/collection/__init__.py +30 -0
  144. mantisdk/store/collection/base.py +587 -0
  145. mantisdk/store/collection/memory.py +970 -0
  146. mantisdk/store/collection/mongo.py +1412 -0
  147. mantisdk/store/collection_based.py +1823 -0
  148. mantisdk/store/insight.py +648 -0
  149. mantisdk/store/listener.py +58 -0
  150. mantisdk/store/memory.py +396 -0
  151. mantisdk/store/mongo.py +165 -0
  152. mantisdk/store/sqlite.py +3 -0
  153. mantisdk/store/threading.py +357 -0
  154. mantisdk/store/utils.py +142 -0
  155. mantisdk/tracer/__init__.py +16 -0
  156. mantisdk/tracer/agentops.py +242 -0
  157. mantisdk/tracer/base.py +287 -0
  158. mantisdk/tracer/dummy.py +106 -0
  159. mantisdk/tracer/otel.py +555 -0
  160. mantisdk/tracer/weave.py +677 -0
  161. mantisdk/trainer/__init__.py +6 -0
  162. mantisdk/trainer/init_utils.py +263 -0
  163. mantisdk/trainer/legacy.py +367 -0
  164. mantisdk/trainer/registry.py +12 -0
  165. mantisdk/trainer/trainer.py +618 -0
  166. mantisdk/types/__init__.py +6 -0
  167. mantisdk/types/core.py +553 -0
  168. mantisdk/types/resources.py +204 -0
  169. mantisdk/types/tracer.py +515 -0
  170. mantisdk/types/tracing.py +218 -0
  171. mantisdk/utils/__init__.py +1 -0
  172. mantisdk/utils/id.py +18 -0
  173. mantisdk/utils/metrics.py +1025 -0
  174. mantisdk/utils/otel.py +578 -0
  175. mantisdk/utils/otlp.py +536 -0
  176. mantisdk/utils/server_launcher.py +1045 -0
  177. mantisdk/utils/system_snapshot.py +81 -0
  178. mantisdk/verl/__init__.py +8 -0
  179. mantisdk/verl/__main__.py +6 -0
  180. mantisdk/verl/async_server.py +46 -0
  181. mantisdk/verl/config.yaml +27 -0
  182. mantisdk/verl/daemon.py +1154 -0
  183. mantisdk/verl/dataset.py +44 -0
  184. mantisdk/verl/entrypoint.py +248 -0
  185. mantisdk/verl/trainer.py +549 -0
  186. mantisdk-0.1.0.dist-info/METADATA +119 -0
  187. mantisdk-0.1.0.dist-info/RECORD +190 -0
  188. mantisdk-0.1.0.dist-info/WHEEL +4 -0
  189. mantisdk-0.1.0.dist-info/entry_points.txt +2 -0
  190. mantisdk-0.1.0.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,536 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Convenience decorators for building lightweight `LitAgent` implementations."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import functools
8
+ import inspect
9
+ import logging
10
+ from typing import Any, Awaitable, Callable, Dict, Protocol, TypeGuard, TypeVar, Union, overload
11
+
12
+ from mantisdk.types import (
13
+ LLM,
14
+ AttemptedRollout,
15
+ NamedResources,
16
+ PromptTemplate,
17
+ ProxyLLM,
18
+ Rollout,
19
+ RolloutRawResult,
20
+ )
21
+
22
+ from .litagent import LitAgent
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ T = TypeVar("T")
27
+
28
+ __all__ = [
29
+ "llm_rollout",
30
+ "prompt_rollout",
31
+ "rollout",
32
+ ]
33
+
34
+
35
+ T_contra = TypeVar("T_contra", contravariant=True)
36
+
37
+
38
+ class LlmRolloutFuncSync2(Protocol[T_contra]):
39
+ def __call__(self, task: T_contra, llm: LLM) -> RolloutRawResult: ...
40
+
41
+
42
+ class LlmRolloutFuncSync3(Protocol[T_contra]):
43
+ def __call__(self, task: T_contra, llm: LLM, rollout: Rollout) -> RolloutRawResult: ...
44
+
45
+
46
+ class LlmRolloutFuncAsync2(Protocol[T_contra]):
47
+ def __call__(self, task: T_contra, llm: LLM) -> Awaitable[RolloutRawResult]: ...
48
+
49
+
50
+ class LlmRolloutFuncAsync3(Protocol[T_contra]):
51
+ def __call__(self, task: T_contra, llm: LLM, rollout: Rollout) -> Awaitable[RolloutRawResult]: ...
52
+
53
+
54
+ LlmRolloutFunc = Union[
55
+ LlmRolloutFuncSync2[T_contra],
56
+ LlmRolloutFuncSync3[T_contra],
57
+ LlmRolloutFuncAsync2[T_contra],
58
+ LlmRolloutFuncAsync3[T_contra],
59
+ ]
60
+
61
+
62
+ class PromptRolloutFuncSync2(Protocol[T_contra]):
63
+ def __call__(self, task: T_contra, prompt_template: PromptTemplate) -> RolloutRawResult: ...
64
+
65
+
66
+ class PromptRolloutFuncAsync2(Protocol[T_contra]):
67
+ def __call__(self, task: T_contra, prompt_template: PromptTemplate) -> Awaitable[RolloutRawResult]: ...
68
+
69
+
70
+ class PromptRolloutFuncSync3(Protocol[T_contra]):
71
+ def __call__(self, task: T_contra, prompt_template: PromptTemplate, rollout: Rollout) -> RolloutRawResult: ...
72
+
73
+
74
+ class PromptRolloutFuncAsync3(Protocol[T_contra]):
75
+ def __call__(
76
+ self, task: T_contra, prompt_template: PromptTemplate, rollout: Rollout
77
+ ) -> Awaitable[RolloutRawResult]: ...
78
+
79
+
80
+ PromptRolloutFunc = Union[
81
+ PromptRolloutFuncSync2[T_contra],
82
+ PromptRolloutFuncSync3[T_contra],
83
+ PromptRolloutFuncAsync2[T_contra],
84
+ PromptRolloutFuncAsync3[T_contra],
85
+ ]
86
+
87
+
88
+ class FunctionalLitAgentFunc(Protocol[T_contra]):
89
+ def __call__(
90
+ self, task: T_contra, *args: Any, **kwargs: Any
91
+ ) -> Union[RolloutRawResult, Awaitable[RolloutRawResult]]: ...
92
+
93
+
94
+ class FunctionalLitAgent(LitAgent[T]):
95
+ """Adapter that turns plain rollout functions into [`LitAgent`][mantisdk.LitAgent] instances.
96
+
97
+ The helper inspects the wrapped function to determine which resources to
98
+ inject, allowing both synchronous and asynchronous callables to participate
99
+ in the training loop without writing a dedicated subclass.
100
+ """
101
+
102
+ def __init__(self, rollout_func: FunctionalLitAgentFunc[T], *, strip_proxy: bool = True) -> None:
103
+ """Initialize the wrapper around a rollout function.
104
+
105
+ Args:
106
+ rollout_func: Callable that implements the rollout. It may be synchronous
107
+ or asynchronous and can optionally receive a
108
+ [`Rollout`][mantisdk.Rollout] alongside resources such as
109
+ `llm` or `prompt_template`.
110
+ strip_proxy: When ``True``, convert
111
+ [`ProxyLLM`][mantisdk.ProxyLLM] inputs into
112
+ [`LLM`][mantisdk.LLM] instances before calling the
113
+ rollout function. Defaults to `True`.
114
+ """
115
+ super().__init__()
116
+ self._rollout_func = rollout_func
117
+ self._strip_proxy = strip_proxy
118
+ self._is_async = inspect.iscoroutinefunction(rollout_func)
119
+ self._sig = inspect.signature(rollout_func)
120
+
121
+ # Copy function metadata to preserve type hints and other attributes
122
+ functools.update_wrapper(self, rollout_func) # type: ignore
123
+
124
+ def _accepts_rollout(self) -> bool:
125
+ return "rollout" in self._sig.parameters
126
+
127
+ def _accepts_llm(self) -> bool:
128
+ return "llm" in self._sig.parameters
129
+
130
+ def _accepts_prompt_template(self) -> bool:
131
+ return "prompt_template" in self._sig.parameters
132
+
133
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
134
+ """Make the agent instance callable, preserving the original function behavior."""
135
+ return self._rollout_func(*args, **kwargs) # type: ignore
136
+
137
+ def is_async(self) -> bool:
138
+ return self._is_async
139
+
140
+ def rollout(self, task: T, resources: NamedResources, rollout: Rollout) -> RolloutRawResult:
141
+ """Execute a synchronous rollout using the wrapped function.
142
+
143
+ Args:
144
+ task: Task input data.
145
+ resources: Mapping of named resources available to the agent.
146
+ rollout: Rollout metadata provided by the runtime.
147
+
148
+ Returns:
149
+ Result produced by the wrapped rollout function.
150
+
151
+ Raises:
152
+ RuntimeError: If the wrapped function is asynchronous.
153
+ """
154
+ if self._is_async:
155
+ raise RuntimeError(f"{self._rollout_func} is asynchronous. Use rollout_async instead.")
156
+
157
+ kwargs = self._get_kwargs(resources, rollout)
158
+ return self._rollout_func(task, **kwargs) # type: ignore
159
+
160
+ async def rollout_async(self, task: T, resources: NamedResources, rollout: Rollout) -> RolloutRawResult:
161
+ """Execute an asynchronous rollout using the wrapped function.
162
+
163
+ Args:
164
+ task: Task input data.
165
+ resources: Mapping of named resources available to the agent.
166
+ rollout: Rollout metadata provided by the runtime.
167
+
168
+ Returns:
169
+ Result produced by the wrapped rollout coroutine.
170
+
171
+ Raises:
172
+ RuntimeError: If the wrapped function is synchronous.
173
+ """
174
+ if not self._is_async:
175
+ raise RuntimeError(f"{self._rollout_func} is synchronous. Use rollout instead.")
176
+
177
+ kwargs = self._get_kwargs(resources, rollout)
178
+ return await self._rollout_func(task, **kwargs) # type: ignore
179
+
180
+ def _get_kwargs(self, resources: NamedResources, rollout: Rollout) -> Dict[str, Any]:
181
+ """Prepare keyword arguments expected by the wrapped rollout function.
182
+
183
+
184
+ It dynamically builds the `kwargs` dictionary by inspecting the function signature and
185
+ including only the parameters the function accepts. This allows flexible function
186
+ signatures that can request any combination of: rollout, llm, and/or prompt_template.
187
+
188
+ Args:
189
+ resources: Mapping of named resources available for the rollout.
190
+ rollout: Rollout metadata provided by the runtime.
191
+
192
+ Returns:
193
+ Dictionary of keyword arguments to forward to the rollout function.
194
+ """
195
+
196
+ kwargs: Dict[str, Any] = {}
197
+ if self._accepts_rollout():
198
+ kwargs["rollout"] = rollout
199
+ if self._accepts_llm():
200
+ kwargs["llm"] = self._get_llm_resource(resources, rollout)
201
+ if self._accepts_prompt_template():
202
+ kwargs["prompt_template"] = self._get_prompt_template_resource(resources, rollout)
203
+
204
+ return kwargs
205
+
206
+ def _get_llm_resource(self, resources: NamedResources, rollout: Rollout) -> LLM:
207
+ """Retrieve the first LLM resource from the available resources.
208
+
209
+ Strip the ProxyLLM resource into a LLM resource if needed.
210
+
211
+ Args:
212
+ resources: Mapping of named resources.
213
+ rollout: Rollout metadata used when stripping proxy endpoints.
214
+
215
+ Returns:
216
+ First [`LLM`][mantisdk.LLM] resource encountered.
217
+
218
+ Raises:
219
+ ValueError: If no LLM resource is present.
220
+ """
221
+ resource_found: LLM | None = None
222
+ for name, resource in resources.items():
223
+ if isinstance(resource, LLM):
224
+ if resource_found is not None:
225
+ logger.warning(f"Multiple LLM resources found in resources. Using the first one: '{name}'.")
226
+ break
227
+ resource_found = resource
228
+
229
+ if resource_found is None:
230
+ raise ValueError("No LLM resource found in the provided resources.")
231
+
232
+ if self._strip_proxy:
233
+ resource_found = self._strip_proxy_helper(resource_found, rollout)
234
+
235
+ return resource_found
236
+
237
+ def _get_prompt_template_resource(self, resources: NamedResources, rollout: Rollout) -> PromptTemplate:
238
+ """Retrieve the first prompt template resource from the available resources.
239
+
240
+ Args:
241
+ resources: Mapping of named resources.
242
+ rollout: Rollout metadata (unused).
243
+
244
+ Returns:
245
+ First [`PromptTemplate`][mantisdk.PromptTemplate] resource encountered.
246
+
247
+ Raises:
248
+ ValueError: If no prompt template resource is present.
249
+ """
250
+ resource_found: PromptTemplate | None = None
251
+ for name, resource in resources.items():
252
+ if isinstance(resource, PromptTemplate):
253
+ if resource_found is not None:
254
+ logger.warning(
255
+ f"Multiple prompt template resources found in resources. Using the first one: '{name}'."
256
+ )
257
+ break
258
+ resource_found = resource
259
+
260
+ if resource_found is None:
261
+ raise ValueError("No prompt template resource found in the provided resources.")
262
+
263
+ return resource_found
264
+
265
+ def _strip_proxy_helper(self, proxy_llm: LLM, rollout: Rollout) -> LLM:
266
+ """Convert [`ProxyLLM`][mantisdk.ProxyLLM] instances into concrete LLMs.
267
+
268
+ It resolves ProxyLLM instances to their concrete LLM implementation
269
+ by attaching the attempted rollout context. This is only used when the function
270
+ signature accepts an `llm` parameter and strip_proxy is True.
271
+
272
+ Args:
273
+ proxy_llm: Candidate LLM resource.
274
+ rollout: Rollout metadata that provides rollout and attempt identifiers.
275
+
276
+ Returns:
277
+ [`LLM`][mantisdk.LLM] with rollout context baked into the endpoint.
278
+
279
+ Raises:
280
+ ValueError: If the rollout is not an
281
+ [`AttemptedRollout`][mantisdk.AttemptedRollout].
282
+ """
283
+
284
+ if not isinstance(proxy_llm, ProxyLLM):
285
+ # Not a ProxyLLM, nothing to strip here.
286
+ return proxy_llm
287
+
288
+ # Rollout is still a Rollout here because API is not stabilized yet.
289
+ # In practice, it must be an AttemptedRollout.
290
+ if not isinstance(rollout, AttemptedRollout):
291
+ raise ValueError("Rollout is not an AttemptedRollout.")
292
+
293
+ return proxy_llm.with_attempted_rollout(rollout)
294
+
295
+
296
+ @overload
297
+ def llm_rollout(func: LlmRolloutFunc[T]) -> FunctionalLitAgent[T]: ...
298
+
299
+
300
+ @overload
301
+ def llm_rollout(*, strip_proxy: bool = True) -> Callable[[LlmRolloutFunc[T]], FunctionalLitAgent[T]]: ...
302
+
303
+
304
+ def llm_rollout(
305
+ func: LlmRolloutFunc[T] | None = None, *, strip_proxy: bool = True
306
+ ) -> FunctionalLitAgent[T] | Callable[[LlmRolloutFunc[T]], FunctionalLitAgent[T]]:
307
+ """Create a [`FunctionalLitAgent`][mantisdk.litagent.decorator.FunctionalLitAgent] for LLM-based rollouts.
308
+
309
+ Args:
310
+ func: Callable defining the agent's behaviour. Supported signatures include:
311
+
312
+ * `(task, llm) -> result`
313
+ * `(task, llm, rollout) -> result`
314
+ * `async (task, llm) -> result`
315
+ * `async (task, llm, rollout) -> result`
316
+
317
+ strip_proxy: When `True`, convert proxy resources into concrete
318
+ [`LLM`][mantisdk.LLM] instances before calling the
319
+ function. Defaults to `True`.
320
+
321
+ Returns:
322
+ [`FunctionalLitAgent`][mantisdk.litagent.decorator.FunctionalLitAgent] that
323
+ wraps the supplied function.
324
+
325
+ Examples:
326
+ ```python
327
+ @llm_rollout
328
+ def my_agent(task, llm):
329
+ return llm.endpoint
330
+
331
+ @llm_rollout(strip_proxy=False)
332
+ def my_agent_no_strip(task, llm):
333
+ return llm.model
334
+
335
+ result = my_agent(task, llm)
336
+ result = my_agent.rollout(task, resources, rollout)
337
+ ```
338
+ """
339
+
340
+ def decorator(f: LlmRolloutFunc[T]) -> FunctionalLitAgent[T]:
341
+ _validate_llm_rollout_func(f)
342
+ return FunctionalLitAgent(f, strip_proxy=strip_proxy)
343
+
344
+ if func is None:
345
+ # Called with arguments: @llm_rollout(strip_proxy=False)
346
+ return decorator
347
+ else:
348
+ # Called without arguments: @llm_rollout
349
+ return decorator(func)
350
+
351
+
352
+ def _validate_llm_rollout_func(func: Any) -> TypeGuard[LlmRolloutFunc[Any]]:
353
+ """Validate the function signature of an LLM rollout function.
354
+
355
+ Ensures the function follows the expected pattern for LLM-based rollouts:
356
+
357
+ - Must have at least 2 parameters
358
+ - First parameter must be named 'task'
359
+ - Must have a parameter named 'llm'
360
+ - Optionally can have a 'rollout' parameter
361
+
362
+ Args:
363
+ func: Function to inspect.
364
+
365
+ Returns:
366
+ `True` when the signature matches the supported patterns.
367
+
368
+ Raises:
369
+ ValueError: If the function signature does not match the expected pattern.
370
+ """
371
+ sig = inspect.signature(func)
372
+ params = list(sig.parameters.keys())
373
+ if len(params) < 2:
374
+ raise ValueError(f"Function {func} must have at least 2 parameters.")
375
+ if params[0] != "task":
376
+ raise ValueError(f"Function {func} must be a positional parameter called 'task'.")
377
+ if "llm" not in params:
378
+ raise ValueError(f"Function {func} must have a positional parameter called 'llm'.")
379
+
380
+ return True
381
+
382
+
383
+ @overload
384
+ def prompt_rollout(func: PromptRolloutFunc[T]) -> FunctionalLitAgent[T]: ...
385
+
386
+
387
+ @overload
388
+ def prompt_rollout() -> Callable[[PromptRolloutFunc[T]], FunctionalLitAgent[T]]: ...
389
+
390
+
391
+ def prompt_rollout(
392
+ func: PromptRolloutFunc[T] | None = None,
393
+ ) -> FunctionalLitAgent[T] | Callable[[PromptRolloutFunc[T]], FunctionalLitAgent[T]]:
394
+ """Create a [`FunctionalLitAgent`][mantisdk.litagent.decorator.FunctionalLitAgent] for prompt-based rollouts.
395
+
396
+ This decorator is designed for agents that work with tunable prompt templates. It enables
397
+ a workflow where algorithms manage and optimize the prompt template, while agents consume
398
+ the template to perform rollouts. This is particularly useful for prompt optimization scenarios.
399
+
400
+ Args:
401
+ func: Callable defining the agent's behavior. Supported signatures include:
402
+
403
+ * `(task, prompt_template) -> result`
404
+ * `(task, prompt_template, rollout) -> result`
405
+ * `async (task, prompt_template) -> result`
406
+ * `async (task, prompt_template, rollout) -> result`
407
+
408
+ Returns:
409
+ [`FunctionalLitAgent`][mantisdk.litagent.decorator.FunctionalLitAgent] that
410
+ wraps the supplied function.
411
+
412
+ Examples:
413
+ ```python
414
+ @prompt_rollout
415
+ def my_agent(task, prompt_template):
416
+ messages = prompt_template.format(task=task.input)
417
+ return messages
418
+
419
+ result = my_agent(task, prompt_template)
420
+ result = my_agent.rollout(task, resources, rollout)
421
+ ```
422
+ """
423
+
424
+ def decorator(f: PromptRolloutFunc[T]) -> FunctionalLitAgent[T]:
425
+ _validate_prompt_rollout_func(f)
426
+ return FunctionalLitAgent(f)
427
+
428
+ if func is None:
429
+ return decorator
430
+ else:
431
+ return decorator(func)
432
+
433
+
434
+ def _validate_prompt_rollout_func(func: Any) -> TypeGuard[PromptRolloutFunc[Any]]:
435
+ """Validate the function signature of a prompt rollout function.
436
+
437
+ Ensures the function follows the expected pattern for prompt-template-based rollouts:
438
+
439
+ - Must have at least 2 parameters
440
+ - First parameter must be named 'task'
441
+ - Must have a parameter named 'prompt_template'
442
+ - Optionally can have a 'rollout' parameter
443
+
444
+ Args:
445
+ func: Function to inspect.
446
+
447
+ Returns:
448
+ `True` when the signature matches the supported patterns.
449
+
450
+ Raises:
451
+ ValueError: If the function signature does not match the expected pattern.
452
+ """
453
+ sig = inspect.signature(func)
454
+ params = list(sig.parameters.keys())
455
+ if len(params) < 2:
456
+ raise ValueError(f"Function {func} must have at least 2 parameters.")
457
+ if params[0] != "task":
458
+ raise ValueError(f"Function {func} must be a positional parameter called 'task'.")
459
+ if "prompt_template" not in params:
460
+ raise ValueError(f"Function {func} must have a positional parameter called 'prompt_template'.")
461
+
462
+ return True
463
+
464
+
465
+ def rollout(func: Union[LlmRolloutFunc[T], PromptRolloutFunc[T], Callable[..., Any]]) -> FunctionalLitAgent[T]:
466
+ """Create a [`FunctionalLitAgent`][mantisdk.litagent.decorator.FunctionalLitAgent] from an arbitrary rollout function.
467
+
468
+ This function inspects the provided callable and creates the appropriate
469
+ agent type based on its signature. It supports both LLM-based and prompt-template-based
470
+ agents. The returned agent instance is callable, preserving the original function's
471
+ behavior and type hints.
472
+
473
+ See [`llm_rollout`][mantisdk.litagent.decorator.llm_rollout] and
474
+ [`prompt_rollout`][mantisdk.litagent.decorator.prompt_rollout] for more details.
475
+
476
+ Args:
477
+ func: Callable that implements the rollout. Supported signatures:
478
+
479
+ - `[async ](task, llm[, rollout])` for LLM-based agents
480
+ - `[async ](task, prompt_template[, rollout])` for prompt-template-based agents
481
+
482
+ The supported output types of `func` is same as the return type of [`rollout`][mantisdk.LitAgent.rollout].
483
+
484
+ Returns:
485
+ [`FunctionalLitAgent`][mantisdk.litagent.decorator.FunctionalLitAgent] that
486
+ wraps the supplied function.
487
+
488
+ Examples:
489
+ ```python
490
+ # LLM-based agent
491
+ @rollout
492
+ def my_llm_agent(task, llm):
493
+ client = OpenAI(base_url=llm.endpoint)
494
+ response = client.chat.completions.create(
495
+ model=llm.model,
496
+ messages=[{"role": "user", "content": task.input}],
497
+ )
498
+ return response
499
+
500
+ # Prompt-template-based agent
501
+ @rollout
502
+ def my_prompt_agent(task, prompt_template):
503
+ messages = prompt_template.format(task=task.input)
504
+ # ... perform rollout with the formatted prompt
505
+ return response
506
+
507
+ # Function is still callable with original behavior
508
+ result = my_llm_agent(task, llm)
509
+
510
+ # Agent methods are also available
511
+ result = my_llm_agent.rollout(task, resources, rollout)
512
+ ```
513
+
514
+ Raises:
515
+ NotImplementedError: If the function signature doesn't match any known patterns.
516
+ """
517
+ # Check if it matches the LLM rollout API pattern
518
+ sig = inspect.signature(func)
519
+
520
+ try:
521
+ if _validate_llm_rollout_func(func):
522
+ return llm_rollout(func)
523
+ except ValueError:
524
+ pass
525
+
526
+ try:
527
+ if _validate_prompt_rollout_func(func):
528
+ return prompt_rollout(func)
529
+ except ValueError:
530
+ pass
531
+
532
+ raise NotImplementedError(
533
+ f"Function signature {sig} does not match any known agent patterns. "
534
+ "Expected signatures: (task, llm[, rollout]) or (task, prompt_template[, rollout]). "
535
+ "Functions can be sync or async."
536
+ )