lionagi 0.13.2__py3-none-any.whl → 0.13.3__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 (44) hide show
  1. lionagi/fields/action.py +0 -1
  2. lionagi/fields/reason.py +0 -1
  3. lionagi/libs/file/save.py +1 -1
  4. lionagi/libs/schema/as_readable.py +142 -196
  5. lionagi/libs/schema/extract_docstring.py +1 -2
  6. lionagi/libs/token_transform/synthlang_/base.py +0 -2
  7. lionagi/libs/validate/string_similarity.py +1 -2
  8. lionagi/models/hashable_model.py +0 -1
  9. lionagi/models/schema_model.py +0 -1
  10. lionagi/operations/ReAct/utils.py +0 -1
  11. lionagi/operations/_act/act.py +0 -1
  12. lionagi/operations/interpret/interpret.py +1 -4
  13. lionagi/operations/manager.py +0 -1
  14. lionagi/operations/plan/plan.py +0 -1
  15. lionagi/operations/select/utils.py +0 -2
  16. lionagi/protocols/forms/flow.py +3 -1
  17. lionagi/protocols/generic/pile.py +1 -2
  18. lionagi/protocols/generic/processor.py +0 -1
  19. lionagi/protocols/graph/graph.py +1 -3
  20. lionagi/protocols/mail/package.py +0 -1
  21. lionagi/protocols/messages/assistant_response.py +0 -2
  22. lionagi/protocols/messages/message.py +0 -1
  23. lionagi/service/connections/endpoint_config.py +6 -0
  24. lionagi/service/connections/match_endpoint.py +26 -8
  25. lionagi/service/connections/providers/claude_code_.py +8 -9
  26. lionagi/service/connections/providers/claude_code_cli.py +414 -0
  27. lionagi/service/connections/providers/oai_.py +1 -1
  28. lionagi/service/manager.py +0 -1
  29. lionagi/service/rate_limited_processor.py +0 -2
  30. lionagi/service/token_calculator.py +0 -3
  31. lionagi/session/branch.py +0 -2
  32. lionagi/session/session.py +0 -1
  33. lionagi/settings.py +0 -1
  34. lionagi/utils.py +6 -9
  35. lionagi/version.py +1 -1
  36. {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/METADATA +5 -3
  37. {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/RECORD +39 -43
  38. lionagi/traits/__init__.py +0 -58
  39. lionagi/traits/base.py +0 -216
  40. lionagi/traits/composer.py +0 -343
  41. lionagi/traits/protocols.py +0 -495
  42. lionagi/traits/registry.py +0 -1071
  43. {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/WHEEL +0 -0
  44. {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/licenses/LICENSE +0 -0
lionagi/fields/action.py CHANGED
@@ -21,7 +21,6 @@ __all__ = (
21
21
 
22
22
 
23
23
  def parse_action_request(content: str | dict) -> list[dict]:
24
-
25
24
  json_blocks = []
26
25
 
27
26
  if isinstance(content, BaseModel):
lionagi/fields/reason.py CHANGED
@@ -11,7 +11,6 @@ __all__ = ("Reason",)
11
11
 
12
12
 
13
13
  class Reason(HashableModel):
14
-
15
14
  title: str | None = None
16
15
  content: str | None = None
17
16
  confidence_score: float | None = Field(
lionagi/libs/file/save.py CHANGED
@@ -82,7 +82,7 @@ def save_chunks(
82
82
  for i, chunk in enumerate(chunks):
83
83
  file_path = create_path(
84
84
  directory=output_path,
85
- filename=f"chunk_{i+1}",
85
+ filename=f"chunk_{i + 1}",
86
86
  extension="json",
87
87
  timestamp=timestamp,
88
88
  random_hash_digits=random_hash_digits,
@@ -11,81 +11,84 @@ from lionagi.utils import to_dict
11
11
  # Try to import rich for enhanced console output
12
12
  try:
13
13
  from rich.align import Align
14
- from rich.box import ROUNDED
14
+ from rich.box import MINIMAL, ROUNDED
15
15
  from rich.console import Console
16
+ from rich.markdown import Markdown
16
17
  from rich.padding import Padding
17
18
  from rich.panel import Panel
19
+ from rich.style import Style
18
20
  from rich.syntax import Syntax
21
+ from rich.text import Text
19
22
  from rich.theme import Theme
20
23
 
24
+ DARK_THEME = Theme(
25
+ {
26
+ "info": "bright_cyan",
27
+ "warning": "bright_yellow",
28
+ "error": "bold bright_red",
29
+ "success": "bold bright_green",
30
+ "panel.border": "bright_blue",
31
+ "panel.title": "bold bright_cyan",
32
+ "markdown.h1": "bold bright_magenta",
33
+ "markdown.h2": "bold bright_blue",
34
+ "markdown.h3": "bold bright_cyan",
35
+ "markdown.h4": "bold bright_green",
36
+ "markdown.code": "bright_yellow on grey23",
37
+ "markdown.code_block": "bright_white on grey15",
38
+ "markdown.paragraph": "bright_white",
39
+ "markdown.text": "bright_white",
40
+ "markdown.emph": "italic bright_yellow",
41
+ "markdown.strong": "bold bright_white",
42
+ "markdown.item": "bright_cyan",
43
+ "markdown.item.bullet": "bright_blue",
44
+ "json.key": "bright_cyan",
45
+ "json.string": "bright_green",
46
+ "json.number": "bright_yellow",
47
+ "json.boolean": "bright_magenta",
48
+ "json.null": "bright_red",
49
+ "yaml.key": "bright_cyan",
50
+ "yaml.string": "bright_green",
51
+ "yaml.number": "bright_yellow",
52
+ "yaml.boolean": "bright_magenta",
53
+ }
54
+ )
55
+
56
+ LIGHT_THEME = Theme(
57
+ {
58
+ "info": "blue",
59
+ "warning": "dark_orange",
60
+ "error": "bold red",
61
+ "success": "bold green4",
62
+ "panel.border": "blue",
63
+ "panel.title": "bold blue",
64
+ "markdown.h1": "bold dark_magenta",
65
+ "markdown.h2": "bold dark_blue",
66
+ "markdown.h3": "bold dark_cyan",
67
+ "markdown.h4": "bold dark_green",
68
+ "markdown.code": "dark_orange on grey93",
69
+ "markdown.code_block": "black on grey82",
70
+ "markdown.paragraph": "black",
71
+ "markdown.text": "black",
72
+ "markdown.emph": "italic dark_orange",
73
+ "markdown.strong": "bold black",
74
+ "markdown.item": "dark_blue",
75
+ "markdown.item.bullet": "blue",
76
+ "json.key": "dark_blue",
77
+ "json.string": "dark_green",
78
+ "json.number": "dark_orange",
79
+ "json.boolean": "dark_magenta",
80
+ "json.null": "dark_red",
81
+ "yaml.key": "dark_blue",
82
+ "yaml.string": "dark_green",
83
+ "yaml.number": "dark_orange",
84
+ "yaml.boolean": "dark_magenta",
85
+ }
86
+ )
21
87
  RICH_AVAILABLE = True
22
88
  except ImportError:
23
89
  RICH_AVAILABLE = False
24
-
25
- # Custom themes for dark and light modes
26
- DARK_THEME = Theme(
27
- {
28
- "info": "bright_cyan",
29
- "warning": "bright_yellow",
30
- "error": "bold bright_red",
31
- "success": "bold bright_green",
32
- "panel.border": "bright_blue",
33
- "panel.title": "bold bright_cyan",
34
- "markdown.h1": "bold bright_magenta",
35
- "markdown.h2": "bold bright_blue",
36
- "markdown.h3": "bold bright_cyan",
37
- "markdown.h4": "bold bright_green",
38
- "markdown.code": "bright_yellow on grey23",
39
- "markdown.code_block": "bright_white on grey15",
40
- "markdown.paragraph": "bright_white",
41
- "markdown.text": "bright_white",
42
- "markdown.emph": "italic bright_yellow",
43
- "markdown.strong": "bold bright_white",
44
- "markdown.item": "bright_cyan",
45
- "markdown.item.bullet": "bright_blue",
46
- "json.key": "bright_cyan",
47
- "json.string": "bright_green",
48
- "json.number": "bright_yellow",
49
- "json.boolean": "bright_magenta",
50
- "json.null": "bright_red",
51
- "yaml.key": "bright_cyan",
52
- "yaml.string": "bright_green",
53
- "yaml.number": "bright_yellow",
54
- "yaml.boolean": "bright_magenta",
55
- }
56
- )
57
-
58
- LIGHT_THEME = Theme(
59
- {
60
- "info": "blue",
61
- "warning": "dark_orange",
62
- "error": "bold red",
63
- "success": "bold green4",
64
- "panel.border": "blue",
65
- "panel.title": "bold blue",
66
- "markdown.h1": "bold dark_magenta",
67
- "markdown.h2": "bold dark_blue",
68
- "markdown.h3": "bold dark_cyan",
69
- "markdown.h4": "bold dark_green",
70
- "markdown.code": "dark_orange on grey93",
71
- "markdown.code_block": "black on grey82",
72
- "markdown.paragraph": "black",
73
- "markdown.text": "black",
74
- "markdown.emph": "italic dark_orange",
75
- "markdown.strong": "bold black",
76
- "markdown.item": "dark_blue",
77
- "markdown.item.bullet": "blue",
78
- "json.key": "dark_blue",
79
- "json.string": "dark_green",
80
- "json.number": "dark_orange",
81
- "json.boolean": "dark_magenta",
82
- "json.null": "dark_red",
83
- "yaml.key": "dark_blue",
84
- "yaml.string": "dark_green",
85
- "yaml.number": "dark_orange",
86
- "yaml.boolean": "dark_magenta",
87
- }
88
- )
90
+ DARK_THEME = None
91
+ LIGHT_THEME = None
89
92
 
90
93
 
91
94
  def in_notebook() -> bool:
@@ -172,6 +175,8 @@ def as_readable(
172
175
  use_rich: bool = True,
173
176
  theme: str = "dark",
174
177
  max_panel_width: int = 140,
178
+ panel: bool = True,
179
+ border: bool = True,
175
180
  ) -> str:
176
181
  """
177
182
  Convert `input_` into a human-readable string. If `format_curly=True`, uses
@@ -191,6 +196,7 @@ def as_readable(
191
196
  theme: Color theme - "dark" (default) or "light". Dark uses GitHub Dark Dimmed,
192
197
  light uses Solarized Light inspired colors.
193
198
  max_panel_width: Maximum width for panels and code blocks in characters.
199
+ panel: If True, wraps the output in a panel for better visibility.
194
200
 
195
201
  Returns:
196
202
  A formatted string representation of `input_` (unless display_str=True).
@@ -249,7 +255,6 @@ def as_readable(
249
255
  final_str = "\n\n".join(rendered).strip()
250
256
 
251
257
  # 4) If Markdown requested, wrap with code fences
252
- # - If we used format_curly, we might do "```yaml" instead. But user specifically asked for JSON code blocks previously
253
258
  if md:
254
259
  if format_curly:
255
260
  return f"```yaml\n{final_str}\n```"
@@ -260,137 +265,78 @@ def as_readable(
260
265
 
261
266
  str_ = _inner(input_).strip()
262
267
  if max_chars is not None and len(str_) > max_chars:
263
- str1 = str_[:max_chars] + "...\n\n[Truncated output]\n\n"
264
- if str_.endswith("\n```"):
265
- str1 += "```"
266
- str_ = str1
267
- if display_str:
268
- if md and in_notebook():
269
- # If in IPython environment, display Markdown
270
- from IPython.display import Markdown, display
271
-
272
- display(Markdown(str_))
273
- elif RICH_AVAILABLE and in_console() and use_rich:
274
- # Use rich for enhanced console output
275
- # Select theme and syntax highlighting based on user preference
276
- console_theme = DARK_THEME if theme == "dark" else LIGHT_THEME
277
- syntax_theme = (
278
- "github-dark" if theme == "dark" else "solarized-light"
279
- )
280
- panel_style = "bright_blue" if theme == "dark" else "blue"
281
-
282
- console = Console(theme=console_theme)
283
-
284
- # Check if content looks like markdown prose (not code)
285
- is_markdown_prose = isinstance(str_, str) and (
286
- str_.startswith("#")
287
- or str_.startswith("**")
288
- or str_.startswith("- ")
289
- or str_.startswith("1.")
290
- or "<multi_reasoning>" in str_
291
- or "\n### " in str_
292
- or "\n## " in str_
293
- or "\n# " in str_
294
- or "│" in str_ # Rich table content
295
- )
296
-
297
- if md and is_markdown_prose:
298
- # Display as formatted markdown
299
- # Create markdown with max width
300
- from rich.markdown import Markdown as RichMarkdown
301
-
302
- md_content = RichMarkdown(str_, code_theme=syntax_theme)
303
-
304
- # Calculate appropriate width
305
- console_width = console.width
306
- panel_width = min(console_width - 4, max_panel_width)
307
-
308
- # Add left margin padding for better alignment
309
- panel = Panel(
310
- Padding(md_content, (0, 2)),
311
- border_style=panel_style,
312
- box=ROUNDED,
313
- width=panel_width,
314
- expand=False,
315
- )
316
-
317
- # Left align with margin
318
- aligned_panel = Align.left(panel, pad=True)
319
- console.print(Padding(aligned_panel, (0, 0, 0, 4)))
320
-
321
- elif md:
322
- # Extract content from markdown code blocks if present
323
- content = str_
324
- if content.startswith("```") and content.endswith("```"):
325
- # Remove code fences
326
- lines = content.split("\n")
327
- if len(lines) > 2:
328
- lang = lines[0][3:].strip() or "json"
329
- content = "\n".join(lines[1:-1])
330
- else:
331
- lang = "json"
332
- else:
333
- lang = "yaml" if format_curly else "json"
334
-
335
- # Calculate appropriate width
336
- console_width = console.width
337
- panel_width = min(console_width - 4, max_panel_width)
338
-
339
- # Create syntax highlighted output
340
- syntax = Syntax(
341
- content,
342
- lang,
343
- theme=syntax_theme,
344
- line_numbers=True,
345
- background_color="default",
346
- word_wrap=True,
268
+ trunc = str_[:max_chars] + "...\n\n[Truncated output]"
269
+ str_ = trunc + ("\n```" if str_.endswith("\n```") else "")
270
+
271
+ # -------------------- PRINT / DISPLAY LOGIC ---------------------------
272
+ if not display_str:
273
+ return str_ # caller will handle printing
274
+
275
+ # (1) IPython notebook --------------------------------------------------
276
+ if md and in_notebook():
277
+ from IPython.display import Markdown, display
278
+
279
+ display(Markdown(str_))
280
+ return
281
+
282
+ # (2) Rich console ------------------------------------------------------
283
+ if RICH_AVAILABLE and in_console() and use_rich:
284
+ console_theme = DARK_THEME if theme == "dark" else LIGHT_THEME
285
+ syntax_theme = "github-dark" if theme == "dark" else "solarized-light"
286
+ console = Console(theme=console_theme)
287
+
288
+ # determine prose / fenced code
289
+ is_fenced_code = (
290
+ md and str_.startswith("```") and str_.rstrip().endswith("```")
291
+ )
292
+ is_prose_md = md and not is_fenced_code
293
+ panel_width = min(console.width - 4, max_panel_width)
294
+
295
+ def _out(rich_obj):
296
+ if not panel:
297
+ console.print(Padding(rich_obj, (0, 0, 0, 2)))
298
+ return
299
+
300
+ console.print(
301
+ Padding(
302
+ Panel(
303
+ Align.left(rich_obj, pad=False),
304
+ border_style="panel.border" if border else "",
305
+ box=ROUNDED if border else MINIMAL,
306
+ width=panel_width,
307
+ expand=False,
308
+ ),
309
+ (0, 0, 0, 4),
347
310
  )
311
+ )
348
312
 
349
- # Add left margin padding for better alignment
350
- panel = Panel(
351
- syntax,
352
- border_style=panel_style,
353
- box=ROUNDED,
354
- width=panel_width,
355
- expand=False,
356
- )
313
+ # 2‑a prose markdown ------------------------------------------------
314
+ if is_prose_md:
315
+ from rich.markdown import Markdown as RichMarkdown
357
316
 
358
- # Left align with margin
359
- aligned_panel = Align.left(panel, pad=True)
360
- console.print(Padding(aligned_panel, (0, 0, 0, 4)))
317
+ _out(RichMarkdown(str_, code_theme=syntax_theme))
318
+ return
361
319
 
362
- else:
363
- # Plain text output with rich formatting
364
- if format_curly:
365
- syntax = Syntax(
366
- str_,
367
- "yaml",
368
- theme=syntax_theme,
369
- background_color="default",
370
- word_wrap=True,
371
- )
372
- else:
373
- syntax = Syntax(
374
- str_,
375
- "json",
376
- theme=syntax_theme,
377
- background_color="default",
378
- word_wrap=True,
379
- )
380
-
381
- # For plain syntax, add left margin
382
- # Create a constrained width container if console is too wide
383
- if console.width > max_panel_width:
384
- content = Align.left(
385
- syntax, width=max_panel_width, pad=False
386
- )
387
- # Add left margin
388
- console.print(Padding(content, (0, 0, 0, 4)))
389
- else:
390
- # Just add left margin
391
- console.print(Padding(syntax, (0, 0, 0, 4)))
320
+ # 2‑b code (fenced or explicit) -------------------------------------
321
+ if is_fenced_code:
322
+ lines = str_.splitlines()
323
+ lang, code = (
324
+ (lines[0][3:].strip() or ("yaml" if format_curly else "json")),
325
+ "\n".join(lines[1:-1]),
326
+ )
392
327
  else:
393
- # Fallback to regular print
394
- print(str_)
395
- else:
396
- return str_
328
+ lang, code = ("yaml" if format_curly else "json"), str_
329
+
330
+ syntax = Syntax(
331
+ code,
332
+ lang,
333
+ theme=syntax_theme,
334
+ line_numbers=False,
335
+ word_wrap=True,
336
+ background_color="default",
337
+ )
338
+ _out(syntax)
339
+ return
340
+
341
+ # (3) Plain fallback ----------------------------------------------------
342
+ print(str_)
@@ -52,8 +52,7 @@ def extract_docstring(
52
52
  )
53
53
  else:
54
54
  raise ValueError(
55
- f'{style} is not supported. Please choose either "google" or'
56
- ' "reST".'
55
+ f'{style} is not supported. Please choose either "google" or "reST".'
57
56
  )
58
57
  return func_description, params_description
59
58
 
@@ -21,7 +21,6 @@ __all__ = (
21
21
 
22
22
 
23
23
  class SynthlangFramework(Resource):
24
-
25
24
  category: ResourceCategory = Field(
26
25
  default=ResourceCategory.FRAMEWORK, frozen=True
27
26
  )
@@ -72,7 +71,6 @@ class SynthlangFramework(Resource):
72
71
  framework_options: list[FRAMEWORK_CHOICES] = None,
73
72
  additional_text: str = "",
74
73
  ) -> str:
75
-
76
74
  framework_options_text = self.build_framework_text(framework_options)
77
75
  base_prompt = self.load_base_system_prompt()
78
76
  template_details = (
@@ -297,8 +297,7 @@ def string_similarity(
297
297
  score_func = algorithm
298
298
  else:
299
299
  raise ValueError(
300
- "algorithm must be a string specifying a built-in algorithm or "
301
- "a callable"
300
+ "algorithm must be a string specifying a built-in algorithm or a callable"
302
301
  )
303
302
 
304
303
  # Calculate similarities
@@ -5,7 +5,6 @@ from lionagi.utils import UNDEFINED, hash_dict
5
5
 
6
6
 
7
7
  class HashableModel(BaseModel):
8
-
9
8
  def to_dict(self, **kwargs) -> dict:
10
9
  """provides interface, specific methods need to be implemented in subclass kwargs for pydantic model_dump"""
11
10
  return {
@@ -10,7 +10,6 @@ __all__ = ("SchemaModel",)
10
10
 
11
11
 
12
12
  class SchemaModel(HashableModel):
13
-
14
13
  model_config = ConfigDict(
15
14
  extra="forbid",
16
15
  validate_default=False,
@@ -113,7 +113,6 @@ class ReActAnalysis(HashableModel):
113
113
 
114
114
 
115
115
  class Analysis(HashableModel):
116
-
117
116
  answer: str | None = None
118
117
 
119
118
  @field_validator("answer", mode="before")
@@ -20,7 +20,6 @@ async def _act(
20
20
  suppress_errors: bool = False,
21
21
  verbose_action: bool = False,
22
22
  ) -> "ActionResponseModel":
23
-
24
23
  _request = {}
25
24
 
26
25
  if isinstance(action_request, BaseModel):
@@ -23,10 +23,7 @@ async def interpret(
23
23
  "Return only the re-written prompt. Do not assume any details not mentioned in the input, nor "
24
24
  "give additional instruction than what is explicitly stated."
25
25
  )
26
- guidance = (
27
- f"Domain hint: {domain or 'general'}. "
28
- f"Desired style: {style or 'concise'}. "
29
- )
26
+ guidance = f"Domain hint: {domain or 'general'}. Desired style: {style or 'concise'}. "
30
27
  if sample_writing:
31
28
  guidance += f" Sample writing: {sample_writing}"
32
29
 
@@ -8,7 +8,6 @@ experimental
8
8
 
9
9
 
10
10
  class OperationManager(Manager):
11
-
12
11
  def __init__(self, *args, **kwargs):
13
12
  super().__init__()
14
13
  self.registry: dict[str, Callable] = {}
@@ -231,7 +231,6 @@ async def plan(
231
231
 
232
232
  # We now handle multiple strategies:
233
233
  match execution_strategy:
234
-
235
234
  # ---------------------------------------------------------
236
235
  # Strategy A: SEQUENTIAL
237
236
  # ---------------------------------------------------------
@@ -65,7 +65,6 @@ def parse_to_representation(
65
65
 
66
66
 
67
67
  def get_choice_representation(choice: Any) -> str:
68
-
69
68
  if isinstance(choice, str):
70
69
  return choice
71
70
 
@@ -77,7 +76,6 @@ def get_choice_representation(choice: Any) -> str:
77
76
 
78
77
 
79
78
  def parse_selection(selection_str: str, choices: Any):
80
-
81
79
  select_from = []
82
80
 
83
81
  if isinstance(choices, dict):
@@ -46,7 +46,9 @@ class FlowDefinition(BaseModel):
46
46
  ins_str, outs_str = seg.split("->", 1)
47
47
  inputs = [x.strip() for x in ins_str.split(",") if x.strip()]
48
48
  outputs = [y.strip() for y in outs_str.split(",") if y.strip()]
49
- step = FlowStep(name=f"step_{i+1}", inputs=inputs, outputs=outputs)
49
+ step = FlowStep(
50
+ name=f"step_{i + 1}", inputs=inputs, outputs=outputs
51
+ )
50
52
  self.steps.append(step)
51
53
 
52
54
  def get_required_fields(self) -> set[str]:
@@ -851,8 +851,7 @@ class Pile(Element, Collective[E], Generic[E]):
851
851
  if self.strict_type:
852
852
  if type(i) not in self.item_type:
853
853
  raise TypeError(
854
- "Invalid item type in pile."
855
- f" Expected {self.item_type}",
854
+ f"Invalid item type in pile. Expected {self.item_type}",
856
855
  )
857
856
  else:
858
857
  if not any(issubclass(type(i), t) for t in self.item_type):
@@ -149,7 +149,6 @@ class Processor(Observer):
149
149
  next_event = await self.dequeue()
150
150
 
151
151
  if await self.request_permission(**next_event.request):
152
-
153
152
  if next_event.streaming:
154
153
  task = asyncio.create_task(next_event.stream())
155
154
  else:
@@ -21,7 +21,6 @@ __all__ = ("Graph",)
21
21
 
22
22
 
23
23
  class Graph(Element, Relational):
24
-
25
24
  internal_nodes: Pile[Node] = Field(
26
25
  default_factory=lambda: Pile(item_type={Node}, strict_type=False),
27
26
  title="Internal Nodes",
@@ -52,8 +51,7 @@ class Graph(Element, Relational):
52
51
  """Add a node to the graph."""
53
52
  if not isinstance(node, Relational):
54
53
  raise RelationError(
55
- "Failed to add node: Invalid node type: "
56
- "not a <Relational> entity."
54
+ "Failed to add node: Invalid node type: not a <Relational> entity."
57
55
  )
58
56
  _id = ID.get_id(node)
59
57
  try:
@@ -91,7 +91,6 @@ class Package(Observable):
91
91
  item: Any,
92
92
  request_source: ID[Communicatable] = None,
93
93
  ):
94
-
95
94
  super().__init__()
96
95
  self.id = IDType.create()
97
96
  self.created_at = time(type_="timestamp")
@@ -15,7 +15,6 @@ from .message import MessageRole, RoledMessage, Template, jinja_env
15
15
  def prepare_assistant_response(
16
16
  assistant_response: BaseModel | list[BaseModel] | dict | str | Any, /
17
17
  ) -> dict:
18
-
19
18
  assistant_response = (
20
19
  [assistant_response]
21
20
  if not isinstance(assistant_response, list)
@@ -26,7 +25,6 @@ def prepare_assistant_response(
26
25
  model_responses = []
27
26
 
28
27
  for i in assistant_response:
29
-
30
28
  if isinstance(i, BaseModel):
31
29
  i = i.model_dump(exclude_none=True, exclude_unset=True)
32
30
 
@@ -232,7 +232,6 @@ class RoledMessage(Node, Sendable):
232
232
  self.template = template
233
233
 
234
234
  def __str__(self) -> str:
235
-
236
235
  content_preview = (
237
236
  f"{str(self.content)[:75]}..."
238
237
  if len(str(self.content)) > 75
@@ -73,6 +73,12 @@ class EndpointConfig(BaseModel):
73
73
 
74
74
  return self
75
75
 
76
+ @field_validator("provider", mode="before")
77
+ def _validate_provider(cls, v: str):
78
+ if not v:
79
+ raise ValueError("Provider must be specified")
80
+ return v.strip().lower()
81
+
76
82
  @property
77
83
  def full_url(self):
78
84
  if not self.endpoint_params: