openai-sdk-helpers 0.0.4__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 (51) 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/prompt/summarizer.jinja +7 -0
  20. openai_sdk_helpers/prompt/translator.jinja +7 -0
  21. openai_sdk_helpers/prompt/validator.jinja +7 -0
  22. openai_sdk_helpers/py.typed +0 -0
  23. openai_sdk_helpers/response/__init__.py +18 -0
  24. openai_sdk_helpers/response/base.py +501 -0
  25. openai_sdk_helpers/response/messages.py +211 -0
  26. openai_sdk_helpers/response/runner.py +104 -0
  27. openai_sdk_helpers/response/tool_call.py +70 -0
  28. openai_sdk_helpers/structure/__init__.py +43 -0
  29. openai_sdk_helpers/structure/agent_blueprint.py +224 -0
  30. openai_sdk_helpers/structure/base.py +713 -0
  31. openai_sdk_helpers/structure/plan/__init__.py +13 -0
  32. openai_sdk_helpers/structure/plan/enum.py +64 -0
  33. openai_sdk_helpers/structure/plan/plan.py +253 -0
  34. openai_sdk_helpers/structure/plan/task.py +122 -0
  35. openai_sdk_helpers/structure/prompt.py +24 -0
  36. openai_sdk_helpers/structure/responses.py +132 -0
  37. openai_sdk_helpers/structure/summary.py +65 -0
  38. openai_sdk_helpers/structure/validation.py +47 -0
  39. openai_sdk_helpers/structure/vector_search.py +86 -0
  40. openai_sdk_helpers/structure/web_search.py +46 -0
  41. openai_sdk_helpers/utils/__init__.py +13 -0
  42. openai_sdk_helpers/utils/core.py +208 -0
  43. openai_sdk_helpers/vector_storage/__init__.py +15 -0
  44. openai_sdk_helpers/vector_storage/cleanup.py +91 -0
  45. openai_sdk_helpers/vector_storage/storage.py +501 -0
  46. openai_sdk_helpers/vector_storage/types.py +58 -0
  47. {openai_sdk_helpers-0.0.4.dist-info → openai_sdk_helpers-0.0.6.dist-info}/METADATA +1 -1
  48. openai_sdk_helpers-0.0.6.dist-info/RECORD +50 -0
  49. openai_sdk_helpers-0.0.4.dist-info/RECORD +0 -4
  50. {openai_sdk_helpers-0.0.4.dist-info → openai_sdk_helpers-0.0.6.dist-info}/WHEEL +0 -0
  51. {openai_sdk_helpers-0.0.4.dist-info → openai_sdk_helpers-0.0.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,13 @@
1
+ """Structured output models for agent tasks and plans."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .plan import PlanStructure
6
+ from .task import TaskStructure
7
+ from .enum import AgentEnum
8
+
9
+ __all__ = [
10
+ "PlanStructure",
11
+ "TaskStructure",
12
+ "AgentEnum",
13
+ ]
@@ -0,0 +1,64 @@
1
+ """Agent task enumeration definitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ...enums.base import CrosswalkJSONEnum
8
+
9
+
10
+ class AgentEnum(CrosswalkJSONEnum):
11
+ """Auto-generated enumeration for AgentEnum.
12
+
13
+ Methods
14
+ -------
15
+ CROSSWALK()
16
+ Return the raw crosswalk data for this enum.
17
+ """
18
+
19
+ WEB_SEARCH = "WebAgentSearch"
20
+ VECTOR_SEARCH = "VectorSearch"
21
+ DATA_ANALYST = "DataAnalyst"
22
+ SUMMARIZER = "SummarizerAgent"
23
+ TRANSLATOR = "TranslatorAgent"
24
+ VALIDATOR = "ValidatorAgent"
25
+ PLANNER = "MetaPlanner"
26
+ DESIGNER = "AgentDesigner"
27
+ BUILDER = "AgentBuilder"
28
+ EVALUATOR = "EvaluationAgent"
29
+ RELEASE_MANAGER = "ReleaseManager"
30
+
31
+ @classmethod
32
+ def CROSSWALK(cls) -> dict[str, dict[str, Any]]:
33
+ """Return the raw crosswalk data for this enum.
34
+
35
+ Returns
36
+ -------
37
+ dict[str, dict[str, Any]]
38
+ Crosswalk mapping keyed by enum member.
39
+
40
+ Raises
41
+ ------
42
+ None
43
+
44
+ Examples
45
+ --------
46
+ >>> AgentEnum.CROSSWALK()["WEB_SEARCH"]["value"]
47
+ 'WebAgentSearch'
48
+ """
49
+ return {
50
+ "WEB_SEARCH": {"value": "WebAgentSearch"},
51
+ "VECTOR_SEARCH": {"value": "VectorSearch"},
52
+ "DATA_ANALYST": {"value": "DataAnalyst"},
53
+ "SUMMARIZER": {"value": "SummarizerAgent"},
54
+ "TRANSLATOR": {"value": "TranslatorAgent"},
55
+ "VALIDATOR": {"value": "ValidatorAgent"},
56
+ "PLANNER": {"value": "MetaPlanner"},
57
+ "DESIGNER": {"value": "AgentDesigner"},
58
+ "BUILDER": {"value": "AgentBuilder"},
59
+ "EVALUATOR": {"value": "EvaluationAgent"},
60
+ "RELEASE_MANAGER": {"value": "ReleaseManager"},
61
+ }
62
+
63
+
64
+ __all__ = ["AgentEnum"]
@@ -0,0 +1,253 @@
1
+ """Structured output model for agent plans."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import inspect
7
+ import threading
8
+ from datetime import datetime, timezone
9
+ from typing import Any, Callable, Dict, List, Mapping
10
+
11
+ from .enum import AgentEnum
12
+ from ..base import BaseStructure, spec_field
13
+ from .task import TaskStructure
14
+
15
+
16
+ class PlanStructure(BaseStructure):
17
+ """Structured representation of an ordered list of agent tasks.
18
+
19
+ Methods
20
+ -------
21
+ print()
22
+ Return a formatted description of every task in order.
23
+ __len__()
24
+ Return the count of tasks in the plan.
25
+ append(task)
26
+ Append an ``TaskStructure`` to the plan.
27
+ execute(agent_registry, halt_on_error)
28
+ Run tasks sequentially using the provided agent callables.
29
+ """
30
+
31
+ tasks: List[TaskStructure] = spec_field(
32
+ "tasks",
33
+ default_factory=list,
34
+ description="Ordered list of agent tasks to execute.",
35
+ )
36
+
37
+ def print(self) -> str:
38
+ """Return a human-readable representation of the plan.
39
+
40
+ Parameters
41
+ ----------
42
+ None
43
+
44
+ Returns
45
+ -------
46
+ str
47
+ Concatenated description of each plan step.
48
+
49
+ Raises
50
+ ------
51
+ None
52
+
53
+ Examples
54
+ --------
55
+ >>> PlanStructure().print()
56
+ 'No tasks defined.'
57
+ """
58
+ if not self.tasks:
59
+ return "No tasks defined."
60
+ return "\n\n".join(
61
+ [f"Task {idx + 1}:\n{task.print()}" for idx, task in enumerate(self.tasks)]
62
+ )
63
+
64
+ def __len__(self) -> int:
65
+ """Return the number of tasks contained in the plan.
66
+
67
+ Parameters
68
+ ----------
69
+ None
70
+
71
+ Returns
72
+ -------
73
+ int
74
+ Count of stored agent tasks.
75
+
76
+ Raises
77
+ ------
78
+ None
79
+
80
+ Examples
81
+ --------
82
+ >>> len(PlanStructure())
83
+ 0
84
+ """
85
+ return len(self.tasks)
86
+
87
+ def append(self, task: TaskStructure) -> None:
88
+ """Add a task to the plan in execution order.
89
+
90
+ Parameters
91
+ ----------
92
+ task : TaskStructure
93
+ Task to append to the plan.
94
+
95
+ Returns
96
+ -------
97
+ None
98
+
99
+ Raises
100
+ ------
101
+ None
102
+
103
+ Examples
104
+ --------
105
+ >>> plan = PlanStructure()
106
+ >>> plan.append(TaskStructure(prompt="Test")) # doctest: +SKIP
107
+ """
108
+ self.tasks.append(task)
109
+
110
+ def execute(
111
+ self,
112
+ agent_registry: Mapping[AgentEnum | str, Callable[..., Any]],
113
+ *,
114
+ halt_on_error: bool = True,
115
+ ) -> list[str]:
116
+ """Execute tasks with registered agent callables and record outputs.
117
+
118
+ Parameters
119
+ ----------
120
+ agent_registry : Mapping[AgentEnum | str, Callable[..., Any]]
121
+ Lookup of agent identifiers to callables. Keys may be ``AgentEnum``
122
+ instances or their string values. Each callable receives the task
123
+ prompt (augmented with prior context) and an optional ``context``
124
+ keyword containing accumulated results.
125
+ halt_on_error : bool, default=True
126
+ Whether execution should stop when a task raises an exception.
127
+
128
+ Returns
129
+ -------
130
+ list[str]
131
+ Flattened list of normalized outputs from executed tasks.
132
+
133
+ Raises
134
+ ------
135
+ KeyError
136
+ If a task does not have a corresponding callable in
137
+ ``agent_registry``.
138
+ """
139
+ aggregated_results: list[str] = []
140
+ for task in self.tasks:
141
+ callable_key = self._resolve_registry_key(task.task_type)
142
+ if callable_key not in agent_registry:
143
+ raise KeyError(f"No agent registered for '{callable_key}'.")
144
+
145
+ agent_callable = agent_registry[callable_key]
146
+ task.start_date = datetime.now(timezone.utc)
147
+ task.status = "running"
148
+
149
+ try:
150
+ result = self._run_task(
151
+ task,
152
+ agent_callable=agent_callable,
153
+ aggregated_context=list(aggregated_results),
154
+ )
155
+ except Exception as exc: # pragma: no cover - defensive guard
156
+ task.status = "error"
157
+ task.results = [f"Task error: {exc}"]
158
+ task.end_date = datetime.now(timezone.utc)
159
+ if halt_on_error:
160
+ break
161
+ aggregated_results.extend(task.results)
162
+ continue
163
+
164
+ normalized = self._normalize_results(result)
165
+ task.results = normalized
166
+ aggregated_results.extend(normalized)
167
+ task.status = "done"
168
+ task.end_date = datetime.now(timezone.utc)
169
+
170
+ return aggregated_results
171
+
172
+ @staticmethod
173
+ def _resolve_registry_key(task_type: AgentEnum | str) -> str:
174
+ """Return a normalized registry key for the given ``task_type``."""
175
+ if isinstance(task_type, AgentEnum):
176
+ return task_type.value
177
+ if task_type in AgentEnum.__members__:
178
+ return AgentEnum.__members__[task_type].value
179
+ try:
180
+ return AgentEnum(task_type).value
181
+ except ValueError:
182
+ return str(task_type)
183
+
184
+ @staticmethod
185
+ def _run_task(
186
+ task: TaskStructure,
187
+ *,
188
+ agent_callable: Callable[..., Any],
189
+ aggregated_context: list[str],
190
+ ) -> Any:
191
+ """Execute a single task using the supplied callable.
192
+
193
+ Parameters
194
+ ----------
195
+ task : TaskStructure
196
+ Task definition containing inputs and metadata.
197
+ agent_callable : Callable[..., Any]
198
+ Function responsible for performing the task.
199
+ aggregated_context : list[str]
200
+ Accumulated results from previously executed tasks.
201
+
202
+ Returns
203
+ -------
204
+ Any
205
+ Raw output from the callable.
206
+ """
207
+ task_context = list(task.context or [])
208
+ combined_context = task_context + list(aggregated_context)
209
+
210
+ prompt_with_context = task.prompt
211
+ if combined_context:
212
+ context_block = "\n".join(combined_context)
213
+ prompt_with_context = f"{task.prompt}\n\nContext:\n{context_block}"
214
+
215
+ try:
216
+ return agent_callable(prompt_with_context, context=combined_context)
217
+ except TypeError:
218
+ return agent_callable(prompt_with_context)
219
+
220
+ @staticmethod
221
+ def _normalize_results(result: Any) -> list[str]:
222
+ """Convert callable outputs into a list of strings."""
223
+ if result is None:
224
+ return []
225
+ if inspect.isawaitable(result):
226
+ return PlanStructure._normalize_results(PlanStructure._await_result(result))
227
+ if isinstance(result, list):
228
+ return [str(item) for item in result]
229
+ return [str(result)]
230
+
231
+ @staticmethod
232
+ def _await_result(result: Any) -> Any:
233
+ """Await the provided result, handling running event loops."""
234
+ try:
235
+ loop = asyncio.get_running_loop()
236
+ except RuntimeError:
237
+ return asyncio.run(result)
238
+
239
+ if loop.is_running():
240
+ container: Dict[str, Any] = {"value": None}
241
+
242
+ def _runner() -> None:
243
+ container["value"] = asyncio.run(result)
244
+
245
+ thread = threading.Thread(target=_runner, daemon=True)
246
+ thread.start()
247
+ thread.join()
248
+ return container["value"]
249
+
250
+ return loop.run_until_complete(result)
251
+
252
+
253
+ __all__ = ["PlanStructure"]
@@ -0,0 +1,122 @@
1
+ """Structured output model for agent tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import List, Literal, Optional
7
+
8
+ from pydantic import field_validator
9
+
10
+ from .enum import AgentEnum
11
+ from ..base import BaseStructure, spec_field
12
+
13
+
14
+ class TaskStructure(BaseStructure):
15
+ """Structured representation of a single agent task.
16
+
17
+ Methods
18
+ -------
19
+ print()
20
+ Return a formatted multi-line description of the task.
21
+ """
22
+
23
+ task_type: AgentEnum = spec_field(
24
+ "task_type",
25
+ default=AgentEnum.WEB_SEARCH,
26
+ description="Agent type responsible for executing the task.",
27
+ )
28
+ prompt: str = spec_field(
29
+ "prompt",
30
+ description="Input passed to the agent.",
31
+ examples=["Research the latest trends in AI-assisted data analysis."],
32
+ )
33
+ context: List[str] | None = spec_field(
34
+ "context",
35
+ default_factory=list,
36
+ description="Additional context forwarded to the agent callable.",
37
+ )
38
+ start_date: Optional[datetime] = spec_field(
39
+ "start_date",
40
+ default=None,
41
+ description="Timestamp marking when the task started (UTC).",
42
+ )
43
+ end_date: Optional[datetime] = spec_field(
44
+ "end_date",
45
+ default=None,
46
+ description="Timestamp marking when the task completed (UTC).",
47
+ )
48
+ status: Literal["waiting", "running", "done", "error"] = spec_field(
49
+ "status",
50
+ default="waiting",
51
+ description="Current lifecycle state for the task.",
52
+ )
53
+ results: List[str] = spec_field(
54
+ "results",
55
+ default_factory=list,
56
+ description="Normalized string outputs returned by the agent.",
57
+ )
58
+
59
+ @field_validator("task_type", mode="before")
60
+ @classmethod
61
+ def _coerce_task_type(cls, value: AgentEnum | str) -> AgentEnum:
62
+ """Coerce string inputs into ``AgentEnum`` values.
63
+
64
+ Parameters
65
+ ----------
66
+ value : AgentEnum | str
67
+ Enum instance or enum value string.
68
+
69
+ Returns
70
+ -------
71
+ AgentEnum
72
+ Parsed enum instance.
73
+
74
+ Raises
75
+ ------
76
+ ValueError
77
+ If the value cannot be mapped to a valid enum member.
78
+
79
+ Examples
80
+ --------
81
+ >>> TaskStructure._coerce_task_type("WebAgentSearch")
82
+ <AgentEnum.WEB_SEARCH: 'WebAgentSearch'>
83
+ """
84
+ if isinstance(value, AgentEnum):
85
+ return value
86
+ return AgentEnum(value)
87
+
88
+ def print(self) -> str:
89
+ """Return a human-readable representation of the task.
90
+
91
+ Parameters
92
+ ----------
93
+ None
94
+
95
+ Returns
96
+ -------
97
+ str
98
+ Multi-line description of the task metadata.
99
+
100
+ Raises
101
+ ------
102
+ None
103
+
104
+ Examples
105
+ --------
106
+ >>> TaskStructure(prompt="Test").print()
107
+ 'Task type: ...' # doctest: +SKIP
108
+ """
109
+ return "\n".join(
110
+ [
111
+ BaseStructure.format_output("Task type", self.task_type),
112
+ BaseStructure.format_output("Prompt", self.prompt),
113
+ BaseStructure.format_output("Context", self.context),
114
+ BaseStructure.format_output("Status", self.status),
115
+ BaseStructure.format_output("Start date", self.start_date),
116
+ BaseStructure.format_output("End date", self.end_date),
117
+ BaseStructure.format_output("Results", self.results),
118
+ ]
119
+ )
120
+
121
+
122
+ __all__ = ["TaskStructure"]
@@ -0,0 +1,24 @@
1
+ """Shared structured output model for prompts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import BaseStructure, spec_field
6
+
7
+
8
+ class PromptStructure(BaseStructure):
9
+ """The prompt text to use for the OpenAI API request.
10
+
11
+ Methods
12
+ -------
13
+ print()
14
+ Return the formatted model fields.
15
+ """
16
+
17
+ prompt: str = spec_field(
18
+ "prompt",
19
+ description="The prompt text to use for the OpenAI API request.",
20
+ examples=[
21
+ "What is the capital of France?",
22
+ "Generate a summary of the latest news in AI.",
23
+ ],
24
+ )
@@ -0,0 +1,132 @@
1
+ """OpenAI response and tool helpers for structured outputs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Type
6
+
7
+ from openai.types.responses.response_format_text_json_schema_config_param import (
8
+ ResponseFormatTextJSONSchemaConfigParam,
9
+ )
10
+ from openai.types.responses.response_text_config_param import ResponseTextConfigParam
11
+
12
+ from .base import BaseStructure
13
+ from ..utils import log
14
+
15
+
16
+ def assistant_tool_definition(
17
+ structure: Type[BaseStructure], name: str, description: str
18
+ ) -> dict:
19
+ """Build a function tool definition for OpenAI Assistants.
20
+
21
+ Parameters
22
+ ----------
23
+ structure : type[BaseStructure]
24
+ Structure class that defines the tool schema.
25
+ name : str
26
+ Name of the function tool.
27
+ description : str
28
+ Description of what the function tool does.
29
+
30
+ Returns
31
+ -------
32
+ dict
33
+ Assistant tool definition payload.
34
+ """
35
+ log(f"{structure.__name__}::assistant_tool_definition")
36
+ return {
37
+ "type": "function",
38
+ "function": {
39
+ "name": name,
40
+ "description": description,
41
+ "parameters": structure.get_schema(),
42
+ },
43
+ }
44
+
45
+
46
+ def assistant_format(structure: Type[BaseStructure]) -> dict:
47
+ """Build a response format definition for OpenAI Assistants.
48
+
49
+ Parameters
50
+ ----------
51
+ structure : type[BaseStructure]
52
+ Structure class that defines the response schema.
53
+
54
+ Returns
55
+ -------
56
+ dict
57
+ Assistant response format definition.
58
+ """
59
+ log(f"{structure.__name__}::assistant_format")
60
+ return {
61
+ "type": "json_schema",
62
+ "json_schema": {
63
+ "name": structure.__name__,
64
+ "schema": structure.get_schema(),
65
+ },
66
+ }
67
+
68
+
69
+ def response_tool_definition(
70
+ structure: Type[BaseStructure],
71
+ tool_name: str,
72
+ tool_description: str,
73
+ ) -> dict:
74
+ """Build a tool definition for OpenAI chat completions.
75
+
76
+ Parameters
77
+ ----------
78
+ structure : type[BaseStructure]
79
+ Structure class that defines the tool schema.
80
+ tool_name : str
81
+ Name of the function tool.
82
+ tool_description : str
83
+ Description of what the function tool does.
84
+
85
+ Returns
86
+ -------
87
+ dict
88
+ Tool definition payload for chat completions.
89
+ """
90
+ log(f"{structure.__name__}::response_tool_definition")
91
+ return {
92
+ "type": "function",
93
+ "name": tool_name,
94
+ "description": tool_description,
95
+ "parameters": structure.get_schema(),
96
+ "strict": True,
97
+ "additionalProperties": False,
98
+ }
99
+
100
+
101
+ def response_format(structure: Type[BaseStructure]) -> ResponseTextConfigParam:
102
+ """Build a response format for OpenAI chat completions.
103
+
104
+ Parameters
105
+ ----------
106
+ structure : type[BaseStructure]
107
+ Structure class that defines the response schema.
108
+
109
+ Returns
110
+ -------
111
+ ResponseTextConfigParam
112
+ Response format definition.
113
+ """
114
+ log(f"{structure.__name__}::response_format")
115
+ response_format_text_JSONSchema_config_param = (
116
+ ResponseFormatTextJSONSchemaConfigParam(
117
+ name=structure.__name__,
118
+ schema=structure.get_schema(),
119
+ type="json_schema",
120
+ description="This is a JSON schema format for the output structure.",
121
+ strict=True,
122
+ )
123
+ )
124
+ return ResponseTextConfigParam(format=response_format_text_JSONSchema_config_param)
125
+
126
+
127
+ __all__ = [
128
+ "assistant_tool_definition",
129
+ "assistant_format",
130
+ "response_tool_definition",
131
+ "response_format",
132
+ ]
@@ -0,0 +1,65 @@
1
+ """Shared structured output models for summaries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List
6
+
7
+ from .base import BaseStructure, spec_field
8
+
9
+
10
+ class SummaryTopic(BaseStructure):
11
+ """Capture a topic-level summary with supporting citations.
12
+
13
+ Methods
14
+ -------
15
+ print()
16
+ Return a formatted string representation of the stored fields.
17
+ """
18
+
19
+ topic: str = spec_field(
20
+ "topic",
21
+ default=...,
22
+ description="Topic or micro-trend identified in the provided excerpts.",
23
+ )
24
+ summary: str = spec_field(
25
+ "summary",
26
+ default=...,
27
+ description="Concise explanation of what the excerpts convey about the topic.",
28
+ )
29
+ citations: List[str] = spec_field(
30
+ "citations",
31
+ default_factory=list,
32
+ description="Indices or short quotes that justify the topic summary.",
33
+ )
34
+
35
+
36
+ class SummaryStructure(BaseStructure):
37
+ """Defines the consolidated summary returned by the summarizer agent.
38
+
39
+ Methods
40
+ -------
41
+ print()
42
+ Return a formatted string representation of the stored fields.
43
+ """
44
+
45
+ text: str = spec_field(
46
+ "text",
47
+ default=...,
48
+ description="Combined summary synthesized from the supplied excerpts.",
49
+ )
50
+
51
+
52
+ class ExtendedSummaryStructure(SummaryStructure):
53
+ """Extend ``SummaryStructure`` with optional topic breakdown metadata.
54
+
55
+ Methods
56
+ -------
57
+ print()
58
+ Return a formatted string representation of the stored fields.
59
+ """
60
+
61
+ metadata: List[SummaryTopic] = spec_field(
62
+ "metadata",
63
+ default_factory=list,
64
+ description="Optional topic-level summaries with supporting citations.",
65
+ )