griptape-nodes 0.62.3__py3-none-any.whl → 0.63.1__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 (76) hide show
  1. griptape_nodes/cli/commands/libraries.py +6 -21
  2. griptape_nodes/cli/commands/models.py +21 -4
  3. griptape_nodes/drivers/thread_storage/__init__.py +15 -0
  4. griptape_nodes/drivers/thread_storage/base_thread_storage_driver.py +106 -0
  5. griptape_nodes/drivers/thread_storage/griptape_cloud_thread_storage_driver.py +213 -0
  6. griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +137 -0
  7. griptape_nodes/drivers/thread_storage/thread_storage_backend.py +10 -0
  8. griptape_nodes/node_library/library_registry.py +16 -9
  9. griptape_nodes/node_library/workflow_registry.py +1 -1
  10. griptape_nodes/retained_mode/events/agent_events.py +232 -9
  11. griptape_nodes/retained_mode/events/library_events.py +32 -3
  12. griptape_nodes/retained_mode/events/model_events.py +4 -4
  13. griptape_nodes/retained_mode/events/os_events.py +101 -1
  14. griptape_nodes/retained_mode/managers/agent_manager.py +335 -135
  15. griptape_nodes/retained_mode/managers/fitness_problems/__init__.py +1 -0
  16. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +59 -0
  17. griptape_nodes/retained_mode/managers/fitness_problems/libraries/advanced_library_load_failure_problem.py +33 -0
  18. griptape_nodes/retained_mode/managers/fitness_problems/libraries/after_library_callback_problem.py +32 -0
  19. griptape_nodes/retained_mode/managers/fitness_problems/libraries/before_library_callback_problem.py +32 -0
  20. griptape_nodes/retained_mode/managers/fitness_problems/libraries/create_config_category_problem.py +32 -0
  21. griptape_nodes/retained_mode/managers/fitness_problems/libraries/dependency_installation_failed_problem.py +32 -0
  22. griptape_nodes/retained_mode/managers/fitness_problems/libraries/deprecated_node_warning_problem.py +83 -0
  23. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_library_problem.py +28 -0
  24. griptape_nodes/retained_mode/managers/fitness_problems/libraries/duplicate_node_registration_problem.py +44 -0
  25. griptape_nodes/retained_mode/managers/fitness_problems/libraries/engine_version_error_problem.py +28 -0
  26. griptape_nodes/retained_mode/managers/fitness_problems/libraries/insufficient_disk_space_problem.py +33 -0
  27. griptape_nodes/retained_mode/managers/fitness_problems/libraries/invalid_version_string_problem.py +32 -0
  28. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_json_decode_problem.py +28 -0
  29. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_load_exception_problem.py +32 -0
  30. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_not_found_problem.py +30 -0
  31. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_problem.py +20 -0
  32. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_exception_problem.py +32 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/library_schema_validation_problem.py +38 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_deprecation_warning_problem.py +44 -0
  35. griptape_nodes/retained_mode/managers/fitness_problems/libraries/modified_parameters_set_removed_problem.py +44 -0
  36. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_base_node_problem.py +40 -0
  37. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_class_not_found_problem.py +38 -0
  38. griptape_nodes/retained_mode/managers/fitness_problems/libraries/node_module_import_problem.py +53 -0
  39. griptape_nodes/retained_mode/managers/fitness_problems/libraries/sandbox_directory_missing_problem.py +28 -0
  40. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_incompatible_problem.py +44 -0
  41. griptape_nodes/retained_mode/managers/fitness_problems/libraries/ui_options_field_modified_warning_problem.py +35 -0
  42. griptape_nodes/retained_mode/managers/fitness_problems/libraries/update_config_category_problem.py +32 -0
  43. griptape_nodes/retained_mode/managers/fitness_problems/libraries/venv_creation_failed_problem.py +32 -0
  44. griptape_nodes/retained_mode/managers/fitness_problems/workflows/__init__.py +75 -0
  45. griptape_nodes/retained_mode/managers/fitness_problems/workflows/deprecated_node_in_workflow_problem.py +83 -0
  46. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_dependency_version_string_problem.py +38 -0
  47. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_library_version_string_problem.py +38 -0
  48. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_schema_problem.py +31 -0
  49. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_metadata_section_count_problem.py +31 -0
  50. griptape_nodes/retained_mode/managers/fitness_problems/workflows/invalid_toml_format_problem.py +30 -0
  51. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_not_registered_problem.py +35 -0
  52. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_below_required_problem.py +41 -0
  53. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_large_difference_problem.py +41 -0
  54. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_major_mismatch_problem.py +41 -0
  55. griptape_nodes/retained_mode/managers/fitness_problems/workflows/library_version_minor_difference_problem.py +41 -0
  56. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_creation_date_problem.py +30 -0
  57. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_last_modified_date_problem.py +30 -0
  58. griptape_nodes/retained_mode/managers/fitness_problems/workflows/missing_toml_section_problem.py +30 -0
  59. griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_found_problem.py +51 -0
  60. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py +27 -0
  61. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py +20 -0
  62. griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py +39 -0
  63. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +17 -3
  64. griptape_nodes/retained_mode/managers/library_manager.py +159 -75
  65. griptape_nodes/retained_mode/managers/model_manager.py +182 -205
  66. griptape_nodes/retained_mode/managers/os_manager.py +172 -1
  67. griptape_nodes/retained_mode/managers/settings.py +5 -0
  68. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +76 -51
  69. griptape_nodes/retained_mode/managers/workflow_manager.py +154 -137
  70. griptape_nodes/servers/static.py +18 -19
  71. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +16 -12
  72. griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +6 -3
  73. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/METADATA +3 -2
  74. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/RECORD +76 -23
  75. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/WHEEL +0 -0
  76. {griptape_nodes-0.62.3.dist-info → griptape_nodes-0.63.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
6
+
7
+
8
+ @dataclass
9
+ class LibraryVersionBelowRequiredProblem(WorkflowProblem):
10
+ """Problem indicating a library version is below the required version.
11
+
12
+ This is stackable - multiple libraries can have versions below requirements.
13
+ """
14
+
15
+ library_name: str
16
+ current_version: str
17
+ required_version: str
18
+
19
+ @classmethod
20
+ def collate_problems_for_display(cls, instances: list[LibraryVersionBelowRequiredProblem]) -> str:
21
+ """Display library version below required problems.
22
+
23
+ Sorts by library_name and lists all affected libraries.
24
+ """
25
+ if len(instances) == 1:
26
+ problem = instances[0]
27
+ return f"'{problem.library_name}' v{problem.current_version} below required v{problem.required_version}. Update library to match workflow requirements."
28
+
29
+ # Sort by library_name
30
+ sorted_instances = sorted(instances, key=lambda p: p.library_name)
31
+
32
+ output_lines = []
33
+ output_lines.append(
34
+ f"{len(instances)} libraries below required versions. Update libraries to match workflow requirements:"
35
+ )
36
+ for i, problem in enumerate(sorted_instances, 1):
37
+ output_lines.append(
38
+ f" {i}. {problem.library_name}: current v{problem.current_version}, required v{problem.required_version}"
39
+ )
40
+
41
+ return "\n".join(output_lines)
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
6
+
7
+
8
+ @dataclass
9
+ class LibraryVersionLargeDifferenceProblem(WorkflowProblem):
10
+ """Problem indicating a library has a large version difference from what workflow expects.
11
+
12
+ This is stackable - multiple libraries can have large version differences.
13
+ """
14
+
15
+ library_name: str
16
+ workflow_version: str
17
+ current_version: str
18
+
19
+ @classmethod
20
+ def collate_problems_for_display(cls, instances: list[LibraryVersionLargeDifferenceProblem]) -> str:
21
+ """Display library version large difference problems.
22
+
23
+ Sorts by library_name and lists all affected libraries.
24
+ """
25
+ if len(instances) == 1:
26
+ problem = instances[0]
27
+ return f"Saved with '{problem.library_name}' v{problem.workflow_version}. You have v{problem.current_version}. Large version difference may cause compatibility issues. Re-save workflow (may require manual adjustments) to update to latest."
28
+
29
+ # Sort by library_name
30
+ sorted_instances = sorted(instances, key=lambda p: p.library_name)
31
+
32
+ output_lines = []
33
+ output_lines.append(
34
+ f"Uses {len(instances)} libraries with large version differences (may cause compatibility issues). Re-save workflow (may require manual adjustments) to update to latest:"
35
+ )
36
+ for i, problem in enumerate(sorted_instances, 1):
37
+ output_lines.append(
38
+ f" {i}. {problem.library_name}: saved with v{problem.workflow_version}, current v{problem.current_version}"
39
+ )
40
+
41
+ return "\n".join(output_lines)
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
6
+
7
+
8
+ @dataclass
9
+ class LibraryVersionMajorMismatchProblem(WorkflowProblem):
10
+ """Problem indicating a library has a major version mismatch from what workflow expects.
11
+
12
+ This is stackable - multiple libraries can have major version mismatches.
13
+ """
14
+
15
+ library_name: str
16
+ workflow_version: str
17
+ current_version: str
18
+
19
+ @classmethod
20
+ def collate_problems_for_display(cls, instances: list[LibraryVersionMajorMismatchProblem]) -> str:
21
+ """Display library version major mismatch problems.
22
+
23
+ Sorts by library_name and lists all affected libraries.
24
+ """
25
+ if len(instances) == 1:
26
+ problem = instances[0]
27
+ return f"Saved with '{problem.library_name}' v{problem.workflow_version}. You have v{problem.current_version}. Major version changes may include breaking changes. Re-save workflow (likely requires manual adjustments) to update to latest."
28
+
29
+ # Sort by library_name
30
+ sorted_instances = sorted(instances, key=lambda p: p.library_name)
31
+
32
+ output_lines = []
33
+ output_lines.append(
34
+ f"Uses {len(instances)} libraries with major version mismatches (may include breaking changes). Re-save workflow (likely requires manual adjustments) to update to latest:"
35
+ )
36
+ for i, problem in enumerate(sorted_instances, 1):
37
+ output_lines.append(
38
+ f" {i}. {problem.library_name}: saved with v{problem.workflow_version}, current v{problem.current_version}"
39
+ )
40
+
41
+ return "\n".join(output_lines)
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
6
+
7
+
8
+ @dataclass
9
+ class LibraryVersionMinorDifferenceProblem(WorkflowProblem):
10
+ """Problem indicating a library has a minor version difference from what workflow expects.
11
+
12
+ This is stackable - multiple libraries can have minor version differences.
13
+ """
14
+
15
+ library_name: str
16
+ workflow_version: str
17
+ current_version: str
18
+
19
+ @classmethod
20
+ def collate_problems_for_display(cls, instances: list[LibraryVersionMinorDifferenceProblem]) -> str:
21
+ """Display library version minor difference problems.
22
+
23
+ Sorts by library_name and lists all affected libraries.
24
+ """
25
+ if len(instances) == 1:
26
+ problem = instances[0]
27
+ return f"Saved with '{problem.library_name}' v{problem.workflow_version}. You have v{problem.current_version}. These are usually compatible. Re-save to update to latest."
28
+
29
+ # Sort by library_name
30
+ sorted_instances = sorted(instances, key=lambda p: p.library_name)
31
+
32
+ output_lines = []
33
+ output_lines.append(
34
+ f"Uses {len(instances)} libraries with minor version differences (usually compatible). Re-save to update to latest:"
35
+ )
36
+ for i, problem in enumerate(sorted_instances, 1):
37
+ output_lines.append(
38
+ f" {i}. {problem.library_name}: saved with v{problem.workflow_version}, current v{problem.current_version}"
39
+ )
40
+
41
+ return "\n".join(output_lines)
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+
6
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class MissingCreationDateProblem(WorkflowProblem):
13
+ """Problem indicating workflow metadata is missing a creation date.
14
+
15
+ This is one-time only - should only occur once per workflow.
16
+ """
17
+
18
+ default_date: str
19
+
20
+ @classmethod
21
+ def collate_problems_for_display(cls, instances: list[MissingCreationDateProblem]) -> str:
22
+ """Display missing creation date problem."""
23
+ if len(instances) > 1:
24
+ logger.error(
25
+ "MissingCreationDateProblem received %d instances but should only receive 1. This indicates a logic error.",
26
+ len(instances),
27
+ )
28
+
29
+ problem = instances[0]
30
+ return f"Missing creation date. Defaulting to {problem.default_date}. Will be replaced on next save."
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+
6
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class MissingLastModifiedDateProblem(WorkflowProblem):
13
+ """Problem indicating workflow metadata is missing a last modified date.
14
+
15
+ This is one-time only - should only occur once per workflow.
16
+ """
17
+
18
+ default_date: str
19
+
20
+ @classmethod
21
+ def collate_problems_for_display(cls, instances: list[MissingLastModifiedDateProblem]) -> str:
22
+ """Display missing last modified date problem."""
23
+ if len(instances) > 1:
24
+ logger.error(
25
+ "MissingLastModifiedDateProblem received %d instances but should only receive 1. This indicates a logic error.",
26
+ len(instances),
27
+ )
28
+
29
+ problem = instances[0]
30
+ return f"Workflow metadata was missing a last modified date. Defaulting to {problem.default_date}. This value will be replaced with the current date the first time it is saved."
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+
6
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class MissingTomlSectionProblem(WorkflowProblem):
13
+ """Problem indicating required TOML section is missing from workflow metadata.
14
+
15
+ This is one-time only - should only occur once per workflow.
16
+ """
17
+
18
+ section_path: str
19
+
20
+ @classmethod
21
+ def collate_problems_for_display(cls, instances: list[MissingTomlSectionProblem]) -> str:
22
+ """Display missing TOML section problem."""
23
+ if len(instances) > 1:
24
+ logger.error(
25
+ "MissingTomlSectionProblem received %d instances but should only receive 1. This indicates a logic error.",
26
+ len(instances),
27
+ )
28
+
29
+ problem = instances[0]
30
+ return f"'{problem.section_path}' in TOML metadata not found."
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
6
+
7
+
8
+ @dataclass
9
+ class NodeTypeNotFoundProblem(WorkflowProblem):
10
+ """Problem indicating a workflow uses a node type that doesn't exist in current library version.
11
+
12
+ This is stackable - workflows can reference multiple missing node types.
13
+ """
14
+
15
+ node_type: str
16
+ library_name: str
17
+ current_library_version: str
18
+ workflow_library_version: str | None
19
+
20
+ @classmethod
21
+ def collate_problems_for_display(cls, instances: list[NodeTypeNotFoundProblem]) -> str:
22
+ """Display node type not found problems.
23
+
24
+ Groups by library_name, then sorts by node_type within each library.
25
+ """
26
+ if len(instances) == 1:
27
+ problem = instances[0]
28
+ return f"Node type '{problem.node_type}' from '{problem.library_name}' not found in current version. May have been removed or renamed."
29
+
30
+ # Group by library_name
31
+ from collections import defaultdict
32
+
33
+ by_library = defaultdict(list)
34
+ for problem in instances:
35
+ by_library[problem.library_name].append(problem)
36
+
37
+ # Sort libraries alphabetically
38
+ sorted_libraries = sorted(by_library.keys())
39
+
40
+ output_lines = []
41
+ output_lines.append(f"{len(instances)} node types not found (may have been removed or renamed):")
42
+
43
+ for library_name in sorted_libraries:
44
+ nodes = by_library[library_name]
45
+ # Sort nodes by node_type within each library
46
+ nodes.sort(key=lambda p: p.node_type)
47
+
48
+ output_lines.append(f" From '{library_name}':")
49
+ output_lines.extend(f" - {node.node_type}" for node in nodes)
50
+
51
+ return "\n".join(output_lines)
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+
6
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class WorkflowNotFoundProblem(WorkflowProblem):
13
+ """Problem indicating a workflow file could not be found.
14
+
15
+ This is one-time only - should only occur once per workflow.
16
+ """
17
+
18
+ @classmethod
19
+ def collate_problems_for_display(cls, instances: list[WorkflowNotFoundProblem]) -> str:
20
+ """Display workflow not found problem."""
21
+ if len(instances) > 1:
22
+ logger.error(
23
+ "WorkflowNotFoundProblem received %d instances but should only receive 1. This indicates a logic error.",
24
+ len(instances),
25
+ )
26
+
27
+ return "Workflow file not found. Will be removed from configuration."
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Self
5
+
6
+
7
+ class WorkflowProblem(ABC):
8
+ """Base class for all workflow fitness problems."""
9
+
10
+ @classmethod
11
+ @abstractmethod
12
+ def collate_problems_for_display(cls, instances: list[Self]) -> str:
13
+ """Collate one or more instances of this problem type for display.
14
+
15
+ Args:
16
+ instances: List of problem instances of this type
17
+
18
+ Returns:
19
+ A formatted string suitable for displaying to the user
20
+ """
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+
6
+ from griptape_nodes.retained_mode.managers.fitness_problems.workflows.workflow_problem import WorkflowProblem
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @dataclass
12
+ class WorkflowSchemaVersionProblem(WorkflowProblem):
13
+ """Problem indicating a workflow has schema version compatibility issues.
14
+
15
+ This is context-specific and likely one-time only per workflow.
16
+ """
17
+
18
+ description: str
19
+
20
+ @classmethod
21
+ def collate_problems_for_display(cls, instances: list[WorkflowSchemaVersionProblem]) -> str:
22
+ """Display workflow schema version problems."""
23
+ if len(instances) > 1:
24
+ logger.error(
25
+ "WorkflowSchemaVersionProblem received %d instances but should typically only receive 1. This may indicate multiple schema issues.",
26
+ len(instances),
27
+ )
28
+
29
+ if len(instances) == 1:
30
+ problem = instances[0]
31
+ return problem.description
32
+
33
+ # Multiple schema issues - list them
34
+ output_lines = []
35
+ output_lines.append(f"Encountered {len(instances)} workflow schema version issues:")
36
+ for i, problem in enumerate(instances, 1):
37
+ output_lines.append(f" {i}. {problem.description}")
38
+
39
+ return "\n".join(output_lines)
@@ -7,6 +7,7 @@ import logging
7
7
  import os
8
8
  import subprocess
9
9
  import sys
10
+ from collections import defaultdict
10
11
  from dataclasses import dataclass
11
12
  from pathlib import Path
12
13
  from typing import TYPE_CHECKING
@@ -108,7 +109,9 @@ class LibraryProvenanceLocalFile(LibraryProvenance):
108
109
  version_issues = GriptapeNodes.VersionCompatibilityManager().check_library_version_compatibility(schema)
109
110
  for issue in version_issues:
110
111
  lifecycle_severity = LibraryStatus(issue.severity.value)
111
- issues.append(LifecycleIssue(message=issue.message, severity=lifecycle_severity))
112
+ # Collate the problem to get the display message
113
+ problem_message = type(issue.problem).collate_problems_for_display([issue.problem])
114
+ issues.append(LifecycleIssue(message=problem_message, severity=lifecycle_severity))
112
115
 
113
116
  # NOTE: Library name conflicts are checked at the manager level
114
117
  # across all evaluated libraries, not here
@@ -225,7 +228,7 @@ class LibraryProvenanceLocalFile(LibraryProvenance):
225
228
  issues=problems,
226
229
  )
227
230
 
228
- def load_library(self, context: LibraryLifecycleContext) -> LibraryLoadedResult:
231
+ def load_library(self, context: LibraryLifecycleContext) -> LibraryLoadedResult: # noqa: C901
229
232
  """Load this local file library into the registry."""
230
233
  issues = []
231
234
 
@@ -333,7 +336,18 @@ class LibraryProvenanceLocalFile(LibraryProvenance):
333
336
 
334
337
  # Convert any problems from library_load_results to issues
335
338
  if library_load_results.problems:
336
- collated_problems = "\n".join(library_load_results.problems)
339
+ # Group problems by type and collate them for display
340
+ problems_by_type = defaultdict(list)
341
+ for problem in library_load_results.problems:
342
+ problems_by_type[type(problem)].append(problem)
343
+
344
+ # Collate each group
345
+ collated_strings = []
346
+ for problem_class, instances in problems_by_type.items():
347
+ collated_display = problem_class.collate_problems_for_display(instances)
348
+ collated_strings.append(collated_display)
349
+
350
+ collated_problems = "\n".join(collated_strings)
337
351
  issues.append(
338
352
  LifecycleIssue(
339
353
  message=collated_problems,