devsper 2.1.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.
- devsper/__init__.py +14 -0
- devsper/agents/a2a/__init__.py +27 -0
- devsper/agents/a2a/client.py +126 -0
- devsper/agents/a2a/discovery.py +24 -0
- devsper/agents/a2a/server.py +128 -0
- devsper/agents/a2a/tool_adapter.py +68 -0
- devsper/agents/a2a/types.py +49 -0
- devsper/agents/agent.py +602 -0
- devsper/agents/critic.py +80 -0
- devsper/agents/message_bus.py +124 -0
- devsper/agents/roles.py +181 -0
- devsper/agents/run_agent.py +78 -0
- devsper/analytics/__init__.py +5 -0
- devsper/analytics/tool_analytics.py +78 -0
- devsper/audit/__init__.py +5 -0
- devsper/audit/logger.py +214 -0
- devsper/bus/__init__.py +29 -0
- devsper/bus/backends/__init__.py +5 -0
- devsper/bus/backends/base.py +38 -0
- devsper/bus/backends/memory.py +55 -0
- devsper/bus/backends/redis.py +146 -0
- devsper/bus/message.py +56 -0
- devsper/bus/schema_version.py +3 -0
- devsper/bus/topics.py +19 -0
- devsper/cache/__init__.py +6 -0
- devsper/cache/embedding_index.py +98 -0
- devsper/cache/hashing.py +24 -0
- devsper/cache/store.py +153 -0
- devsper/cache/task_cache.py +191 -0
- devsper/cli/__init__.py +6 -0
- devsper/cli/commands/reg.py +733 -0
- devsper/cli/github_oauth.py +157 -0
- devsper/cli/init.py +637 -0
- devsper/cli/main.py +2956 -0
- devsper/cli/run_progress.py +103 -0
- devsper/cli/ui/__init__.py +65 -0
- devsper/cli/ui/components.py +94 -0
- devsper/cli/ui/errors.py +104 -0
- devsper/cli/ui/logging.py +120 -0
- devsper/cli/ui/onboarding.py +102 -0
- devsper/cli/ui/progress.py +43 -0
- devsper/cli/ui/run_view.py +308 -0
- devsper/cli/ui/theme.py +40 -0
- devsper/cluster/__init__.py +29 -0
- devsper/cluster/election.py +84 -0
- devsper/cluster/local.py +97 -0
- devsper/cluster/node_info.py +77 -0
- devsper/cluster/registry.py +71 -0
- devsper/cluster/router.py +117 -0
- devsper/cluster/state_backend.py +105 -0
- devsper/compliance/__init__.py +5 -0
- devsper/compliance/pii.py +147 -0
- devsper/config/__init__.py +52 -0
- devsper/config/config_loader.py +121 -0
- devsper/config/defaults.py +77 -0
- devsper/config/resolver.py +342 -0
- devsper/config/schema.py +237 -0
- devsper/credentials/__init__.py +19 -0
- devsper/credentials/cli.py +197 -0
- devsper/credentials/migration.py +124 -0
- devsper/credentials/store.py +142 -0
- devsper/dashboard/__init__.py +9 -0
- devsper/dashboard/dashboard.py +87 -0
- devsper/dev/__init__.py +25 -0
- devsper/dev/builder.py +195 -0
- devsper/dev/debugger.py +95 -0
- devsper/dev/repo_index.py +138 -0
- devsper/dev/sandbox.py +203 -0
- devsper/dev/scaffold.py +122 -0
- devsper/embeddings/__init__.py +5 -0
- devsper/embeddings/service.py +36 -0
- devsper/explainability/__init__.py +14 -0
- devsper/explainability/decision_tree.py +104 -0
- devsper/explainability/rationale.py +38 -0
- devsper/explainability/simulation.py +56 -0
- devsper/hitl/__init__.py +13 -0
- devsper/hitl/approval.py +160 -0
- devsper/hitl/escalation.py +95 -0
- devsper/intelligence/__init__.py +9 -0
- devsper/intelligence/adaptation.py +88 -0
- devsper/intelligence/analysis/__init__.py +19 -0
- devsper/intelligence/analysis/analyzer.py +71 -0
- devsper/intelligence/analysis/cost_estimator.py +66 -0
- devsper/intelligence/analysis/formatter.py +103 -0
- devsper/intelligence/analysis/run_report.py +402 -0
- devsper/intelligence/learning_engine.py +92 -0
- devsper/intelligence/strategies/__init__.py +23 -0
- devsper/intelligence/strategies/base.py +14 -0
- devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
- devsper/intelligence/strategies/data_science_strategy.py +33 -0
- devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
- devsper/intelligence/strategies/experiment_strategy.py +33 -0
- devsper/intelligence/strategies/research_strategy.py +34 -0
- devsper/intelligence/strategy_selector.py +84 -0
- devsper/intelligence/synthesis.py +132 -0
- devsper/intelligence/task_optimizer.py +92 -0
- devsper/knowledge/__init__.py +5 -0
- devsper/knowledge/extractor.py +204 -0
- devsper/knowledge/knowledge_graph.py +184 -0
- devsper/knowledge/query.py +285 -0
- devsper/memory/__init__.py +35 -0
- devsper/memory/consolidation.py +138 -0
- devsper/memory/embeddings.py +60 -0
- devsper/memory/memory_index.py +97 -0
- devsper/memory/memory_router.py +62 -0
- devsper/memory/memory_store.py +221 -0
- devsper/memory/memory_types.py +54 -0
- devsper/memory/namespaces.py +45 -0
- devsper/memory/scoring.py +77 -0
- devsper/memory/summarizer.py +52 -0
- devsper/nodes/__init__.py +5 -0
- devsper/nodes/controller.py +449 -0
- devsper/nodes/rpc.py +127 -0
- devsper/nodes/single.py +161 -0
- devsper/nodes/worker.py +506 -0
- devsper/orchestration/__init__.py +19 -0
- devsper/orchestration/meta_planner.py +239 -0
- devsper/orchestration/priority_queue.py +61 -0
- devsper/plugins/__init__.py +19 -0
- devsper/plugins/marketplace/__init__.py +0 -0
- devsper/plugins/plugin_loader.py +70 -0
- devsper/plugins/plugin_registry.py +34 -0
- devsper/plugins/registry.py +83 -0
- devsper/protocols/__init__.py +6 -0
- devsper/providers/__init__.py +17 -0
- devsper/providers/anthropic.py +84 -0
- devsper/providers/base.py +75 -0
- devsper/providers/complexity_router.py +94 -0
- devsper/providers/gemini.py +36 -0
- devsper/providers/github.py +180 -0
- devsper/providers/model_router.py +40 -0
- devsper/providers/openai.py +105 -0
- devsper/providers/router/__init__.py +21 -0
- devsper/providers/router/backends/__init__.py +19 -0
- devsper/providers/router/backends/anthropic_backend.py +111 -0
- devsper/providers/router/backends/custom_backend.py +138 -0
- devsper/providers/router/backends/gemini_backend.py +89 -0
- devsper/providers/router/backends/github_backend.py +165 -0
- devsper/providers/router/backends/ollama_backend.py +104 -0
- devsper/providers/router/backends/openai_backend.py +142 -0
- devsper/providers/router/backends/vllm_backend.py +35 -0
- devsper/providers/router/base.py +60 -0
- devsper/providers/router/factory.py +92 -0
- devsper/providers/router/legacy.py +101 -0
- devsper/providers/router/router.py +135 -0
- devsper/reasoning/__init__.py +12 -0
- devsper/reasoning/graph.py +59 -0
- devsper/reasoning/nodes.py +20 -0
- devsper/reasoning/store.py +67 -0
- devsper/runtime/__init__.py +12 -0
- devsper/runtime/health.py +88 -0
- devsper/runtime/replay.py +53 -0
- devsper/runtime/replay_engine.py +142 -0
- devsper/runtime/run_history.py +204 -0
- devsper/runtime/telemetry.py +116 -0
- devsper/runtime/visualize.py +58 -0
- devsper/sandbox/__init__.py +13 -0
- devsper/sandbox/sandbox.py +161 -0
- devsper/swarm/checkpointer.py +65 -0
- devsper/swarm/executor.py +558 -0
- devsper/swarm/map_reduce.py +44 -0
- devsper/swarm/planner.py +197 -0
- devsper/swarm/prefetcher.py +91 -0
- devsper/swarm/scheduler.py +153 -0
- devsper/swarm/speculation.py +47 -0
- devsper/swarm/swarm.py +562 -0
- devsper/tools/__init__.py +33 -0
- devsper/tools/base.py +29 -0
- devsper/tools/code_intelligence/__init__.py +13 -0
- devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
- devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
- devsper/tools/code_intelligence/codebase_indexer.py +71 -0
- devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
- devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
- devsper/tools/code_intelligence/large_function_detector.py +68 -0
- devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
- devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
- devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
- devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
- devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
- devsper/tools/coding/__init__.py +12 -0
- devsper/tools/coding/analyze_code_complexity.py +48 -0
- devsper/tools/coding/dependency_analyzer.py +42 -0
- devsper/tools/coding/extract_functions.py +38 -0
- devsper/tools/coding/format_python.py +50 -0
- devsper/tools/coding/generate_docstrings.py +40 -0
- devsper/tools/coding/generate_unit_tests.py +42 -0
- devsper/tools/coding/lint_python.py +51 -0
- devsper/tools/coding/refactor_function.py +41 -0
- devsper/tools/coding/repo_structure_map.py +54 -0
- devsper/tools/coding/run_python.py +53 -0
- devsper/tools/data/__init__.py +12 -0
- devsper/tools/data/column_type_detection.py +64 -0
- devsper/tools/data/csv_summary.py +52 -0
- devsper/tools/data/dataframe_filter.py +51 -0
- devsper/tools/data/dataframe_groupby.py +47 -0
- devsper/tools/data/dataframe_stats.py +38 -0
- devsper/tools/data/dataset_sampling.py +55 -0
- devsper/tools/data/dataset_schema.py +45 -0
- devsper/tools/data/json_pretty_print.py +37 -0
- devsper/tools/data/json_query.py +46 -0
- devsper/tools/data/missing_value_report.py +47 -0
- devsper/tools/data_science/__init__.py +13 -0
- devsper/tools/data_science/correlation_heatmap.py +72 -0
- devsper/tools/data_science/dataset_bias_detector.py +49 -0
- devsper/tools/data_science/dataset_distribution_report.py +64 -0
- devsper/tools/data_science/dataset_drift_detector.py +64 -0
- devsper/tools/data_science/dataset_outlier_detector.py +65 -0
- devsper/tools/data_science/dataset_profile.py +76 -0
- devsper/tools/data_science/distributed_dataset_processor.py +54 -0
- devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
- devsper/tools/data_science/feature_importance_estimator.py +82 -0
- devsper/tools/data_science/model_input_validator.py +59 -0
- devsper/tools/data_science/time_series_analyzer.py +57 -0
- devsper/tools/documents/__init__.py +11 -0
- devsper/tools/documents/_docproc.py +56 -0
- devsper/tools/documents/document_to_markdown.py +29 -0
- devsper/tools/documents/extract_document_images.py +39 -0
- devsper/tools/documents/extract_document_text.py +29 -0
- devsper/tools/documents/extract_equations.py +36 -0
- devsper/tools/documents/extract_tables.py +47 -0
- devsper/tools/documents/summarize_document.py +42 -0
- devsper/tools/documents/write_latex_document.py +133 -0
- devsper/tools/documents/write_markdown_document.py +89 -0
- devsper/tools/documents/write_word_document.py +149 -0
- devsper/tools/experiments/__init__.py +13 -0
- devsper/tools/experiments/bootstrap_estimator.py +54 -0
- devsper/tools/experiments/experiment_report_generator.py +50 -0
- devsper/tools/experiments/experiment_tracker.py +36 -0
- devsper/tools/experiments/grid_search_runner.py +50 -0
- devsper/tools/experiments/model_benchmark_runner.py +45 -0
- devsper/tools/experiments/monte_carlo_experiment.py +38 -0
- devsper/tools/experiments/parameter_sweep_runner.py +51 -0
- devsper/tools/experiments/result_comparator.py +58 -0
- devsper/tools/experiments/simulation_runner.py +43 -0
- devsper/tools/experiments/statistical_significance_test.py +56 -0
- devsper/tools/experiments/swarm_map_reduce.py +42 -0
- devsper/tools/filesystem/__init__.py +12 -0
- devsper/tools/filesystem/append_file.py +42 -0
- devsper/tools/filesystem/file_hash.py +40 -0
- devsper/tools/filesystem/file_line_count.py +36 -0
- devsper/tools/filesystem/file_metadata.py +38 -0
- devsper/tools/filesystem/file_preview.py +55 -0
- devsper/tools/filesystem/find_large_files.py +50 -0
- devsper/tools/filesystem/list_directory.py +39 -0
- devsper/tools/filesystem/read_file.py +35 -0
- devsper/tools/filesystem/search_files.py +60 -0
- devsper/tools/filesystem/write_file.py +41 -0
- devsper/tools/flagship/__init__.py +15 -0
- devsper/tools/flagship/distributed_document_analysis.py +77 -0
- devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
- devsper/tools/flagship/repository_semantic_map.py +99 -0
- devsper/tools/flagship/research_graph_builder.py +111 -0
- devsper/tools/flagship/swarm_experiment_runner.py +86 -0
- devsper/tools/knowledge/__init__.py +10 -0
- devsper/tools/knowledge/citation_graph_builder.py +69 -0
- devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
- devsper/tools/knowledge/corpus_builder.py +66 -0
- devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
- devsper/tools/knowledge/document_corpus_summary.py +68 -0
- devsper/tools/knowledge/document_topic_extractor.py +58 -0
- devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
- devsper/tools/knowledge/timeline_extractor.py +59 -0
- devsper/tools/math/__init__.py +12 -0
- devsper/tools/math/calculate_expression.py +52 -0
- devsper/tools/math/correlation.py +44 -0
- devsper/tools/math/distribution_summary.py +39 -0
- devsper/tools/math/histogram.py +53 -0
- devsper/tools/math/linear_regression.py +47 -0
- devsper/tools/math/matrix_multiply.py +38 -0
- devsper/tools/math/mean_std.py +35 -0
- devsper/tools/math/monte_carlo_simulation.py +43 -0
- devsper/tools/math/polynomial_fit.py +40 -0
- devsper/tools/math/random_sample.py +36 -0
- devsper/tools/mcp/__init__.py +23 -0
- devsper/tools/mcp/adapter.py +53 -0
- devsper/tools/mcp/client.py +235 -0
- devsper/tools/mcp/discovery.py +53 -0
- devsper/tools/memory/__init__.py +16 -0
- devsper/tools/memory/delete_memory.py +25 -0
- devsper/tools/memory/list_memory.py +34 -0
- devsper/tools/memory/search_memory.py +36 -0
- devsper/tools/memory/store_memory.py +47 -0
- devsper/tools/memory/summarize_memory.py +41 -0
- devsper/tools/memory/tag_memory.py +47 -0
- devsper/tools/pipelines.py +92 -0
- devsper/tools/registry.py +39 -0
- devsper/tools/research/__init__.py +12 -0
- devsper/tools/research/arxiv_download.py +55 -0
- devsper/tools/research/arxiv_search.py +58 -0
- devsper/tools/research/citation_extractor.py +35 -0
- devsper/tools/research/duckduckgo_search.py +42 -0
- devsper/tools/research/paper_metadata_extractor.py +45 -0
- devsper/tools/research/paper_summarizer.py +41 -0
- devsper/tools/research/research_question_generator.py +39 -0
- devsper/tools/research/topic_cluster.py +46 -0
- devsper/tools/research/web_search.py +47 -0
- devsper/tools/research/wikipedia_lookup.py +50 -0
- devsper/tools/research_advanced/__init__.py +14 -0
- devsper/tools/research_advanced/citation_context_extractor.py +60 -0
- devsper/tools/research_advanced/literature_review_generator.py +79 -0
- devsper/tools/research_advanced/methodology_extractor.py +58 -0
- devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
- devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
- devsper/tools/research_advanced/paper_method_comparator.py +62 -0
- devsper/tools/research_advanced/paper_similarity_search.py +69 -0
- devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
- devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
- devsper/tools/research_advanced/research_gap_finder.py +71 -0
- devsper/tools/research_advanced/research_topic_mapper.py +69 -0
- devsper/tools/research_advanced/swarm_literature_review.py +58 -0
- devsper/tools/scoring/__init__.py +52 -0
- devsper/tools/scoring/report.py +44 -0
- devsper/tools/scoring/scorer.py +39 -0
- devsper/tools/scoring/selector.py +61 -0
- devsper/tools/scoring/store.py +267 -0
- devsper/tools/selector.py +130 -0
- devsper/tools/system/__init__.py +12 -0
- devsper/tools/system/cpu_usage.py +22 -0
- devsper/tools/system/disk_usage.py +35 -0
- devsper/tools/system/environment_variables.py +29 -0
- devsper/tools/system/memory_usage.py +23 -0
- devsper/tools/system/pip_install.py +44 -0
- devsper/tools/system/pip_search.py +29 -0
- devsper/tools/system/process_list.py +34 -0
- devsper/tools/system/python_package_list.py +40 -0
- devsper/tools/system/run_shell_command.py +51 -0
- devsper/tools/system/system_info.py +26 -0
- devsper/tools/tool_runner.py +122 -0
- devsper/tui/__init__.py +5 -0
- devsper/tui/activity_feed_view.py +73 -0
- devsper/tui/adaptive_tasks_view.py +75 -0
- devsper/tui/agent_role_view.py +35 -0
- devsper/tui/app.py +395 -0
- devsper/tui/dashboard_screen.py +290 -0
- devsper/tui/dev_view.py +99 -0
- devsper/tui/inject_screen.py +73 -0
- devsper/tui/knowledge_graph_view.py +46 -0
- devsper/tui/layout.py +43 -0
- devsper/tui/logs_view.py +83 -0
- devsper/tui/memory_view.py +58 -0
- devsper/tui/performance_view.py +33 -0
- devsper/tui/reasoning_graph_view.py +39 -0
- devsper/tui/results_view.py +139 -0
- devsper/tui/swarm_view.py +37 -0
- devsper/tui/task_detail_screen.py +55 -0
- devsper/tui/task_view.py +103 -0
- devsper/types/event.py +97 -0
- devsper/types/exceptions.py +21 -0
- devsper/types/swarm.py +41 -0
- devsper/types/task.py +80 -0
- devsper/upgrade/__init__.py +21 -0
- devsper/upgrade/changelog.py +124 -0
- devsper/upgrade/cli.py +145 -0
- devsper/upgrade/installer.py +103 -0
- devsper/upgrade/notifier.py +52 -0
- devsper/upgrade/version_check.py +121 -0
- devsper/utils/event_logger.py +88 -0
- devsper/utils/http.py +43 -0
- devsper/utils/models.py +54 -0
- devsper/visualization/__init__.py +5 -0
- devsper/visualization/dag_export.py +67 -0
- devsper/workflow/__init__.py +18 -0
- devsper/workflow/conditions.py +157 -0
- devsper/workflow/context.py +108 -0
- devsper/workflow/loader.py +156 -0
- devsper/workflow/resolver.py +109 -0
- devsper/workflow/runner.py +562 -0
- devsper/workflow/schema.py +63 -0
- devsper/workflow/validator.py +128 -0
- devsper-2.1.6.dist-info/METADATA +346 -0
- devsper-2.1.6.dist-info/RECORD +375 -0
- devsper-2.1.6.dist-info/WHEEL +4 -0
- devsper-2.1.6.dist-info/entry_points.txt +3 -0
- devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Delete a memory entry by id."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
from devsper.memory.memory_store import get_default_store
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeleteMemoryTool(Tool):
|
|
9
|
+
name = "delete_memory"
|
|
10
|
+
description = "Delete a stored memory entry by its id."
|
|
11
|
+
input_schema = {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"memory_id": {"type": "string", "description": "Id of the memory to delete"},
|
|
15
|
+
},
|
|
16
|
+
"required": ["memory_id"],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def run(self, **kwargs) -> str:
|
|
20
|
+
memory_id = kwargs.get("memory_id")
|
|
21
|
+
if not memory_id or not isinstance(memory_id, str):
|
|
22
|
+
return "Error: memory_id must be a non-empty string"
|
|
23
|
+
store = get_default_store()
|
|
24
|
+
deleted = store.delete(memory_id)
|
|
25
|
+
return "Deleted." if deleted else "Memory not found."
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""List stored memory entries with optional type filter."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
from devsper.memory.memory_store import get_default_store
|
|
6
|
+
from devsper.memory.memory_types import MemoryType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ListMemoryTool(Tool):
|
|
10
|
+
name = "list_memory"
|
|
11
|
+
description = "List stored memory entries. Optionally filter by type and limit."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"memory_type": {"type": "string", "description": "Optional: episodic, semantic, artifact, research"},
|
|
16
|
+
"limit": {"type": "integer", "description": "Max entries to return (default 20)"},
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def run(self, **kwargs) -> str:
|
|
21
|
+
store = get_default_store()
|
|
22
|
+
mt = kwargs.get("memory_type")
|
|
23
|
+
limit = kwargs.get("limit", 20)
|
|
24
|
+
if not isinstance(limit, int) or limit < 1:
|
|
25
|
+
limit = 20
|
|
26
|
+
try:
|
|
27
|
+
memory_type = MemoryType(mt.lower()) if mt else None
|
|
28
|
+
except (ValueError, AttributeError):
|
|
29
|
+
memory_type = None
|
|
30
|
+
records = store.list_memory(memory_type=memory_type, limit=limit)
|
|
31
|
+
if not records:
|
|
32
|
+
return "No memory entries."
|
|
33
|
+
lines = [f"- {r.id} [{r.memory_type.value}] {r.content[:150]}{'...' if len(r.content) > 150 else ''}" for r in records]
|
|
34
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Semantic search over stored memory."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
from devsper.memory.memory_store import get_default_store
|
|
6
|
+
from devsper.memory.memory_index import MemoryIndex
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SearchMemoryTool(Tool):
|
|
10
|
+
name = "search_memory"
|
|
11
|
+
description = "Search stored memory by semantic similarity to a query text. Returns top matches."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"query": {"type": "string", "description": "Search query"},
|
|
16
|
+
"top_k": {"type": "integer", "description": "Max number of results (default 5)"},
|
|
17
|
+
},
|
|
18
|
+
"required": ["query"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def run(self, **kwargs) -> str:
|
|
22
|
+
query = kwargs.get("query", "")
|
|
23
|
+
if not query or not isinstance(query, str):
|
|
24
|
+
return "Error: query must be a non-empty string"
|
|
25
|
+
top_k = kwargs.get("top_k", 5)
|
|
26
|
+
if not isinstance(top_k, int) or top_k < 1:
|
|
27
|
+
top_k = 5
|
|
28
|
+
store = get_default_store()
|
|
29
|
+
index = MemoryIndex(store)
|
|
30
|
+
records = index.query_memory(query, top_k=top_k)
|
|
31
|
+
if not records:
|
|
32
|
+
return "No matching memory found."
|
|
33
|
+
lines = []
|
|
34
|
+
for r in records:
|
|
35
|
+
lines.append(f"[{r.id}] ({r.memory_type.value}) {r.content[:300]}{'...' if len(r.content) > 300 else ''}")
|
|
36
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Store a memory entry (content, type, tags, source_task)."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
from devsper.memory.memory_store import get_default_store, generate_memory_id
|
|
6
|
+
from devsper.memory.memory_types import MemoryRecord, MemoryType
|
|
7
|
+
from devsper.memory.memory_index import MemoryIndex
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StoreMemoryTool(Tool):
|
|
11
|
+
name = "store_memory"
|
|
12
|
+
description = "Store a memory entry with content, type (episodic/semantic/artifact/research), optional tags and source_task."
|
|
13
|
+
input_schema = {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {
|
|
16
|
+
"content": {"type": "string", "description": "Content to store"},
|
|
17
|
+
"memory_type": {"type": "string", "description": "One of: episodic, semantic, artifact, research"},
|
|
18
|
+
"tags": {"type": "array", "items": {"type": "string"}, "description": "Optional tags"},
|
|
19
|
+
"source_task": {"type": "string", "description": "Optional source task id or description"},
|
|
20
|
+
},
|
|
21
|
+
"required": ["content"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def run(self, **kwargs) -> str:
|
|
25
|
+
content = kwargs.get("content", "")
|
|
26
|
+
if not content or not isinstance(content, str):
|
|
27
|
+
return "Error: content must be a non-empty string"
|
|
28
|
+
try:
|
|
29
|
+
mt = MemoryType((kwargs.get("memory_type") or "semantic").lower())
|
|
30
|
+
except ValueError:
|
|
31
|
+
return "Error: memory_type must be one of episodic, semantic, artifact, research"
|
|
32
|
+
tags = kwargs.get("tags") or []
|
|
33
|
+
if isinstance(tags, str):
|
|
34
|
+
tags = [t.strip() for t in tags.split(",") if t.strip()]
|
|
35
|
+
source_task = kwargs.get("source_task") or ""
|
|
36
|
+
store = get_default_store()
|
|
37
|
+
index = MemoryIndex(store)
|
|
38
|
+
record = MemoryRecord(
|
|
39
|
+
id=generate_memory_id(),
|
|
40
|
+
memory_type=mt,
|
|
41
|
+
content=content,
|
|
42
|
+
tags=tags,
|
|
43
|
+
source_task=source_task,
|
|
44
|
+
)
|
|
45
|
+
record = index.ensure_embedding(record)
|
|
46
|
+
mid = store.store(record)
|
|
47
|
+
return f"Stored memory id: {mid}"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Summarize stored memory (e.g. by type or recent N)."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
from devsper.memory.memory_store import get_default_store
|
|
6
|
+
from devsper.memory.memory_types import MemoryType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SummarizeMemoryTool(Tool):
|
|
10
|
+
name = "summarize_memory"
|
|
11
|
+
description = "Summarize stored memory: counts by type and optional short list of recent entries."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"memory_type": {"type": "string", "description": "Optional: episodic, semantic, artifact, research"},
|
|
16
|
+
"limit": {"type": "integer", "description": "Include up to N recent entries in summary (default 5)"},
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def run(self, **kwargs) -> str:
|
|
21
|
+
store = get_default_store()
|
|
22
|
+
mt = kwargs.get("memory_type")
|
|
23
|
+
limit = kwargs.get("limit", 5)
|
|
24
|
+
if not isinstance(limit, int) or limit < 0:
|
|
25
|
+
limit = 5
|
|
26
|
+
try:
|
|
27
|
+
memory_type = MemoryType(mt.lower()) if mt else None
|
|
28
|
+
except (ValueError, AttributeError):
|
|
29
|
+
memory_type = None
|
|
30
|
+
records = store.list_memory(memory_type=memory_type, limit=1000)
|
|
31
|
+
total = len(records)
|
|
32
|
+
by_type: dict[str, int] = {}
|
|
33
|
+
for r in records:
|
|
34
|
+
k = r.memory_type.value
|
|
35
|
+
by_type[k] = by_type.get(k, 0) + 1
|
|
36
|
+
lines = [f"Total: {total} entries.", "By type: " + ", ".join(f"{k}={v}" for k, v in sorted(by_type.items()))]
|
|
37
|
+
if limit > 0 and records:
|
|
38
|
+
lines.append(f"\nRecent (up to {limit}):")
|
|
39
|
+
for r in records[:limit]:
|
|
40
|
+
lines.append(f" - [{r.memory_type.value}] {r.content[:100]}{'...' if len(r.content) > 100 else ''}")
|
|
41
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Add or replace tags on a memory entry."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
from devsper.memory.memory_store import get_default_store
|
|
6
|
+
from devsper.memory.memory_types import MemoryRecord, MemoryType
|
|
7
|
+
from devsper.memory.memory_index import MemoryIndex
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TagMemoryTool(Tool):
|
|
11
|
+
name = "tag_memory"
|
|
12
|
+
description = "Add or replace tags on an existing memory entry by id."
|
|
13
|
+
input_schema = {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {
|
|
16
|
+
"memory_id": {"type": "string", "description": "Id of the memory"},
|
|
17
|
+
"tags": {"type": "array", "items": {"type": "string"}, "description": "Tags to set (replaces existing if replace=true)"},
|
|
18
|
+
"replace": {"type": "boolean", "description": "If true, replace existing tags; else append (default false)"},
|
|
19
|
+
},
|
|
20
|
+
"required": ["memory_id", "tags"],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def run(self, **kwargs) -> str:
|
|
24
|
+
memory_id = kwargs.get("memory_id")
|
|
25
|
+
tags = kwargs.get("tags") or []
|
|
26
|
+
replace = kwargs.get("replace", False)
|
|
27
|
+
if not memory_id or not isinstance(memory_id, str):
|
|
28
|
+
return "Error: memory_id must be a non-empty string"
|
|
29
|
+
if not isinstance(tags, list):
|
|
30
|
+
tags = [str(tags)]
|
|
31
|
+
tags = [str(t).strip() for t in tags if str(t).strip()]
|
|
32
|
+
store = get_default_store()
|
|
33
|
+
record = store.retrieve(memory_id)
|
|
34
|
+
if not record:
|
|
35
|
+
return "Memory not found."
|
|
36
|
+
new_tags = tags if replace else list(dict.fromkeys(record.tags + tags))
|
|
37
|
+
updated = MemoryRecord(
|
|
38
|
+
id=record.id,
|
|
39
|
+
memory_type=record.memory_type,
|
|
40
|
+
timestamp=record.timestamp,
|
|
41
|
+
source_task=record.source_task,
|
|
42
|
+
content=record.content,
|
|
43
|
+
tags=new_tags,
|
|
44
|
+
embedding=record.embedding,
|
|
45
|
+
)
|
|
46
|
+
store.store(updated)
|
|
47
|
+
return f"Tags updated: {new_tags}"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool pipeline engine: chain tools so output of one feeds into the next.
|
|
3
|
+
|
|
4
|
+
Example: docproc → entity_extractor → knowledge_graph_builder.
|
|
5
|
+
Agents may invoke pipelines instead of single tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from devsper.tools.base import Tool
|
|
9
|
+
from devsper.tools.registry import get
|
|
10
|
+
from devsper.tools.tool_runner import run_tool
|
|
11
|
+
|
|
12
|
+
# Key used to pass previous stage output into the next tool's args
|
|
13
|
+
PIPELINE_INPUT_KEY = "input"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolPipeline:
|
|
17
|
+
"""
|
|
18
|
+
A chain of tools. run() executes each in order; each tool's output
|
|
19
|
+
is passed as PIPELINE_INPUT_KEY to the next. Initial args go to the first tool.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, tools: list[str] | list[Tool], name: str = "") -> None:
|
|
23
|
+
"""
|
|
24
|
+
tools: list of tool names (str) or Tool instances. Names are resolved via registry.
|
|
25
|
+
name: optional pipeline name for display/registry.
|
|
26
|
+
"""
|
|
27
|
+
self._tool_refs: list[tuple[str, Tool | None]] = []
|
|
28
|
+
for t in tools:
|
|
29
|
+
if isinstance(t, Tool):
|
|
30
|
+
self._tool_refs.append((t.name, t))
|
|
31
|
+
else:
|
|
32
|
+
self._tool_refs.append((str(t), get(str(t))))
|
|
33
|
+
self.name = name or "pipeline_" + "_".join(n for n, _ in self._tool_refs[:3])
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def tools(self) -> list[Tool]:
|
|
37
|
+
"""Resolved list of Tool instances (skips unresolved names)."""
|
|
38
|
+
return [t for _, t in self._tool_refs if t is not None]
|
|
39
|
+
|
|
40
|
+
def run(self, **kwargs) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Run the pipeline: first tool gets kwargs; each next tool gets
|
|
43
|
+
PIPELINE_INPUT_KEY set to the previous tool's string output.
|
|
44
|
+
Returns the last tool's output, or an error message.
|
|
45
|
+
"""
|
|
46
|
+
if not self._tool_refs:
|
|
47
|
+
return "Pipeline has no tools."
|
|
48
|
+
current_input: str | None = None
|
|
49
|
+
for i, (tool_name, tool) in enumerate(self._tool_refs):
|
|
50
|
+
if tool is None:
|
|
51
|
+
return f"Tool not found: {tool_name}"
|
|
52
|
+
if i == 0:
|
|
53
|
+
args = dict(kwargs)
|
|
54
|
+
else:
|
|
55
|
+
args = {PIPELINE_INPUT_KEY: current_input or ""}
|
|
56
|
+
result = run_tool(tool.name, args)
|
|
57
|
+
if (
|
|
58
|
+
result.startswith("Error:")
|
|
59
|
+
or result.startswith("Tool not found:")
|
|
60
|
+
or result.startswith("Validation error:")
|
|
61
|
+
):
|
|
62
|
+
return result
|
|
63
|
+
current_input = result
|
|
64
|
+
return current_input or ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_pipeline(*tool_names: str, name: str = "") -> ToolPipeline:
|
|
68
|
+
"""Build a pipeline from a sequence of tool names. Example: build_pipeline('docproc_corpus_pipeline', 'knowledge_graph_extractor')."""
|
|
69
|
+
return ToolPipeline(list(tool_names), name=name)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class PipelineAsTool(Tool):
|
|
73
|
+
"""Exposes a ToolPipeline as a Tool so agents can invoke it by name."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, pipeline: ToolPipeline, description: str = "") -> None:
|
|
76
|
+
self._pipeline = pipeline
|
|
77
|
+
self.name = pipeline.name
|
|
78
|
+
self.description = (
|
|
79
|
+
description
|
|
80
|
+
or f"Run pipeline: {' → '.join(n for n, _ in pipeline._tool_refs)}"
|
|
81
|
+
)
|
|
82
|
+
# Merge required keys from first tool if available
|
|
83
|
+
first_tool = pipeline.tools[0] if pipeline.tools else None
|
|
84
|
+
self.input_schema = (
|
|
85
|
+
getattr(first_tool, "input_schema", {"type": "object", "properties": {}})
|
|
86
|
+
if first_tool
|
|
87
|
+
else {"type": "object", "properties": {}}
|
|
88
|
+
)
|
|
89
|
+
self.category = "pipeline"
|
|
90
|
+
|
|
91
|
+
def run(self, **kwargs) -> str:
|
|
92
|
+
return self._pipeline.run(**kwargs)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool registry: register, get, and list tools by name.
|
|
3
|
+
|
|
4
|
+
Tools register themselves when their module is imported (see each category __init__.py).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from devsper.tools.base import Tool
|
|
8
|
+
|
|
9
|
+
_tools: dict[str, Tool] = {}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(tool: Tool) -> None:
|
|
13
|
+
"""Register a tool by name. Overwrites if the name already exists."""
|
|
14
|
+
_tools[tool.name] = tool
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get(name: str) -> Tool | None:
|
|
18
|
+
"""Return the tool with the given name, or None if not found."""
|
|
19
|
+
return _tools.get(name)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_with_mcp_fallback(name: str) -> Tool | None:
|
|
23
|
+
"""
|
|
24
|
+
Return the tool by name. If not found and name has no dot (e.g. 'list_dir'),
|
|
25
|
+
look for a single MCP-style tool whose name ends with '.' + name (e.g. 'filesystem.list_dir').
|
|
26
|
+
Lets agents use short names when only one such MCP tool is registered.
|
|
27
|
+
"""
|
|
28
|
+
t = _tools.get(name)
|
|
29
|
+
if t is not None:
|
|
30
|
+
return t
|
|
31
|
+
if "." in name:
|
|
32
|
+
return None
|
|
33
|
+
candidates = [t for t in _tools.values() if t.name.endswith("." + name)]
|
|
34
|
+
return candidates[0] if len(candidates) == 1 else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def list_tools() -> list[Tool]:
|
|
38
|
+
"""Return all registered tools."""
|
|
39
|
+
return list(_tools.values())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Research tools: web search, Wikipedia, arXiv, paper analysis, etc."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.research.web_search import WebSearchTool
|
|
4
|
+
from devsper.tools.research.duckduckgo_search import DuckDuckGoSearchTool
|
|
5
|
+
from devsper.tools.research.wikipedia_lookup import WikipediaLookupTool
|
|
6
|
+
from devsper.tools.research.arxiv_search import ArxivSearchTool
|
|
7
|
+
from devsper.tools.research.arxiv_download import ArxivDownloadTool
|
|
8
|
+
from devsper.tools.research.paper_summarizer import PaperSummarizerTool
|
|
9
|
+
from devsper.tools.research.citation_extractor import CitationExtractorTool
|
|
10
|
+
from devsper.tools.research.topic_cluster import TopicClusterTool
|
|
11
|
+
from devsper.tools.research.research_question_generator import ResearchQuestionGeneratorTool
|
|
12
|
+
from devsper.tools.research.paper_metadata_extractor import PaperMetadataExtractorTool
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Fetch arXiv paper metadata and optionally download PDF URL."""
|
|
2
|
+
|
|
3
|
+
import urllib.parse
|
|
4
|
+
import urllib.request
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
|
|
7
|
+
from devsper.tools.base import Tool
|
|
8
|
+
from devsper.tools.registry import register
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ArxivDownloadTool(Tool):
|
|
12
|
+
"""Get arXiv paper metadata and PDF link by arXiv ID (e.g. 2401.12345 or 2401.12345v1)."""
|
|
13
|
+
|
|
14
|
+
name = "arxiv_download"
|
|
15
|
+
description = "Get arXiv paper metadata and PDF URL by ID. Does not download file to disk."
|
|
16
|
+
input_schema = {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"arxiv_id": {"type": "string", "description": "arXiv ID (e.g. 2401.12345)"},
|
|
20
|
+
},
|
|
21
|
+
"required": ["arxiv_id"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def run(self, **kwargs) -> str:
|
|
25
|
+
arxiv_id = (kwargs.get("arxiv_id") or "").strip()
|
|
26
|
+
if not arxiv_id:
|
|
27
|
+
return "Error: arxiv_id must be a non-empty string"
|
|
28
|
+
try:
|
|
29
|
+
url = f"http://export.arxiv.org/api/query?id_list={arxiv_id}"
|
|
30
|
+
req = urllib.request.Request(url, headers={"User-Agent": "devsper/1.0"})
|
|
31
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
32
|
+
root = ET.fromstring(resp.read())
|
|
33
|
+
ns = {"atom": "http://www.w3.org/2005/Atom"}
|
|
34
|
+
entry = root.find("atom:entry", ns)
|
|
35
|
+
if entry is None:
|
|
36
|
+
return f"No paper found for arXiv ID: {arxiv_id}"
|
|
37
|
+
id_el = entry.find("atom:id", ns)
|
|
38
|
+
title_el = entry.find("atom:title", ns)
|
|
39
|
+
summary_el = entry.find("atom:summary", ns)
|
|
40
|
+
authors = [a.find("atom:name", ns).text for a in entry.findall("atom:author", ns) if a.find("atom:name", ns) is not None]
|
|
41
|
+
link_pdf = None
|
|
42
|
+
for link in entry.findall("atom:link", ns):
|
|
43
|
+
if link.get("title") == "pdf":
|
|
44
|
+
link_pdf = link.get("href")
|
|
45
|
+
break
|
|
46
|
+
aid = (id_el.text or "").split("/")[-1] if id_el is not None else arxiv_id
|
|
47
|
+
title = (title_el.text or "").strip().replace("\n", " ") if title_el is not None else ""
|
|
48
|
+
abstract = (summary_el.text or "").strip().replace("\n", " ") if summary_el is not None else ""
|
|
49
|
+
pdf_line = f"PDF: {link_pdf}" if link_pdf else "PDF link not found"
|
|
50
|
+
return f"ID: {aid}\nTitle: {title}\nAuthors: {', '.join(authors or [])}\nAbstract: {abstract}\n{pdf_line}"
|
|
51
|
+
except Exception as e:
|
|
52
|
+
return f"Error: {e}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
register(ArxivDownloadTool())
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Search the arXiv API for papers."""
|
|
2
|
+
|
|
3
|
+
import urllib.parse
|
|
4
|
+
import urllib.request
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
|
|
7
|
+
from devsper.tools.base import Tool
|
|
8
|
+
from devsper.tools.registry import register
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ArxivSearchTool(Tool):
|
|
12
|
+
"""Search arXiv for papers by query. Returns titles, authors, abstracts, and IDs."""
|
|
13
|
+
|
|
14
|
+
name = "arxiv_search"
|
|
15
|
+
description = "Search arXiv. Returns paper titles, authors, abstracts, and arxiv IDs."
|
|
16
|
+
input_schema = {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"query": {"type": "string", "description": "Search query"},
|
|
20
|
+
"max_results": {"type": "integer", "description": "Max results (default 5)"},
|
|
21
|
+
},
|
|
22
|
+
"required": ["query"],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def run(self, **kwargs) -> str:
|
|
26
|
+
query = kwargs.get("query")
|
|
27
|
+
max_results = kwargs.get("max_results", 5)
|
|
28
|
+
if not query or not isinstance(query, str):
|
|
29
|
+
return "Error: query must be a non-empty string"
|
|
30
|
+
if not isinstance(max_results, int) or max_results < 1:
|
|
31
|
+
max_results = 5
|
|
32
|
+
try:
|
|
33
|
+
url = "http://export.arxiv.org/api/query?" + urllib.parse.urlencode({
|
|
34
|
+
"search_query": f"all:{query}",
|
|
35
|
+
"start": 0,
|
|
36
|
+
"max_results": max_results,
|
|
37
|
+
})
|
|
38
|
+
req = urllib.request.Request(url, headers={"User-Agent": "devsper/1.0"})
|
|
39
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
40
|
+
root = ET.fromstring(resp.read())
|
|
41
|
+
ns = {"atom": "http://www.w3.org/2005/Atom"}
|
|
42
|
+
entries = root.findall("atom:entry", ns)
|
|
43
|
+
lines = []
|
|
44
|
+
for e in entries:
|
|
45
|
+
id_el = e.find("atom:id", ns)
|
|
46
|
+
title_el = e.find("atom:title", ns)
|
|
47
|
+
summary_el = e.find("atom:summary", ns)
|
|
48
|
+
authors = [a.find("atom:name", ns).text for a in e.findall("atom:author", ns) if a.find("atom:name", ns) is not None]
|
|
49
|
+
arxiv_id = (id_el.text or "").split("/")[-1] if id_el is not None else ""
|
|
50
|
+
title = (title_el.text or "").strip().replace("\n", " ") if title_el is not None else ""
|
|
51
|
+
abstract = (summary_el.text or "").strip().replace("\n", " ")[:500] if summary_el is not None else ""
|
|
52
|
+
lines.append(f"ID: {arxiv_id}\nTitle: {title}\nAuthors: {', '.join(authors)}\nAbstract: {abstract}...")
|
|
53
|
+
return "\n\n---\n\n".join(lines) if lines else "No results found."
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return f"Error: {e}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
register(ArxivSearchTool())
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Extract citation-like patterns from text (e.g. Author et al. (Year), [1], etc.)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from devsper.tools.base import Tool
|
|
6
|
+
from devsper.tools.registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CitationExtractorTool(Tool):
|
|
10
|
+
"""Extract citation-like patterns from text: (Author et al., 20XX), [1], etc."""
|
|
11
|
+
|
|
12
|
+
name = "citation_extractor"
|
|
13
|
+
description = "Extract citation patterns from text: (Author et al., year), [n], numbered refs."
|
|
14
|
+
input_schema = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {"text": {"type": "string", "description": "Text that may contain citations"}},
|
|
17
|
+
"required": ["text"],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def run(self, **kwargs) -> str:
|
|
21
|
+
text = kwargs.get("text")
|
|
22
|
+
if text is None:
|
|
23
|
+
return "Error: text is required"
|
|
24
|
+
if not isinstance(text, str):
|
|
25
|
+
text = str(text)
|
|
26
|
+
paren = re.findall(r'\([^)]*?\b(?:et\s+al\.?|&\s*[^)]+)[^)]*?\d{4}[^)]*\)', text, re.I)
|
|
27
|
+
bracket = re.findall(r'\[\d+(?:\s*[-–,]\s*\d+)*\]', text)
|
|
28
|
+
author_year = re.findall(r'\([A-Z][a-z]+(?:\s+et\s+al\.?)?,?\s*\d{4}\)', text)
|
|
29
|
+
all_refs = list(dict.fromkeys(paren + bracket + author_year))
|
|
30
|
+
if not all_refs:
|
|
31
|
+
return "No citation patterns found."
|
|
32
|
+
return "Extracted citations:\n" + "\n".join(all_refs)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
register(CitationExtractorTool())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""DuckDuckGo search via duckduckgo-search package."""
|
|
2
|
+
|
|
3
|
+
from devsper.tools.base import Tool
|
|
4
|
+
from devsper.tools.registry import register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DuckDuckGoSearchTool(Tool):
|
|
8
|
+
"""Search using DuckDuckGo. Requires duckduckgo-search package."""
|
|
9
|
+
|
|
10
|
+
name = "duckduckgo_search"
|
|
11
|
+
description = "Search DuckDuckGo for a query. Returns titles, snippets, and URLs."
|
|
12
|
+
input_schema = {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"query": {"type": "string", "description": "Search query"},
|
|
16
|
+
"max_results": {"type": "integer", "description": "Max results (default 5)"},
|
|
17
|
+
},
|
|
18
|
+
"required": ["query"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def run(self, **kwargs) -> str:
|
|
22
|
+
query = kwargs.get("query")
|
|
23
|
+
max_results = kwargs.get("max_results", 5)
|
|
24
|
+
if not query or not isinstance(query, str):
|
|
25
|
+
return "Error: query must be a non-empty string"
|
|
26
|
+
if not isinstance(max_results, int) or max_results < 1:
|
|
27
|
+
max_results = 5
|
|
28
|
+
try:
|
|
29
|
+
from duckduckgo_search import DDGS
|
|
30
|
+
with DDGS() as ddgs:
|
|
31
|
+
results = list(ddgs.text(query, max_results=max_results))
|
|
32
|
+
if not results:
|
|
33
|
+
return "No results found."
|
|
34
|
+
lines = [f"- {r.get('title', '')} | {r.get('href', '')}\n {r.get('body', '')}" for r in results]
|
|
35
|
+
return "\n\n".join(lines)
|
|
36
|
+
except ImportError:
|
|
37
|
+
return "Error: Install duckduckgo-search (pip install duckduckgo-search)."
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return f"Error: {e}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
register(DuckDuckGoSearchTool())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Extract paper metadata (title, authors, year) from bib-like or plain text."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from devsper.tools.base import Tool
|
|
6
|
+
from devsper.tools.registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PaperMetadataExtractorTool(Tool):
|
|
10
|
+
"""Extract title, authors, and year from bib-like text or structured paragraphs."""
|
|
11
|
+
|
|
12
|
+
name = "paper_metadata_extractor"
|
|
13
|
+
description = "Extract title, authors, year from bib-style or plain text."
|
|
14
|
+
input_schema = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {"text": {"type": "string", "description": "Bib entry or text containing title/author/year"}},
|
|
17
|
+
"required": ["text"],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def run(self, **kwargs) -> str:
|
|
21
|
+
text = kwargs.get("text")
|
|
22
|
+
if text is None:
|
|
23
|
+
return "Error: text is required"
|
|
24
|
+
if not isinstance(text, str):
|
|
25
|
+
text = str(text)
|
|
26
|
+
out = []
|
|
27
|
+
title_match = re.search(r"title\s*=\s*[{\"]([^}\"]+)[}\"]", text, re.I)
|
|
28
|
+
if title_match:
|
|
29
|
+
out.append(f"Title: {title_match.group(1).strip()}")
|
|
30
|
+
author_match = re.search(r"author\s*=\s*[{\"]([^}\"]+)[}\"]", text, re.I)
|
|
31
|
+
if author_match:
|
|
32
|
+
out.append(f"Authors: {author_match.group(1).strip()}")
|
|
33
|
+
year_match = re.search(r"year\s*=\s*[\"]?(\d{4})[\"]?", text)
|
|
34
|
+
if year_match:
|
|
35
|
+
out.append(f"Year: {year_match.group(1)}")
|
|
36
|
+
if not out and re.search(r"\d{4}", text):
|
|
37
|
+
years = re.findall(r"\b(19\d{2}|20\d{2})\b", text)
|
|
38
|
+
if years:
|
|
39
|
+
out.append(f"Year: {years[0]}")
|
|
40
|
+
if not out:
|
|
41
|
+
return "No structured metadata found. Try bib-style or explicit title/author/year."
|
|
42
|
+
return "\n".join(out)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
register(PaperMetadataExtractorTool())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Summarize paper text by truncation and key sentence extraction heuristics."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from devsper.tools.base import Tool
|
|
6
|
+
from devsper.tools.registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PaperSummarizerTool(Tool):
|
|
10
|
+
"""Produce a short summary of paper text (first N chars and first few sentences). No LLM."""
|
|
11
|
+
|
|
12
|
+
name = "paper_summarizer"
|
|
13
|
+
description = "Summarize paper/abstract text: first 500 chars and first 3 sentences."
|
|
14
|
+
input_schema = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"text": {"type": "string", "description": "Paper abstract or body text"},
|
|
18
|
+
"max_chars": {"type": "integer", "description": "Max characters in summary (default 500)"},
|
|
19
|
+
},
|
|
20
|
+
"required": ["text"],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def run(self, **kwargs) -> str:
|
|
24
|
+
text = kwargs.get("text")
|
|
25
|
+
max_chars = kwargs.get("max_chars", 500)
|
|
26
|
+
if text is None:
|
|
27
|
+
return "Error: text is required"
|
|
28
|
+
if not isinstance(text, str):
|
|
29
|
+
text = str(text)
|
|
30
|
+
if not isinstance(max_chars, int) or max_chars < 1:
|
|
31
|
+
max_chars = 500
|
|
32
|
+
text = text.strip()
|
|
33
|
+
if not text:
|
|
34
|
+
return "Error: text is empty"
|
|
35
|
+
head = text[:max_chars] + ("..." if len(text) > max_chars else "")
|
|
36
|
+
sentences = re.split(r'(?<=[.!?])\s+', text)
|
|
37
|
+
first_sentences = " ".join(sentences[:3]) if sentences else head
|
|
38
|
+
return f"Summary (first {max_chars} chars):\n{head}\n\nKey sentences:\n{first_sentences}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
register(PaperSummarizerTool())
|