aiecs 1.5.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.
- aiecs/__init__.py +72 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +469 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +363 -0
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +100 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
- aiecs/application/knowledge_graph/search/reranker.py +295 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +484 -0
- aiecs/config/__init__.py +16 -0
- aiecs/config/config.py +498 -0
- aiecs/config/graph_config.py +137 -0
- aiecs/config/registry.py +23 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +152 -0
- aiecs/core/interface/storage_interface.py +171 -0
- aiecs/domain/__init__.py +289 -0
- aiecs/domain/agent/__init__.py +189 -0
- aiecs/domain/agent/base_agent.py +697 -0
- aiecs/domain/agent/exceptions.py +103 -0
- aiecs/domain/agent/graph_aware_mixin.py +559 -0
- aiecs/domain/agent/hybrid_agent.py +490 -0
- aiecs/domain/agent/integration/__init__.py +26 -0
- aiecs/domain/agent/integration/context_compressor.py +222 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
- aiecs/domain/agent/integration/retry_policy.py +219 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +646 -0
- aiecs/domain/agent/lifecycle.py +296 -0
- aiecs/domain/agent/llm_agent.py +300 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +197 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +160 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
- aiecs/domain/agent/models.py +317 -0
- aiecs/domain/agent/observability.py +407 -0
- aiecs/domain/agent/persistence.py +289 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +161 -0
- aiecs/domain/agent/prompts/formatters.py +189 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +260 -0
- aiecs/domain/agent/tool_agent.py +257 -0
- aiecs/domain/agent/tools/__init__.py +12 -0
- aiecs/domain/agent/tools/schema_generator.py +221 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +477 -0
- aiecs/domain/community/analytics.py +481 -0
- aiecs/domain/community/collaborative_workflow.py +642 -0
- aiecs/domain/community/communication_hub.py +645 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +800 -0
- aiecs/domain/community/community_manager.py +813 -0
- aiecs/domain/community/decision_engine.py +879 -0
- aiecs/domain/community/exceptions.py +225 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +268 -0
- aiecs/domain/community/resource_manager.py +457 -0
- aiecs/domain/community/shared_context_manager.py +603 -0
- aiecs/domain/context/__init__.py +58 -0
- aiecs/domain/context/context_engine.py +989 -0
- aiecs/domain/context/conversation_models.py +354 -0
- aiecs/domain/context/graph_memory.py +467 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +57 -0
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +130 -0
- aiecs/domain/knowledge_graph/models/evidence.py +194 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
- aiecs/domain/knowledge_graph/models/path.py +179 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
- aiecs/domain/knowledge_graph/models/query.py +272 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
- aiecs/domain/knowledge_graph/models/relation.py +136 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +613 -0
- aiecs/domain/task/model.py +62 -0
- aiecs/domain/task/task_context.py +268 -0
- aiecs/infrastructure/__init__.py +24 -0
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +601 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
- aiecs/infrastructure/graph_storage/cache.py +429 -0
- aiecs/infrastructure/graph_storage/distributed.py +226 -0
- aiecs/infrastructure/graph_storage/error_handling.py +390 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +514 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
- aiecs/infrastructure/graph_storage/metrics.py +357 -0
- aiecs/infrastructure/graph_storage/migration.py +413 -0
- aiecs/infrastructure/graph_storage/pagination.py +471 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
- aiecs/infrastructure/graph_storage/postgres.py +871 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +623 -0
- aiecs/infrastructure/graph_storage/streaming.py +495 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
- aiecs/infrastructure/messaging/websocket_manager.py +298 -0
- aiecs/infrastructure/monitoring/__init__.py +34 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
- aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
- aiecs/infrastructure/monitoring/structured_logger.py +48 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
- aiecs/infrastructure/persistence/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +187 -0
- aiecs/infrastructure/persistence/database_manager.py +333 -0
- aiecs/infrastructure/persistence/file_storage.py +754 -0
- aiecs/infrastructure/persistence/redis_client.py +220 -0
- aiecs/llm/__init__.py +86 -0
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/callbacks/custom_callbacks.py +264 -0
- aiecs/llm/client_factory.py +420 -0
- aiecs/llm/clients/__init__.py +33 -0
- aiecs/llm/clients/base_client.py +193 -0
- aiecs/llm/clients/googleai_client.py +181 -0
- aiecs/llm/clients/openai_client.py +131 -0
- aiecs/llm/clients/vertex_client.py +437 -0
- aiecs/llm/clients/xai_client.py +184 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +275 -0
- aiecs/llm/config/config_validator.py +236 -0
- aiecs/llm/config/model_config.py +151 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +91 -0
- aiecs/main.py +363 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/version_manager.py +215 -0
- aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
- aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
- aiecs/scripts/dependance_check/__init__.py +17 -0
- aiecs/scripts/dependance_check/dependency_checker.py +938 -0
- aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
- aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
- aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
- aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
- aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
- aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
- aiecs/scripts/tools_develop/README.md +449 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
- aiecs/scripts/tools_develop/verify_tools.py +356 -0
- aiecs/tasks/__init__.py +1 -0
- aiecs/tasks/worker.py +172 -0
- aiecs/tools/__init__.py +299 -0
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +303 -0
- aiecs/tools/apisource/providers/__init__.py +115 -0
- aiecs/tools/apisource/providers/base.py +664 -0
- aiecs/tools/apisource/providers/census.py +401 -0
- aiecs/tools/apisource/providers/fred.py +564 -0
- aiecs/tools/apisource/providers/newsapi.py +412 -0
- aiecs/tools/apisource/providers/worldbank.py +357 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +375 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
- aiecs/tools/apisource/tool.py +850 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +338 -0
- aiecs/tools/base_tool.py +201 -0
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +599 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
- aiecs/tools/docs/content_insertion_tool.py +1333 -0
- aiecs/tools/docs/document_creator_tool.py +1317 -0
- aiecs/tools/docs/document_layout_tool.py +1166 -0
- aiecs/tools/docs/document_parser_tool.py +994 -0
- aiecs/tools/docs/document_writer_tool.py +1818 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
- aiecs/tools/langchain_adapter.py +542 -0
- aiecs/tools/schema_generator.py +275 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +589 -0
- aiecs/tools/search_tool/cache.py +260 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +216 -0
- aiecs/tools/search_tool/core.py +749 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +271 -0
- aiecs/tools/search_tool/metrics.py +371 -0
- aiecs/tools/search_tool/rate_limiter.py +178 -0
- aiecs/tools/search_tool/schemas.py +277 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
- aiecs/tools/statistics/data_loader_tool.py +564 -0
- aiecs/tools/statistics/data_profiler_tool.py +658 -0
- aiecs/tools/statistics/data_transformer_tool.py +573 -0
- aiecs/tools/statistics/data_visualizer_tool.py +495 -0
- aiecs/tools/statistics/model_trainer_tool.py +487 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
- aiecs/tools/task_tools/__init__.py +86 -0
- aiecs/tools/task_tools/chart_tool.py +732 -0
- aiecs/tools/task_tools/classfire_tool.py +922 -0
- aiecs/tools/task_tools/image_tool.py +447 -0
- aiecs/tools/task_tools/office_tool.py +684 -0
- aiecs/tools/task_tools/pandas_tool.py +635 -0
- aiecs/tools/task_tools/report_tool.py +635 -0
- aiecs/tools/task_tools/research_tool.py +392 -0
- aiecs/tools/task_tools/scraper_tool.py +715 -0
- aiecs/tools/task_tools/stats_tool.py +688 -0
- aiecs/tools/temp_file_manager.py +130 -0
- aiecs/tools/tool_executor/__init__.py +37 -0
- aiecs/tools/tool_executor/tool_executor.py +881 -0
- aiecs/utils/LLM_output_structor.py +445 -0
- aiecs/utils/__init__.py +34 -0
- aiecs/utils/base_callback.py +47 -0
- aiecs/utils/cache_provider.py +695 -0
- aiecs/utils/execution_utils.py +184 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +14 -0
- aiecs/utils/token_usage_repository.py +323 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +52 -0
- aiecs-1.5.1.dist-info/METADATA +608 -0
- aiecs-1.5.1.dist-info/RECORD +302 -0
- aiecs-1.5.1.dist-info/WHEEL +5 -0
- aiecs-1.5.1.dist-info/entry_points.txt +10 -0
- aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
- aiecs-1.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Domain Exceptions
|
|
3
|
+
|
|
4
|
+
Defines agent-specific exceptions for the base AI agent model.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentException(Exception):
|
|
11
|
+
"""Base exception for agent-related errors."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: str, agent_id: Optional[str] = None):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.agent_id = agent_id
|
|
16
|
+
self.message = message
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentNotFoundError(AgentException):
|
|
20
|
+
"""Raised when an agent cannot be found."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, agent_id: str, message: Optional[str] = None):
|
|
23
|
+
msg = message or f"Agent with ID '{agent_id}' not found"
|
|
24
|
+
super().__init__(msg, agent_id)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentAlreadyRegisteredError(AgentException):
|
|
28
|
+
"""Raised when attempting to register an agent with an existing ID."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, agent_id: str):
|
|
31
|
+
msg = f"Agent with ID '{agent_id}' is already registered"
|
|
32
|
+
super().__init__(msg, agent_id)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InvalidStateTransitionError(AgentException):
|
|
36
|
+
"""Raised when an invalid agent state transition is attempted."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
agent_id: str,
|
|
41
|
+
current_state: str,
|
|
42
|
+
attempted_state: str,
|
|
43
|
+
message: Optional[str] = None,
|
|
44
|
+
):
|
|
45
|
+
msg = (
|
|
46
|
+
message
|
|
47
|
+
or f"Invalid state transition for agent '{agent_id}': "
|
|
48
|
+
f"cannot transition from '{current_state}' to '{attempted_state}'"
|
|
49
|
+
)
|
|
50
|
+
super().__init__(msg, agent_id)
|
|
51
|
+
self.current_state = current_state
|
|
52
|
+
self.attempted_state = attempted_state
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ConfigurationError(AgentException):
|
|
56
|
+
"""Raised when agent configuration is invalid."""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
message: str,
|
|
61
|
+
agent_id: Optional[str] = None,
|
|
62
|
+
field: Optional[str] = None,
|
|
63
|
+
):
|
|
64
|
+
super().__init__(message, agent_id)
|
|
65
|
+
self.field = field
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TaskExecutionError(AgentException):
|
|
69
|
+
"""Raised when task execution fails."""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
message: str,
|
|
74
|
+
agent_id: Optional[str] = None,
|
|
75
|
+
task_id: Optional[str] = None,
|
|
76
|
+
retry_count: Optional[int] = None,
|
|
77
|
+
):
|
|
78
|
+
super().__init__(message, agent_id)
|
|
79
|
+
self.task_id = task_id
|
|
80
|
+
self.retry_count = retry_count
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ToolAccessDeniedError(AgentException):
|
|
84
|
+
"""Raised when an agent attempts to use a tool it doesn't have access to."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, agent_id: str, tool_name: str):
|
|
87
|
+
msg = f"Agent '{agent_id}' does not have access to tool '{tool_name}'"
|
|
88
|
+
super().__init__(msg, agent_id)
|
|
89
|
+
self.tool_name = tool_name
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SerializationError(AgentException):
|
|
93
|
+
"""Raised when agent serialization/deserialization fails."""
|
|
94
|
+
|
|
95
|
+
def __init__(self, message: str, agent_id: Optional[str] = None):
|
|
96
|
+
super().__init__(message, agent_id)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AgentInitializationError(AgentException):
|
|
100
|
+
"""Raised when agent initialization fails."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, message: str, agent_id: Optional[str] = None):
|
|
103
|
+
super().__init__(message, agent_id)
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graph-Aware Agent Mixin
|
|
3
|
+
|
|
4
|
+
Provides reusable knowledge graph functionality for agents.
|
|
5
|
+
Can be mixed into any agent class to add graph capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, List, Any, Optional, Set
|
|
10
|
+
|
|
11
|
+
from aiecs.domain.knowledge_graph.models.entity import Entity
|
|
12
|
+
from aiecs.domain.knowledge_graph.models.relation import Relation
|
|
13
|
+
from aiecs.domain.knowledge_graph.models.path import Path
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GraphAwareAgentMixin:
|
|
19
|
+
"""
|
|
20
|
+
Mixin to add knowledge graph capabilities to any agent.
|
|
21
|
+
|
|
22
|
+
Provides:
|
|
23
|
+
- Graph store integration
|
|
24
|
+
- Knowledge formatting utilities
|
|
25
|
+
- Graph query helpers
|
|
26
|
+
- Entity/relation utilities
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
class MyAgent(BaseAIAgent, GraphAwareAgentMixin):
|
|
30
|
+
def __init__(self, graph_store, ...):
|
|
31
|
+
super().__init__(...)
|
|
32
|
+
self.graph_store = graph_store
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Note: Assumes the class has self.graph_store: Optional[GraphStore]
|
|
36
|
+
|
|
37
|
+
# ==================== Knowledge Formatting ====================
|
|
38
|
+
|
|
39
|
+
def format_entity(self, entity: Entity, include_properties: bool = True) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Format a single entity as a readable string.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
entity: Entity to format
|
|
45
|
+
include_properties: Whether to include properties
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Formatted string
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> entity = Entity(id="alice", entity_type="Person", properties={"name": "Alice"})
|
|
52
|
+
>>> mixin.format_entity(entity)
|
|
53
|
+
"Person: alice (name=Alice)"
|
|
54
|
+
"""
|
|
55
|
+
parts = [f"{entity.entity_type}: {entity.id}"]
|
|
56
|
+
|
|
57
|
+
if include_properties and entity.properties:
|
|
58
|
+
props_str = ", ".join(f"{k}={v}" for k, v in entity.properties.items())
|
|
59
|
+
parts.append(f"({props_str})")
|
|
60
|
+
|
|
61
|
+
return " ".join(parts)
|
|
62
|
+
|
|
63
|
+
def format_entities(self, entities: List[Entity], max_items: int = 10) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Format a list of entities as a readable string.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
entities: List of entities to format
|
|
69
|
+
max_items: Maximum number of entities to include
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Formatted string with one entity per line
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> entities = [Entity(id="alice", entity_type="Person", ...), ...]
|
|
76
|
+
>>> mixin.format_entities(entities)
|
|
77
|
+
"- Person: alice (name=Alice)\n- Company: tech_corp (name=TechCorp)"
|
|
78
|
+
"""
|
|
79
|
+
if not entities:
|
|
80
|
+
return ""
|
|
81
|
+
|
|
82
|
+
lines = []
|
|
83
|
+
for entity in entities[:max_items]:
|
|
84
|
+
lines.append(f"- {self.format_entity(entity)}")
|
|
85
|
+
|
|
86
|
+
if len(entities) > max_items:
|
|
87
|
+
lines.append(f"... and {len(entities) - max_items} more")
|
|
88
|
+
|
|
89
|
+
return "\n".join(lines)
|
|
90
|
+
|
|
91
|
+
def format_relation(self, relation: Relation, include_entities: bool = False) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Format a relation as a readable string.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
relation: Relation to format
|
|
97
|
+
include_entities: Whether to fetch and include entity details
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Formatted string
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
>>> relation = Relation(id="r1", source_id="alice", target_id="bob", relation_type="KNOWS")
|
|
104
|
+
>>> mixin.format_relation(relation)
|
|
105
|
+
"alice --[KNOWS]--> bob"
|
|
106
|
+
"""
|
|
107
|
+
if include_entities and hasattr(self, "graph_store") and self.graph_store:
|
|
108
|
+
# Fetch entity details (async, but this is a sync method)
|
|
109
|
+
# In production, this would be async
|
|
110
|
+
return f"{relation.source_id} --[{relation.relation_type}]--> {relation.target_id}"
|
|
111
|
+
|
|
112
|
+
return f"{relation.source_id} --[{relation.relation_type}]--> {relation.target_id}"
|
|
113
|
+
|
|
114
|
+
def format_path(self, path: Path, include_properties: bool = False) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Format a graph path as a readable string.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
path: Path to format
|
|
120
|
+
include_properties: Whether to include entity properties
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Formatted string showing the path
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> path = Path(nodes=[e1, e2], edges=[r1], weight=1.0)
|
|
127
|
+
>>> mixin.format_path(path)
|
|
128
|
+
"alice --[KNOWS]--> bob --[WORKS_FOR]--> tech_corp"
|
|
129
|
+
"""
|
|
130
|
+
if not path.nodes:
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
parts = []
|
|
134
|
+
|
|
135
|
+
# Add first node
|
|
136
|
+
parts.append(self.format_entity(path.nodes[0], include_properties))
|
|
137
|
+
|
|
138
|
+
# Add edges and subsequent nodes
|
|
139
|
+
for i, edge in enumerate(path.edges):
|
|
140
|
+
parts.append(f"--[{edge.relation_type}]-->")
|
|
141
|
+
if i + 1 < len(path.nodes):
|
|
142
|
+
parts.append(self.format_entity(path.nodes[i + 1], include_properties))
|
|
143
|
+
|
|
144
|
+
return " ".join(parts)
|
|
145
|
+
|
|
146
|
+
def format_knowledge_summary(
|
|
147
|
+
self,
|
|
148
|
+
entities: List[Entity],
|
|
149
|
+
relations: Optional[List[Relation]] = None,
|
|
150
|
+
max_entities: int = 5,
|
|
151
|
+
max_relations: int = 5,
|
|
152
|
+
) -> str:
|
|
153
|
+
"""
|
|
154
|
+
Format a summary of knowledge (entities and relations).
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
entities: List of entities
|
|
158
|
+
relations: Optional list of relations
|
|
159
|
+
max_entities: Maximum entities to show
|
|
160
|
+
max_relations: Maximum relations to show
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Formatted summary string
|
|
164
|
+
"""
|
|
165
|
+
lines = []
|
|
166
|
+
|
|
167
|
+
if entities:
|
|
168
|
+
lines.append(f"Entities ({len(entities)}):")
|
|
169
|
+
lines.append(self.format_entities(entities, max_items=max_entities))
|
|
170
|
+
|
|
171
|
+
if relations:
|
|
172
|
+
lines.append(f"\nRelations ({len(relations)}):")
|
|
173
|
+
for rel in relations[:max_relations]:
|
|
174
|
+
lines.append(f" {self.format_relation(rel)}")
|
|
175
|
+
if len(relations) > max_relations:
|
|
176
|
+
lines.append(f" ... and {len(relations) - max_relations} more")
|
|
177
|
+
|
|
178
|
+
return "\n".join(lines)
|
|
179
|
+
|
|
180
|
+
# ==================== Graph Query Utilities ====================
|
|
181
|
+
|
|
182
|
+
async def find_entity_by_property(
|
|
183
|
+
self,
|
|
184
|
+
entity_type: Optional[str] = None,
|
|
185
|
+
property_name: str = "name",
|
|
186
|
+
property_value: Any = None,
|
|
187
|
+
limit: int = 10,
|
|
188
|
+
) -> List[Entity]:
|
|
189
|
+
"""
|
|
190
|
+
Find entities by property value.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
entity_type: Optional filter by entity type
|
|
194
|
+
property_name: Property name to search
|
|
195
|
+
property_value: Property value to match
|
|
196
|
+
limit: Maximum results
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of matching entities
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
>>> entities = await mixin.find_entity_by_property(
|
|
203
|
+
... entity_type="Person",
|
|
204
|
+
... property_name="name",
|
|
205
|
+
... property_value="Alice"
|
|
206
|
+
... )
|
|
207
|
+
"""
|
|
208
|
+
if not hasattr(self, "graph_store") or self.graph_store is None:
|
|
209
|
+
logger.warning("GraphStore not available")
|
|
210
|
+
return []
|
|
211
|
+
|
|
212
|
+
# This is a simplified implementation
|
|
213
|
+
# In production, would use proper graph query or filtering
|
|
214
|
+
try:
|
|
215
|
+
# For now, return empty - would need graph query support
|
|
216
|
+
# This is a placeholder for future enhancement
|
|
217
|
+
return []
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(f"Error finding entity by property: {e}")
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
async def get_entity_neighbors(
|
|
223
|
+
self,
|
|
224
|
+
entity_id: str,
|
|
225
|
+
relation_type: Optional[str] = None,
|
|
226
|
+
direction: str = "outgoing",
|
|
227
|
+
limit: int = 10,
|
|
228
|
+
) -> List[Entity]:
|
|
229
|
+
"""
|
|
230
|
+
Get neighboring entities for a given entity.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
entity_id: Entity ID
|
|
234
|
+
relation_type: Optional filter by relation type
|
|
235
|
+
direction: "outgoing", "incoming", or "both"
|
|
236
|
+
limit: Maximum results
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
List of neighboring entities
|
|
240
|
+
|
|
241
|
+
Example:
|
|
242
|
+
>>> neighbors = await mixin.get_entity_neighbors(
|
|
243
|
+
... entity_id="alice",
|
|
244
|
+
... relation_type="KNOWS",
|
|
245
|
+
... direction="outgoing"
|
|
246
|
+
... )
|
|
247
|
+
"""
|
|
248
|
+
if not hasattr(self, "graph_store") or self.graph_store is None:
|
|
249
|
+
logger.warning("GraphStore not available")
|
|
250
|
+
return []
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
neighbors = await self.graph_store.get_neighbors(
|
|
254
|
+
entity_id=entity_id,
|
|
255
|
+
relation_type=relation_type,
|
|
256
|
+
direction=direction,
|
|
257
|
+
)
|
|
258
|
+
return neighbors[:limit]
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Error getting neighbors: {e}")
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
async def find_paths_between(
|
|
264
|
+
self,
|
|
265
|
+
source_id: str,
|
|
266
|
+
target_id: str,
|
|
267
|
+
max_depth: int = 3,
|
|
268
|
+
relation_types: Optional[List[str]] = None,
|
|
269
|
+
) -> List[Path]:
|
|
270
|
+
"""
|
|
271
|
+
Find paths between two entities.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
source_id: Source entity ID
|
|
275
|
+
target_id: Target entity ID
|
|
276
|
+
max_depth: Maximum path depth
|
|
277
|
+
relation_types: Optional filter by relation types
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
List of paths
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
>>> paths = await mixin.find_paths_between(
|
|
284
|
+
... source_id="alice",
|
|
285
|
+
... target_id="tech_corp",
|
|
286
|
+
... max_depth=2
|
|
287
|
+
... )
|
|
288
|
+
"""
|
|
289
|
+
if not hasattr(self, "graph_store") or self.graph_store is None:
|
|
290
|
+
logger.warning("GraphStore not available")
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
# Use graph store's find_paths method
|
|
295
|
+
paths = await self.graph_store.find_paths(
|
|
296
|
+
source_entity_id=source_id,
|
|
297
|
+
target_entity_id=target_id,
|
|
298
|
+
max_depth=max_depth,
|
|
299
|
+
max_paths=10,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Filter by relation types if specified
|
|
303
|
+
if relation_types:
|
|
304
|
+
filtered_paths = []
|
|
305
|
+
for path in paths:
|
|
306
|
+
# Check if all edges match relation types
|
|
307
|
+
if all(edge.relation_type in relation_types for edge in path.edges):
|
|
308
|
+
filtered_paths.append(path)
|
|
309
|
+
return filtered_paths
|
|
310
|
+
|
|
311
|
+
return paths
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(f"Error finding paths: {e}")
|
|
314
|
+
return []
|
|
315
|
+
|
|
316
|
+
async def get_entity_subgraph(
|
|
317
|
+
self,
|
|
318
|
+
entity_id: str,
|
|
319
|
+
max_depth: int = 2,
|
|
320
|
+
relation_types: Optional[List[str]] = None,
|
|
321
|
+
) -> Dict[str, Any]:
|
|
322
|
+
"""
|
|
323
|
+
Get a subgraph centered around an entity.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
entity_id: Center entity ID
|
|
327
|
+
max_depth: Maximum depth for traversal
|
|
328
|
+
relation_types: Optional filter by relation types
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Dictionary with 'entities' and 'relations' lists
|
|
332
|
+
|
|
333
|
+
Example:
|
|
334
|
+
>>> subgraph = await mixin.get_entity_subgraph(
|
|
335
|
+
... entity_id="alice",
|
|
336
|
+
... max_depth=2
|
|
337
|
+
... )
|
|
338
|
+
>>> print(f"Entities: {len(subgraph['entities'])}")
|
|
339
|
+
"""
|
|
340
|
+
if not hasattr(self, "graph_store") or self.graph_store is None:
|
|
341
|
+
logger.warning("GraphStore not available")
|
|
342
|
+
return {"entities": [], "relations": []}
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
entities = []
|
|
346
|
+
relations = []
|
|
347
|
+
visited: Set[str] = {entity_id}
|
|
348
|
+
|
|
349
|
+
# Get center entity
|
|
350
|
+
center = await self.graph_store.get_entity(entity_id)
|
|
351
|
+
if not center:
|
|
352
|
+
return {"entities": [], "relations": []}
|
|
353
|
+
|
|
354
|
+
entities.append(center)
|
|
355
|
+
current_level = [entity_id]
|
|
356
|
+
|
|
357
|
+
for depth in range(max_depth):
|
|
358
|
+
next_level = []
|
|
359
|
+
|
|
360
|
+
for e_id in current_level:
|
|
361
|
+
neighbors = await self.graph_store.get_neighbors(
|
|
362
|
+
entity_id=e_id,
|
|
363
|
+
relation_type=None, # Get all relation types
|
|
364
|
+
direction="both",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
for neighbor in neighbors:
|
|
368
|
+
if neighbor.id not in visited:
|
|
369
|
+
visited.add(neighbor.id)
|
|
370
|
+
entities.append(neighbor)
|
|
371
|
+
next_level.append(neighbor.id)
|
|
372
|
+
|
|
373
|
+
# Note: In a full implementation, we'd also collect relations
|
|
374
|
+
# This is simplified
|
|
375
|
+
|
|
376
|
+
current_level = next_level
|
|
377
|
+
if not current_level:
|
|
378
|
+
break
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
"entities": [e.model_dump() for e in entities],
|
|
382
|
+
"relations": relations,
|
|
383
|
+
}
|
|
384
|
+
except Exception as e:
|
|
385
|
+
logger.error(f"Error getting subgraph: {e}")
|
|
386
|
+
return {"entities": [], "relations": []}
|
|
387
|
+
|
|
388
|
+
async def search_entities(
|
|
389
|
+
self,
|
|
390
|
+
query: Optional[str] = None,
|
|
391
|
+
entity_types: Optional[List[str]] = None,
|
|
392
|
+
limit: int = 10,
|
|
393
|
+
threshold: float = 0.7,
|
|
394
|
+
) -> List[Entity]:
|
|
395
|
+
"""
|
|
396
|
+
Search for entities using vector search or filtering.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
query: Optional search query (for vector search)
|
|
400
|
+
entity_types: Optional filter by entity types
|
|
401
|
+
limit: Maximum results
|
|
402
|
+
threshold: Similarity threshold for vector search
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
List of matching entities
|
|
406
|
+
|
|
407
|
+
Example:
|
|
408
|
+
>>> entities = await mixin.search_entities(
|
|
409
|
+
... query="engineer at tech company",
|
|
410
|
+
... entity_types=["Person"],
|
|
411
|
+
... limit=5
|
|
412
|
+
... )
|
|
413
|
+
"""
|
|
414
|
+
if not hasattr(self, "graph_store") or self.graph_store is None:
|
|
415
|
+
logger.warning("GraphStore not available")
|
|
416
|
+
return []
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
# If query provided, try vector search
|
|
420
|
+
if query:
|
|
421
|
+
# Note: This would require embedding generation
|
|
422
|
+
# For now, this is a placeholder
|
|
423
|
+
# In production: generate embedding and use vector_search
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
# For now, return empty
|
|
427
|
+
# In production, would use vector_search or filtering
|
|
428
|
+
return []
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.error(f"Error searching entities: {e}")
|
|
431
|
+
return []
|
|
432
|
+
|
|
433
|
+
# ==================== Knowledge Context Utilities ====================
|
|
434
|
+
|
|
435
|
+
def extract_entity_mentions(self, text: str) -> List[str]:
|
|
436
|
+
"""
|
|
437
|
+
Extract potential entity mentions from text.
|
|
438
|
+
|
|
439
|
+
Simple implementation - in production would use NER or more sophisticated methods.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
text: Text to analyze
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
List of potential entity IDs or names
|
|
446
|
+
|
|
447
|
+
Example:
|
|
448
|
+
>>> mentions = mixin.extract_entity_mentions("Alice works at TechCorp")
|
|
449
|
+
>>> # Returns: ["Alice", "TechCorp"]
|
|
450
|
+
"""
|
|
451
|
+
# Simple implementation - split by common delimiters
|
|
452
|
+
# In production, would use NER or entity linking
|
|
453
|
+
words = text.split()
|
|
454
|
+
mentions = []
|
|
455
|
+
|
|
456
|
+
# Look for capitalized words (potential entity names)
|
|
457
|
+
for word in words:
|
|
458
|
+
if word and word[0].isupper() and len(word) > 1:
|
|
459
|
+
mentions.append(word.strip(".,!?;:"))
|
|
460
|
+
|
|
461
|
+
return mentions
|
|
462
|
+
|
|
463
|
+
def build_knowledge_context_prompt(
|
|
464
|
+
self,
|
|
465
|
+
entities: List[Entity],
|
|
466
|
+
relations: Optional[List[Relation]] = None,
|
|
467
|
+
max_length: int = 500,
|
|
468
|
+
) -> str:
|
|
469
|
+
"""
|
|
470
|
+
Build a prompt section with knowledge context.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
entities: List of entities to include
|
|
474
|
+
relations: Optional list of relations
|
|
475
|
+
max_length: Maximum length of formatted text
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
Formatted prompt section
|
|
479
|
+
|
|
480
|
+
Example:
|
|
481
|
+
>>> prompt = mixin.build_knowledge_context_prompt(
|
|
482
|
+
... entities=[alice, bob],
|
|
483
|
+
... relations=[knows_rel]
|
|
484
|
+
... )
|
|
485
|
+
>>> # Returns formatted string for inclusion in prompt
|
|
486
|
+
"""
|
|
487
|
+
lines = ["RELEVANT KNOWLEDGE:"]
|
|
488
|
+
|
|
489
|
+
# Add entities
|
|
490
|
+
if entities:
|
|
491
|
+
lines.append("\nEntities:")
|
|
492
|
+
entity_text = self.format_entities(entities, max_items=5)
|
|
493
|
+
lines.append(entity_text)
|
|
494
|
+
|
|
495
|
+
# Add relations
|
|
496
|
+
if relations:
|
|
497
|
+
lines.append("\nRelations:")
|
|
498
|
+
for rel in relations[:5]:
|
|
499
|
+
lines.append(f" {self.format_relation(rel)}")
|
|
500
|
+
|
|
501
|
+
full_text = "\n".join(lines)
|
|
502
|
+
|
|
503
|
+
# Truncate if too long
|
|
504
|
+
if len(full_text) > max_length:
|
|
505
|
+
full_text = full_text[:max_length] + "..."
|
|
506
|
+
|
|
507
|
+
return full_text
|
|
508
|
+
|
|
509
|
+
def validate_graph_store(self) -> bool:
|
|
510
|
+
"""
|
|
511
|
+
Validate that graph store is available and initialized.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
True if graph store is available, False otherwise
|
|
515
|
+
"""
|
|
516
|
+
if not hasattr(self, "graph_store"):
|
|
517
|
+
return False
|
|
518
|
+
|
|
519
|
+
if self.graph_store is None:
|
|
520
|
+
return False
|
|
521
|
+
|
|
522
|
+
# Could add more validation (e.g., ping the store)
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
def get_graph_stats(self) -> Dict[str, Any]:
|
|
526
|
+
"""
|
|
527
|
+
Get statistics about the knowledge graph.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
Dictionary with graph statistics
|
|
531
|
+
|
|
532
|
+
Example:
|
|
533
|
+
>>> stats = mixin.get_graph_stats()
|
|
534
|
+
>>> print(f"Entities: {stats['entity_count']}")
|
|
535
|
+
"""
|
|
536
|
+
if not self.validate_graph_store():
|
|
537
|
+
return {"available": False, "entity_count": 0, "relation_count": 0}
|
|
538
|
+
|
|
539
|
+
try:
|
|
540
|
+
# Use graph store's get_stats if available
|
|
541
|
+
if hasattr(self.graph_store, "get_stats"):
|
|
542
|
+
stats = self.graph_store.get_stats()
|
|
543
|
+
# Normalize stats format
|
|
544
|
+
return {
|
|
545
|
+
"available": True,
|
|
546
|
+
"entity_count": stats.get("entities", stats.get("nodes", "unknown")),
|
|
547
|
+
"relation_count": stats.get("relations", stats.get("edges", "unknown")),
|
|
548
|
+
**stats, # Include all original stats
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
# Otherwise return basic info
|
|
552
|
+
return {
|
|
553
|
+
"available": True,
|
|
554
|
+
"entity_count": "unknown",
|
|
555
|
+
"relation_count": "unknown",
|
|
556
|
+
}
|
|
557
|
+
except Exception as e:
|
|
558
|
+
logger.error(f"Error getting graph stats: {e}")
|
|
559
|
+
return {"available": False, "error": str(e)}
|