openai-sdk-helpers 0.0.5__py3-none-any.whl → 0.0.6__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 (48) hide show
  1. openai_sdk_helpers/__init__.py +62 -0
  2. openai_sdk_helpers/agent/__init__.py +31 -0
  3. openai_sdk_helpers/agent/base.py +330 -0
  4. openai_sdk_helpers/agent/config.py +66 -0
  5. openai_sdk_helpers/agent/project_manager.py +511 -0
  6. openai_sdk_helpers/agent/prompt_utils.py +9 -0
  7. openai_sdk_helpers/agent/runner.py +215 -0
  8. openai_sdk_helpers/agent/summarizer.py +85 -0
  9. openai_sdk_helpers/agent/translator.py +139 -0
  10. openai_sdk_helpers/agent/utils.py +47 -0
  11. openai_sdk_helpers/agent/validation.py +97 -0
  12. openai_sdk_helpers/agent/vector_search.py +462 -0
  13. openai_sdk_helpers/agent/web_search.py +404 -0
  14. openai_sdk_helpers/config.py +153 -0
  15. openai_sdk_helpers/enums/__init__.py +7 -0
  16. openai_sdk_helpers/enums/base.py +29 -0
  17. openai_sdk_helpers/environment.py +27 -0
  18. openai_sdk_helpers/prompt/__init__.py +77 -0
  19. openai_sdk_helpers/py.typed +0 -0
  20. openai_sdk_helpers/response/__init__.py +18 -0
  21. openai_sdk_helpers/response/base.py +501 -0
  22. openai_sdk_helpers/response/messages.py +211 -0
  23. openai_sdk_helpers/response/runner.py +104 -0
  24. openai_sdk_helpers/response/tool_call.py +70 -0
  25. openai_sdk_helpers/structure/__init__.py +43 -0
  26. openai_sdk_helpers/structure/agent_blueprint.py +224 -0
  27. openai_sdk_helpers/structure/base.py +713 -0
  28. openai_sdk_helpers/structure/plan/__init__.py +13 -0
  29. openai_sdk_helpers/structure/plan/enum.py +64 -0
  30. openai_sdk_helpers/structure/plan/plan.py +253 -0
  31. openai_sdk_helpers/structure/plan/task.py +122 -0
  32. openai_sdk_helpers/structure/prompt.py +24 -0
  33. openai_sdk_helpers/structure/responses.py +132 -0
  34. openai_sdk_helpers/structure/summary.py +65 -0
  35. openai_sdk_helpers/structure/validation.py +47 -0
  36. openai_sdk_helpers/structure/vector_search.py +86 -0
  37. openai_sdk_helpers/structure/web_search.py +46 -0
  38. openai_sdk_helpers/utils/__init__.py +13 -0
  39. openai_sdk_helpers/utils/core.py +208 -0
  40. openai_sdk_helpers/vector_storage/__init__.py +15 -0
  41. openai_sdk_helpers/vector_storage/cleanup.py +91 -0
  42. openai_sdk_helpers/vector_storage/storage.py +501 -0
  43. openai_sdk_helpers/vector_storage/types.py +58 -0
  44. {openai_sdk_helpers-0.0.5.dist-info → openai_sdk_helpers-0.0.6.dist-info}/METADATA +1 -1
  45. openai_sdk_helpers-0.0.6.dist-info/RECORD +50 -0
  46. openai_sdk_helpers-0.0.5.dist-info/RECORD +0 -7
  47. {openai_sdk_helpers-0.0.5.dist-info → openai_sdk_helpers-0.0.6.dist-info}/WHEEL +0 -0
  48. {openai_sdk_helpers-0.0.5.dist-info → openai_sdk_helpers-0.0.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,404 @@
1
+ """Core workflow management for ``web search``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Union
8
+
9
+ from agents import custom_span, gen_trace_id, trace
10
+ from agents.model_settings import ModelSettings
11
+ from agents.tool import WebSearchTool
12
+
13
+ from ..structure.web_search import (
14
+ WebSearchItemStructure,
15
+ WebSearchItemResultStructure,
16
+ WebSearchStructure,
17
+ WebSearchPlanStructure,
18
+ WebSearchReportStructure,
19
+ )
20
+ from .base import AgentBase
21
+ from .config import AgentConfig
22
+ from .utils import run_coroutine_agent_sync
23
+
24
+ MAX_CONCURRENT_SEARCHES = 10
25
+
26
+
27
+ class WebAgentPlanner(AgentBase):
28
+ """Plan web searches to satisfy a user query.
29
+
30
+ Methods
31
+ -------
32
+ run_agent(query)
33
+ Generate a search plan for the provided query.
34
+ """
35
+
36
+ def __init__(
37
+ self, prompt_dir: Optional[Path] = None, default_model: Optional[str] = None
38
+ ) -> None:
39
+ """Initialize the planner agent.
40
+
41
+ Parameters
42
+ ----------
43
+ prompt_dir : pathlib.Path or None, default=None
44
+ Directory containing prompt templates.
45
+ default_model : str or None, default=None
46
+ Default model identifier to use when not defined in config.
47
+
48
+ Returns
49
+ -------
50
+ None
51
+ """
52
+ config = AgentConfig(
53
+ name="web_planner",
54
+ description="Agent that plans web searches based on a user query.",
55
+ output_type=WebSearchPlanStructure,
56
+ )
57
+ super().__init__(
58
+ config=config, prompt_dir=prompt_dir, default_model=default_model
59
+ )
60
+
61
+ async def run_agent(self, query: str) -> WebSearchPlanStructure:
62
+ """Plan searches for ``query``.
63
+
64
+ Parameters
65
+ ----------
66
+ query : str
67
+ User search query.
68
+
69
+ Returns
70
+ -------
71
+ WebSearchPlanStructure
72
+ Plan describing searches to perform.
73
+ """
74
+ result: WebSearchPlanStructure = await self.run_async(
75
+ input=query,
76
+ output_type=self._output_type,
77
+ )
78
+
79
+ return result
80
+
81
+
82
+ class WebSearchToolAgent(AgentBase):
83
+ """Execute web searches defined in a plan.
84
+
85
+ Methods
86
+ -------
87
+ run_agent(search_plan)
88
+ Execute searches described by the plan.
89
+ run_search(item)
90
+ Perform a single web search and summarise the result.
91
+ """
92
+
93
+ def __init__(
94
+ self, prompt_dir: Optional[Path] = None, default_model: Optional[str] = None
95
+ ) -> None:
96
+ """Initialize the search tool agent.
97
+
98
+ Parameters
99
+ ----------
100
+ prompt_dir : pathlib.Path or None, default=None
101
+ Directory containing prompt templates.
102
+ default_model : str or None, default=None
103
+ Default model identifier to use when not defined in config.
104
+
105
+ Returns
106
+ -------
107
+ None
108
+ """
109
+ config = AgentConfig(
110
+ name="web_search",
111
+ description="Agent that performs web searches and summarizes results.",
112
+ input_type=WebSearchPlanStructure,
113
+ tools=[WebSearchTool()],
114
+ model_settings=ModelSettings(tool_choice="required"),
115
+ )
116
+ super().__init__(
117
+ config=config, prompt_dir=prompt_dir, default_model=default_model
118
+ )
119
+
120
+ async def run_agent(
121
+ self, search_plan: WebSearchPlanStructure
122
+ ) -> List[WebSearchItemResultStructure]:
123
+ """Execute all searches in the plan with a progress bar.
124
+
125
+ Parameters
126
+ ----------
127
+ search_plan : WebSearchPlanStructure
128
+ Plan describing each search to perform.
129
+
130
+ Returns
131
+ -------
132
+ list[WebSearchItemResultStructure]
133
+ Completed search results.
134
+ """
135
+ with custom_span("Search the web"):
136
+ semaphore = asyncio.Semaphore(MAX_CONCURRENT_SEARCHES)
137
+
138
+ async def _bounded_search(
139
+ item: WebSearchItemStructure,
140
+ ) -> WebSearchItemResultStructure:
141
+ """Execute a single search within the concurrency limit.
142
+
143
+ Parameters
144
+ ----------
145
+ item : WebSearchItemStructure
146
+ Search item to process.
147
+
148
+ Returns
149
+ -------
150
+ WebSearchItemResultStructure
151
+ Search result for ``item``.
152
+ """
153
+ async with semaphore:
154
+ return await self.run_search(item)
155
+
156
+ tasks = [
157
+ asyncio.create_task(_bounded_search(item))
158
+ for item in search_plan.searches
159
+ ]
160
+ results = await asyncio.gather(*tasks)
161
+ return [result for result in results if result is not None]
162
+
163
+ async def run_search(
164
+ self, item: WebSearchItemStructure
165
+ ) -> WebSearchItemResultStructure:
166
+ """Perform a single web search using the search agent.
167
+
168
+ Parameters
169
+ ----------
170
+ item : WebSearchItemStructure
171
+ Search item containing the query and reason.
172
+
173
+ Returns
174
+ -------
175
+ WebSearchItemResultStructure
176
+ Search result summarising the page.
177
+ """
178
+ template_context: Dict[str, Any] = {
179
+ "search_term": item.query,
180
+ "reason": item.reason,
181
+ }
182
+
183
+ result = await super().run_async(
184
+ input=item.query,
185
+ context=template_context,
186
+ output_type=str,
187
+ )
188
+ return self._coerce_item_result(result)
189
+
190
+ @staticmethod
191
+ def _coerce_item_result(
192
+ result: Union[str, WebSearchItemResultStructure, Any],
193
+ ) -> WebSearchItemResultStructure:
194
+ """Return a WebSearchItemResultStructure from varied agent outputs."""
195
+ if isinstance(result, WebSearchItemResultStructure):
196
+ return result
197
+ try:
198
+ return WebSearchItemResultStructure(text=str(result))
199
+ except Exception:
200
+ return WebSearchItemResultStructure(text="")
201
+
202
+
203
+ class WebAgentWriter(AgentBase):
204
+ """Summarize search results into a human-readable report.
205
+
206
+ Methods
207
+ -------
208
+ run_agent(query, search_results)
209
+ Compile a report from search results.
210
+ """
211
+
212
+ def __init__(
213
+ self, prompt_dir: Optional[Path] = None, default_model: Optional[str] = None
214
+ ) -> None:
215
+ """Initialize the writer agent.
216
+
217
+ Parameters
218
+ ----------
219
+ prompt_dir : pathlib.Path or None, default=None
220
+ Directory containing prompt templates.
221
+ default_model : str or None, default=None
222
+ Default model identifier to use when not defined in config.
223
+
224
+ Returns
225
+ -------
226
+ None
227
+ """
228
+ config = AgentConfig(
229
+ name="web_writer",
230
+ description="Agent that writes a report based on web search results.",
231
+ output_type=WebSearchReportStructure,
232
+ )
233
+ super().__init__(
234
+ config=config, prompt_dir=prompt_dir, default_model=default_model
235
+ )
236
+
237
+ async def run_agent(
238
+ self, query: str, search_results: List[WebSearchItemResultStructure]
239
+ ) -> WebSearchReportStructure:
240
+ """Compile a report from search results.
241
+
242
+ Parameters
243
+ ----------
244
+ query : str
245
+ Original search query.
246
+ search_results : list[WebSearchItemResultStructure]
247
+ Results produced by the search step.
248
+
249
+ Returns
250
+ -------
251
+ WebSearchReportStructure
252
+ Generated report for the query.
253
+ """
254
+ template_context: Dict[str, Any] = {
255
+ "original_query": query,
256
+ "search_results": search_results,
257
+ }
258
+ result: WebSearchReportStructure = await self.run_async(
259
+ input=query,
260
+ context=template_context,
261
+ output_type=self._output_type,
262
+ )
263
+
264
+ return result
265
+
266
+
267
+ class WebAgentSearch(AgentBase):
268
+ """Manage the complete web search workflow.
269
+
270
+ Methods
271
+ -------
272
+ run_agent(search_query)
273
+ Execute the research workflow asynchronously.
274
+ run_agent_sync(search_query)
275
+ Execute the research workflow synchronously.
276
+ run_web_agent(search_query)
277
+ Convenience asynchronous entry point for the workflow.
278
+ run_web_agent_sync(search_query)
279
+ Convenience synchronous entry point for the workflow.
280
+ """
281
+
282
+ def __init__(
283
+ self,
284
+ config: Optional[AgentConfig] = None,
285
+ prompt_dir: Optional[Path] = None,
286
+ default_model: Optional[str] = None,
287
+ ) -> None:
288
+ """Create the main web search agent.
289
+
290
+ Parameters
291
+ ----------
292
+ config : AgentConfig or None, default=None
293
+ Optional configuration for the agent.
294
+ prompt_dir : pathlib.Path or None, default=None
295
+ Directory containing prompt templates.
296
+ default_model : str or None, default=None
297
+ Default model identifier to use when not defined in config.
298
+
299
+ Returns
300
+ -------
301
+ None
302
+ """
303
+ if config is None:
304
+ config = AgentConfig(
305
+ name="web_agent",
306
+ description="Agent that coordinates web searches and report writing.",
307
+ output_type=WebSearchStructure,
308
+ )
309
+ super().__init__(
310
+ config=config, prompt_dir=prompt_dir, default_model=default_model
311
+ )
312
+ self._prompt_dir = prompt_dir
313
+
314
+ async def run_agent(self, search_query: str) -> WebSearchStructure:
315
+ """Execute the entire research workflow for ``search_query``.
316
+
317
+ Parameters
318
+ ----------
319
+ search_query : str
320
+ User's research query.
321
+
322
+ Returns
323
+ -------
324
+ WebSearchStructure
325
+ Completed research output.
326
+ """
327
+ trace_id = gen_trace_id()
328
+ with trace("WebAgentSearch trace", trace_id=trace_id):
329
+ planner = WebAgentPlanner(
330
+ prompt_dir=self._prompt_dir, default_model=self.model
331
+ )
332
+ tool = WebSearchToolAgent(
333
+ prompt_dir=self._prompt_dir, default_model=self.model
334
+ )
335
+ writer = WebAgentWriter(
336
+ prompt_dir=self._prompt_dir, default_model=self.model
337
+ )
338
+ search_plan = await planner.run_agent(query=search_query)
339
+ search_results = await tool.run_agent(search_plan=search_plan)
340
+ search_report = await writer.run_agent(search_query, search_results)
341
+ return WebSearchStructure(
342
+ query=search_query,
343
+ web_search_plan=search_plan,
344
+ web_search_results=search_results,
345
+ web_search_report=search_report,
346
+ )
347
+
348
+ def run_agent_sync(self, search_query: str) -> WebSearchStructure:
349
+ """Run :meth:`run_agent` synchronously for ``search_query``.
350
+
351
+ Parameters
352
+ ----------
353
+ search_query : str
354
+ User's research query.
355
+
356
+ Returns
357
+ -------
358
+ WebSearchStructure
359
+ Completed research output.
360
+ """
361
+ return run_coroutine_agent_sync(self.run_agent(search_query))
362
+
363
+ @staticmethod
364
+ async def run_web_agent(search_query: str) -> WebSearchStructure:
365
+ """Return a research report for the given query using ``WebAgentSearch``.
366
+
367
+ Parameters
368
+ ----------
369
+ search_query : str
370
+ User's research query.
371
+
372
+ Returns
373
+ -------
374
+ WebSearchStructure
375
+ Completed research output.
376
+ """
377
+ return await WebAgentSearch().run_agent(search_query=search_query)
378
+
379
+ @staticmethod
380
+ def run_web_agent_sync(search_query: str) -> WebSearchStructure:
381
+ """Run :meth:`run_web_agent` synchronously for ``search_query``.
382
+
383
+ Parameters
384
+ ----------
385
+ search_query : str
386
+ User's research query.
387
+
388
+ Returns
389
+ -------
390
+ WebSearchStructure
391
+ Completed research output.
392
+ """
393
+ return run_coroutine_agent_sync(
394
+ WebAgentSearch.run_web_agent(search_query=search_query)
395
+ )
396
+
397
+
398
+ __all__ = [
399
+ "MAX_CONCURRENT_SEARCHES",
400
+ "WebAgentPlanner",
401
+ "WebSearchToolAgent",
402
+ "WebAgentWriter",
403
+ "WebAgentSearch",
404
+ ]
@@ -0,0 +1,153 @@
1
+ """Shared configuration for OpenAI SDK usage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any, Dict, Mapping, Optional
8
+
9
+ from dotenv import dotenv_values
10
+ from openai import OpenAI
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+
14
+ class OpenAISettings(BaseModel):
15
+ """Configuration helpers for constructing OpenAI clients.
16
+
17
+ Methods
18
+ -------
19
+ from_env(dotenv_path, **overrides)
20
+ Build settings from environment variables and optional overrides.
21
+ client_kwargs()
22
+ Return keyword arguments for ``OpenAI`` initialization.
23
+ create_client()
24
+ Instantiate an ``OpenAI`` client using the stored configuration.
25
+ """
26
+
27
+ model_config = ConfigDict(extra="ignore")
28
+
29
+ api_key: Optional[str] = Field(
30
+ default=None,
31
+ description=(
32
+ "API key used to authenticate requests. Defaults to ``OPENAI_API_KEY``"
33
+ " from the environment."
34
+ ),
35
+ )
36
+ org_id: Optional[str] = Field(
37
+ default=None,
38
+ description=(
39
+ "Organization identifier applied to outgoing requests. Defaults to"
40
+ " ``OPENAI_ORG_ID``."
41
+ ),
42
+ )
43
+ project_id: Optional[str] = Field(
44
+ default=None,
45
+ description=(
46
+ "Project identifier used for billing and resource scoping. Defaults to"
47
+ " ``OPENAI_PROJECT_ID``."
48
+ ),
49
+ )
50
+ base_url: Optional[str] = Field(
51
+ default=None,
52
+ description=(
53
+ "Custom base URL for self-hosted or proxied deployments. Defaults to"
54
+ " ``OPENAI_BASE_URL``."
55
+ ),
56
+ )
57
+ default_model: Optional[str] = Field(
58
+ default=None,
59
+ description=(
60
+ "Model name used when constructing agents if no model is explicitly"
61
+ " provided. Defaults to ``OPENAI_MODEL``."
62
+ ),
63
+ )
64
+
65
+ @classmethod
66
+ def from_env(
67
+ cls, dotenv_path: Optional[Path] = None, **overrides: Any
68
+ ) -> "OpenAISettings":
69
+ """Load settings from the environment and optional overrides.
70
+
71
+ Parameters
72
+ ----------
73
+ dotenv_path : Path | None
74
+ Path to a ``.env`` file to load before reading environment
75
+ variables. Default ``None``.
76
+ overrides : Any
77
+ Keyword overrides applied on top of environment values.
78
+
79
+ Returns
80
+ -------
81
+ OpenAISettings
82
+ Settings instance populated from environment variables and overrides.
83
+ """
84
+ env_file_values: Mapping[str, Optional[str]]
85
+ if dotenv_path is not None:
86
+ env_file_values = dotenv_values(dotenv_path)
87
+ else:
88
+ env_file_values = dotenv_values()
89
+
90
+ values: Dict[str, Optional[str]] = {
91
+ "api_key": overrides.get("api_key")
92
+ or env_file_values.get("OPENAI_API_KEY")
93
+ or os.getenv("OPENAI_API_KEY"),
94
+ "org_id": overrides.get("org_id")
95
+ or env_file_values.get("OPENAI_ORG_ID")
96
+ or os.getenv("OPENAI_ORG_ID"),
97
+ "project_id": overrides.get("project_id")
98
+ or env_file_values.get("OPENAI_PROJECT_ID")
99
+ or os.getenv("OPENAI_PROJECT_ID"),
100
+ "base_url": overrides.get("base_url")
101
+ or env_file_values.get("OPENAI_BASE_URL")
102
+ or os.getenv("OPENAI_BASE_URL"),
103
+ "default_model": overrides.get("default_model")
104
+ or env_file_values.get("OPENAI_MODEL")
105
+ or os.getenv("OPENAI_MODEL"),
106
+ }
107
+
108
+ settings = cls(**values)
109
+ if not settings.api_key:
110
+ source_hint = (
111
+ f" from {dotenv_path}"
112
+ if dotenv_path is not None
113
+ else " from environment"
114
+ )
115
+ raise ValueError(
116
+ "OPENAI_API_KEY is required to configure the OpenAI client"
117
+ f" and was not found{source_hint}."
118
+ )
119
+
120
+ return settings
121
+
122
+ def client_kwargs(self) -> Dict[str, Any]:
123
+ """Return keyword arguments for constructing an ``OpenAI`` client.
124
+
125
+ Returns
126
+ -------
127
+ dict
128
+ Keyword arguments populated with available authentication and routing
129
+ values.
130
+ """
131
+ kwargs: Dict[str, Any] = {}
132
+ if self.api_key:
133
+ kwargs["api_key"] = self.api_key
134
+ if self.org_id:
135
+ kwargs["organization"] = self.org_id
136
+ if self.project_id:
137
+ kwargs["project"] = self.project_id
138
+ if self.base_url:
139
+ kwargs["base_url"] = self.base_url
140
+ return kwargs
141
+
142
+ def create_client(self) -> OpenAI:
143
+ """Instantiate an ``OpenAI`` client using the stored configuration.
144
+
145
+ Returns
146
+ -------
147
+ OpenAI
148
+ Client initialized with ``client_kwargs``.
149
+ """
150
+ return OpenAI(**self.client_kwargs())
151
+
152
+
153
+ __all__ = ["OpenAISettings"]
@@ -0,0 +1,7 @@
1
+ """Enum helpers for openai-sdk-helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import CrosswalkJSONEnum
6
+
7
+ __all__ = ["CrosswalkJSONEnum"]
@@ -0,0 +1,29 @@
1
+ """Base enum helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class CrosswalkJSONEnum(str, Enum):
9
+ """Enum base class with crosswalk metadata hooks.
10
+
11
+ Methods
12
+ -------
13
+ CROSSWALK()
14
+ Return metadata describing enum values keyed by name.
15
+ """
16
+
17
+ @classmethod
18
+ def CROSSWALK(cls) -> dict[str, dict[str, object]]:
19
+ """Return metadata describing enum values keyed by name.
20
+
21
+ Returns
22
+ -------
23
+ dict[str, dict[str, object]]
24
+ Mapping of enum member names to structured metadata details.
25
+ """
26
+ raise NotImplementedError("CROSSWALK must be implemented by subclasses.")
27
+
28
+
29
+ __all__ = ["CrosswalkJSONEnum"]
@@ -0,0 +1,27 @@
1
+ """Environment helpers for openai-sdk-helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ DATETIME_FMT = "%Y%m%d_%H%M%S"
8
+
9
+
10
+ def get_data_path(module_name: str) -> Path:
11
+ """Return a writable data directory for the given module name.
12
+
13
+ Parameters
14
+ ----------
15
+ module_name : str
16
+ Name of the module requesting a data directory.
17
+
18
+ Returns
19
+ -------
20
+ Path
21
+ Directory path under ``~/.openai-sdk-helpers`` specific to ``module_name``. The
22
+ directory is created if it does not already exist.
23
+ """
24
+ base = Path.home() / ".openai-sdk-helpers"
25
+ path = base / module_name
26
+ path.mkdir(parents=True, exist_ok=True)
27
+ return path
@@ -0,0 +1,77 @@
1
+ """Core prompt rendering utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from pathlib import Path
7
+ from typing import Any, Mapping, Optional
8
+
9
+ from dotenv import load_dotenv
10
+ from jinja2 import Environment, FileSystemLoader, Template
11
+
12
+ load_dotenv()
13
+ warnings.filterwarnings("ignore")
14
+
15
+
16
+ class PromptRenderer:
17
+ """Render prompts using Jinja2 templates.
18
+
19
+ The renderer loads templates from a base directory (defaulting to the
20
+ ``prompt`` package directory) and exposes a rendering helper for
21
+ injecting context values.
22
+
23
+ Methods
24
+ -------
25
+ render(template_path, context)
26
+ Render the template at ``template_path`` with the supplied context.
27
+ """
28
+
29
+ def __init__(self, base_dir: Optional[Path] = None) -> None:
30
+ """Initialize the renderer with a Jinja2 environment.
31
+
32
+ Parameters
33
+ ----------
34
+ base_dir : Path or None, default=None
35
+ Base directory containing Jinja2 templates. Defaults to the
36
+ ``prompt`` directory adjacent to this file when ``None``.
37
+
38
+ Returns
39
+ -------
40
+ None
41
+ """
42
+ if base_dir is None:
43
+ # Defaults to the directory containing this file, which also
44
+ # contains the builtin prompt templates.
45
+ self.base_dir = Path(__file__).resolve().parent
46
+ else:
47
+ self.base_dir = base_dir
48
+
49
+ self._env = Environment(
50
+ loader=FileSystemLoader(str(self.base_dir)),
51
+ autoescape=False, # Prompts are plain text
52
+ )
53
+
54
+ def render(
55
+ self, template_path: str, context: Optional[Mapping[str, Any]] = None
56
+ ) -> str:
57
+ """Render a Jinja2 template with the given context.
58
+
59
+ Parameters
60
+ ----------
61
+ template_path : str
62
+ Path to the template file, relative to ``base_dir``.
63
+ context : Mapping[str, Any] or None, default=None
64
+ Context variables passed to the template.
65
+
66
+ Returns
67
+ -------
68
+ str
69
+ Rendered prompt as a string.
70
+ """
71
+ template_path_ = Path(self.base_dir, template_path)
72
+ template_path_text = template_path_.read_text()
73
+ template = Template(template_path_text)
74
+ return template.render(context or {})
75
+
76
+
77
+ __all__ = ["PromptRenderer"]
File without changes