PraisonAI 3.0.0__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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
praisonai/acp/session.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ACP session management.
|
|
3
|
+
|
|
4
|
+
Handles session creation, persistence, and resume functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ACPSession:
|
|
20
|
+
"""Represents an ACP conversation session."""
|
|
21
|
+
|
|
22
|
+
session_id: str
|
|
23
|
+
workspace: Path
|
|
24
|
+
created_at: float = field(default_factory=time.time)
|
|
25
|
+
last_activity: float = field(default_factory=time.time)
|
|
26
|
+
|
|
27
|
+
# Agent attribution
|
|
28
|
+
agent_id: Optional[str] = None
|
|
29
|
+
run_id: Optional[str] = None
|
|
30
|
+
trace_id: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
# Session state
|
|
33
|
+
mode: str = "manual" # manual, auto, full_auto
|
|
34
|
+
model: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
# Conversation history (for resume)
|
|
37
|
+
messages: List[Dict[str, Any]] = field(default_factory=list)
|
|
38
|
+
tool_calls: List[Dict[str, Any]] = field(default_factory=list)
|
|
39
|
+
|
|
40
|
+
# MCP servers
|
|
41
|
+
mcp_servers: List[Dict[str, Any]] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def create(cls, workspace: Path, agent_id: Optional[str] = None) -> "ACPSession":
|
|
45
|
+
"""Create a new session."""
|
|
46
|
+
session_id = f"sess_{uuid.uuid4().hex[:16]}"
|
|
47
|
+
return cls(
|
|
48
|
+
session_id=session_id,
|
|
49
|
+
workspace=workspace,
|
|
50
|
+
agent_id=agent_id,
|
|
51
|
+
run_id=f"run_{uuid.uuid4().hex[:8]}",
|
|
52
|
+
trace_id=f"trace_{uuid.uuid4().hex[:8]}",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def update_activity(self) -> None:
|
|
56
|
+
"""Update last activity timestamp."""
|
|
57
|
+
self.last_activity = time.time()
|
|
58
|
+
|
|
59
|
+
def add_message(self, role: str, content: Any) -> None:
|
|
60
|
+
"""Add a message to the conversation history."""
|
|
61
|
+
self.messages.append({
|
|
62
|
+
"role": role,
|
|
63
|
+
"content": content,
|
|
64
|
+
"timestamp": time.time(),
|
|
65
|
+
})
|
|
66
|
+
self.update_activity()
|
|
67
|
+
|
|
68
|
+
def add_tool_call(self, tool_call_id: str, title: str, status: str, **kwargs) -> None:
|
|
69
|
+
"""Add a tool call to the history."""
|
|
70
|
+
self.tool_calls.append({
|
|
71
|
+
"tool_call_id": tool_call_id,
|
|
72
|
+
"title": title,
|
|
73
|
+
"status": status,
|
|
74
|
+
"timestamp": time.time(),
|
|
75
|
+
**kwargs,
|
|
76
|
+
})
|
|
77
|
+
self.update_activity()
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
80
|
+
"""Serialize session to dictionary."""
|
|
81
|
+
return {
|
|
82
|
+
"session_id": self.session_id,
|
|
83
|
+
"workspace": str(self.workspace),
|
|
84
|
+
"created_at": self.created_at,
|
|
85
|
+
"last_activity": self.last_activity,
|
|
86
|
+
"agent_id": self.agent_id,
|
|
87
|
+
"run_id": self.run_id,
|
|
88
|
+
"trace_id": self.trace_id,
|
|
89
|
+
"mode": self.mode,
|
|
90
|
+
"model": self.model,
|
|
91
|
+
"messages": self.messages,
|
|
92
|
+
"tool_calls": self.tool_calls,
|
|
93
|
+
"mcp_servers": self.mcp_servers,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ACPSession":
|
|
98
|
+
"""Deserialize session from dictionary."""
|
|
99
|
+
return cls(
|
|
100
|
+
session_id=data["session_id"],
|
|
101
|
+
workspace=Path(data["workspace"]),
|
|
102
|
+
created_at=data.get("created_at", time.time()),
|
|
103
|
+
last_activity=data.get("last_activity", time.time()),
|
|
104
|
+
agent_id=data.get("agent_id"),
|
|
105
|
+
run_id=data.get("run_id"),
|
|
106
|
+
trace_id=data.get("trace_id"),
|
|
107
|
+
mode=data.get("mode", "manual"),
|
|
108
|
+
model=data.get("model"),
|
|
109
|
+
messages=data.get("messages", []),
|
|
110
|
+
tool_calls=data.get("tool_calls", []),
|
|
111
|
+
mcp_servers=data.get("mcp_servers", []),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class SessionStore:
|
|
116
|
+
"""Persistent storage for ACP sessions."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, storage_dir: Optional[Path] = None):
|
|
119
|
+
"""Initialize session store."""
|
|
120
|
+
if storage_dir is None:
|
|
121
|
+
storage_dir = Path.home() / ".praison" / "acp" / "sessions"
|
|
122
|
+
self.storage_dir = storage_dir
|
|
123
|
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
self._last_session_file = self.storage_dir / ".last_session"
|
|
125
|
+
|
|
126
|
+
def _session_path(self, session_id: str) -> Path:
|
|
127
|
+
"""Get path for session file."""
|
|
128
|
+
return self.storage_dir / f"{session_id}.json"
|
|
129
|
+
|
|
130
|
+
def save(self, session: ACPSession) -> None:
|
|
131
|
+
"""Save session to disk."""
|
|
132
|
+
try:
|
|
133
|
+
path = self._session_path(session.session_id)
|
|
134
|
+
with open(path, "w") as f:
|
|
135
|
+
json.dump(session.to_dict(), f, indent=2)
|
|
136
|
+
|
|
137
|
+
# Update last session pointer
|
|
138
|
+
with open(self._last_session_file, "w") as f:
|
|
139
|
+
f.write(session.session_id)
|
|
140
|
+
|
|
141
|
+
logger.debug(f"Saved session {session.session_id}")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Failed to save session: {e}")
|
|
144
|
+
|
|
145
|
+
def load(self, session_id: str) -> Optional[ACPSession]:
|
|
146
|
+
"""Load session from disk."""
|
|
147
|
+
try:
|
|
148
|
+
path = self._session_path(session_id)
|
|
149
|
+
if not path.exists():
|
|
150
|
+
logger.warning(f"Session not found: {session_id}")
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
with open(path) as f:
|
|
154
|
+
data = json.load(f)
|
|
155
|
+
|
|
156
|
+
return ACPSession.from_dict(data)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Failed to load session: {e}")
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def load_last(self) -> Optional[ACPSession]:
|
|
162
|
+
"""Load the most recent session."""
|
|
163
|
+
try:
|
|
164
|
+
if not self._last_session_file.exists():
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
with open(self._last_session_file) as f:
|
|
168
|
+
session_id = f.read().strip()
|
|
169
|
+
|
|
170
|
+
if session_id:
|
|
171
|
+
return self.load(session_id)
|
|
172
|
+
return None
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Failed to load last session: {e}")
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def delete(self, session_id: str) -> bool:
|
|
178
|
+
"""Delete a session."""
|
|
179
|
+
try:
|
|
180
|
+
path = self._session_path(session_id)
|
|
181
|
+
if path.exists():
|
|
182
|
+
path.unlink()
|
|
183
|
+
logger.debug(f"Deleted session {session_id}")
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"Failed to delete session: {e}")
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
def list_sessions(self, limit: int = 50) -> List[ACPSession]:
|
|
191
|
+
"""List all sessions, sorted by last activity."""
|
|
192
|
+
sessions = []
|
|
193
|
+
try:
|
|
194
|
+
for path in self.storage_dir.glob("sess_*.json"):
|
|
195
|
+
try:
|
|
196
|
+
with open(path) as f:
|
|
197
|
+
data = json.load(f)
|
|
198
|
+
sessions.append(ACPSession.from_dict(data))
|
|
199
|
+
except Exception:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Sort by last activity, most recent first
|
|
203
|
+
sessions.sort(key=lambda s: s.last_activity, reverse=True)
|
|
204
|
+
return sessions[:limit]
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.error(f"Failed to list sessions: {e}")
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
def cleanup_old_sessions(self, max_age_days: int = 30) -> int:
|
|
210
|
+
"""Remove sessions older than max_age_days."""
|
|
211
|
+
cutoff = time.time() - (max_age_days * 24 * 60 * 60)
|
|
212
|
+
removed = 0
|
|
213
|
+
|
|
214
|
+
for session in self.list_sessions(limit=1000):
|
|
215
|
+
if session.last_activity < cutoff:
|
|
216
|
+
if self.delete(session.session_id):
|
|
217
|
+
removed += 1
|
|
218
|
+
|
|
219
|
+
return removed
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PraisonAI Adapters - Implementations for core protocols.
|
|
3
|
+
|
|
4
|
+
This module provides concrete implementations of:
|
|
5
|
+
- Reader adapters (AutoReader, LlamaIndexReaderAdapter, MarkItDownReaderAdapter)
|
|
6
|
+
- Vector store adapters (ChromaAdapter, PineconeAdapter, etc.)
|
|
7
|
+
- Retriever implementations
|
|
8
|
+
- Reranker implementations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Lazy loading to avoid heavy imports at package load time
|
|
12
|
+
_LAZY_IMPORTS = {
|
|
13
|
+
# Readers
|
|
14
|
+
"AutoReader": ("praisonai.adapters.readers", "AutoReader"),
|
|
15
|
+
"MarkItDownReader": ("praisonai.adapters.readers", "MarkItDownReader"),
|
|
16
|
+
"TextReader": ("praisonai.adapters.readers", "TextReader"),
|
|
17
|
+
"DirectoryReader": ("praisonai.adapters.readers", "DirectoryReader"),
|
|
18
|
+
"register_default_readers": ("praisonai.adapters.readers", "register_default_readers"),
|
|
19
|
+
|
|
20
|
+
# Vector stores
|
|
21
|
+
"ChromaVectorStore": ("praisonai.adapters.vector_stores", "ChromaVectorStore"),
|
|
22
|
+
"register_default_vector_stores": ("praisonai.adapters.vector_stores", "register_default_vector_stores"),
|
|
23
|
+
|
|
24
|
+
# Retrievers
|
|
25
|
+
"BasicRetriever": ("praisonai.adapters.retrievers", "BasicRetriever"),
|
|
26
|
+
"FusionRetriever": ("praisonai.adapters.retrievers", "FusionRetriever"),
|
|
27
|
+
"register_default_retrievers": ("praisonai.adapters.retrievers", "register_default_retrievers"),
|
|
28
|
+
|
|
29
|
+
# Rerankers
|
|
30
|
+
"LLMReranker": ("praisonai.adapters.rerankers", "LLMReranker"),
|
|
31
|
+
"register_default_rerankers": ("praisonai.adapters.rerankers", "register_default_rerankers"),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def __getattr__(name: str):
|
|
36
|
+
"""Lazy load adapters."""
|
|
37
|
+
if name in _LAZY_IMPORTS:
|
|
38
|
+
module_path, attr_name = _LAZY_IMPORTS[name]
|
|
39
|
+
import importlib
|
|
40
|
+
module = importlib.import_module(module_path)
|
|
41
|
+
return getattr(module, attr_name)
|
|
42
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __dir__():
|
|
46
|
+
"""List available attributes."""
|
|
47
|
+
return list(_LAZY_IMPORTS.keys())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = list(_LAZY_IMPORTS.keys())
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reader Adapters for PraisonAI.
|
|
3
|
+
|
|
4
|
+
Provides concrete implementations of ReaderProtocol:
|
|
5
|
+
- TextReader: Plain text files
|
|
6
|
+
- MarkItDownReader: Uses markitdown for documents
|
|
7
|
+
- DirectoryReader: Reads all files in a directory
|
|
8
|
+
- AutoReader: Automatic file type detection and routing
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import glob as glob_module
|
|
13
|
+
import logging
|
|
14
|
+
import importlib.util
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Lazy import flags
|
|
20
|
+
_MARKITDOWN_AVAILABLE = None
|
|
21
|
+
_LLAMA_INDEX_AVAILABLE = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _check_markitdown():
|
|
25
|
+
"""Check if markitdown is available."""
|
|
26
|
+
global _MARKITDOWN_AVAILABLE
|
|
27
|
+
if _MARKITDOWN_AVAILABLE is None:
|
|
28
|
+
_MARKITDOWN_AVAILABLE = importlib.util.find_spec("markitdown") is not None
|
|
29
|
+
return _MARKITDOWN_AVAILABLE
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _check_llama_index():
|
|
33
|
+
"""Check if llama_index is available."""
|
|
34
|
+
global _LLAMA_INDEX_AVAILABLE
|
|
35
|
+
if _LLAMA_INDEX_AVAILABLE is None:
|
|
36
|
+
_LLAMA_INDEX_AVAILABLE = importlib.util.find_spec("llama_index") is not None
|
|
37
|
+
return _LLAMA_INDEX_AVAILABLE
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TextReader:
|
|
41
|
+
"""Simple text file reader."""
|
|
42
|
+
|
|
43
|
+
name: str = "text"
|
|
44
|
+
supported_extensions: List[str] = ["txt", "text", "log"]
|
|
45
|
+
|
|
46
|
+
def load(
|
|
47
|
+
self,
|
|
48
|
+
source: str,
|
|
49
|
+
*,
|
|
50
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
51
|
+
) -> List[Dict[str, Any]]:
|
|
52
|
+
"""Load a text file."""
|
|
53
|
+
from praisonaiagents.knowledge.readers import Document
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
with open(source, 'r', encoding='utf-8') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
|
|
59
|
+
doc_metadata = metadata or {}
|
|
60
|
+
doc_metadata.update({
|
|
61
|
+
"source": source,
|
|
62
|
+
"filename": os.path.basename(source),
|
|
63
|
+
"file_type": "text"
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
return [Document(content=content, metadata=doc_metadata)]
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Failed to read text file {source}: {e}")
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
def can_handle(self, source: str) -> bool:
|
|
72
|
+
"""Check if this reader can handle the source."""
|
|
73
|
+
ext = os.path.splitext(source)[1].lower().lstrip(".")
|
|
74
|
+
return ext in self.supported_extensions
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MarkItDownReader:
|
|
78
|
+
"""Reader using markitdown for document conversion."""
|
|
79
|
+
|
|
80
|
+
name: str = "markitdown"
|
|
81
|
+
supported_extensions: List[str] = [
|
|
82
|
+
"pdf", "doc", "docx", "ppt", "pptx", "xls", "xlsx",
|
|
83
|
+
"html", "htm", "md", "markdown", "csv", "json", "xml",
|
|
84
|
+
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp",
|
|
85
|
+
"mp3", "wav", "ogg", "m4a", "flac"
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def __init__(self):
|
|
89
|
+
self._converter = None
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def converter(self):
|
|
93
|
+
"""Lazy load markitdown converter."""
|
|
94
|
+
if self._converter is None:
|
|
95
|
+
if not _check_markitdown():
|
|
96
|
+
raise ImportError(
|
|
97
|
+
"markitdown is required for document conversion. "
|
|
98
|
+
"Install with: pip install markitdown"
|
|
99
|
+
)
|
|
100
|
+
from markitdown import MarkItDown
|
|
101
|
+
self._converter = MarkItDown()
|
|
102
|
+
return self._converter
|
|
103
|
+
|
|
104
|
+
def load(
|
|
105
|
+
self,
|
|
106
|
+
source: str,
|
|
107
|
+
*,
|
|
108
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
109
|
+
) -> List[Dict[str, Any]]:
|
|
110
|
+
"""Load a document using markitdown."""
|
|
111
|
+
from praisonaiagents.knowledge.readers import Document
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
result = self.converter.convert(source)
|
|
115
|
+
content = result.text_content
|
|
116
|
+
|
|
117
|
+
if not content:
|
|
118
|
+
logger.warning(f"No content extracted from {source}")
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
doc_metadata = metadata or {}
|
|
122
|
+
doc_metadata.update({
|
|
123
|
+
"source": source,
|
|
124
|
+
"filename": os.path.basename(source),
|
|
125
|
+
"file_type": os.path.splitext(source)[1].lower().lstrip(".")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return [Document(content=content, metadata=doc_metadata)]
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Failed to convert document {source}: {e}")
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
def can_handle(self, source: str) -> bool:
|
|
134
|
+
"""Check if this reader can handle the source."""
|
|
135
|
+
ext = os.path.splitext(source)[1].lower().lstrip(".")
|
|
136
|
+
return ext in self.supported_extensions and _check_markitdown()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class DirectoryReader:
|
|
140
|
+
"""Reader for directories - recursively reads all files."""
|
|
141
|
+
|
|
142
|
+
name: str = "directory"
|
|
143
|
+
supported_extensions: List[str] = [] # Handles directories, not extensions
|
|
144
|
+
|
|
145
|
+
def __init__(self, recursive: bool = True, exclude_patterns: Optional[List[str]] = None):
|
|
146
|
+
self.recursive = recursive
|
|
147
|
+
self.exclude_patterns = exclude_patterns or [
|
|
148
|
+
"*.pyc", "__pycache__", ".git", ".svn", "node_modules",
|
|
149
|
+
"*.egg-info", ".env", ".venv", "venv"
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
def load(
|
|
153
|
+
self,
|
|
154
|
+
source: str,
|
|
155
|
+
*,
|
|
156
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
157
|
+
) -> List[Dict[str, Any]]:
|
|
158
|
+
"""Load all files from a directory."""
|
|
159
|
+
from praisonaiagents.knowledge.readers import get_reader_registry
|
|
160
|
+
|
|
161
|
+
if not os.path.isdir(source):
|
|
162
|
+
logger.error(f"Not a directory: {source}")
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
documents = []
|
|
166
|
+
registry = get_reader_registry()
|
|
167
|
+
|
|
168
|
+
# Walk directory
|
|
169
|
+
if self.recursive:
|
|
170
|
+
for root, dirs, files in os.walk(source):
|
|
171
|
+
# Filter out excluded directories
|
|
172
|
+
dirs[:] = [d for d in dirs if not self._should_exclude(d)]
|
|
173
|
+
|
|
174
|
+
for file in files:
|
|
175
|
+
if self._should_exclude(file):
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
file_path = os.path.join(root, file)
|
|
179
|
+
reader = registry.get_for_source(file_path)
|
|
180
|
+
|
|
181
|
+
if reader and reader.name != "directory":
|
|
182
|
+
file_metadata = (metadata or {}).copy()
|
|
183
|
+
file_metadata["parent_dir"] = source
|
|
184
|
+
loaded_docs = reader.load(file_path, metadata=file_metadata)
|
|
185
|
+
documents.extend(loaded_docs)
|
|
186
|
+
else:
|
|
187
|
+
for file in os.listdir(source):
|
|
188
|
+
if self._should_exclude(file):
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
file_path = os.path.join(source, file)
|
|
192
|
+
if os.path.isfile(file_path):
|
|
193
|
+
reader = registry.get_for_source(file_path)
|
|
194
|
+
if reader and reader.name != "directory":
|
|
195
|
+
file_metadata = (metadata or {}).copy()
|
|
196
|
+
file_metadata["parent_dir"] = source
|
|
197
|
+
docs = reader.load(file_path, metadata=file_metadata)
|
|
198
|
+
documents.extend(docs)
|
|
199
|
+
|
|
200
|
+
return documents
|
|
201
|
+
|
|
202
|
+
def can_handle(self, source: str) -> bool:
|
|
203
|
+
"""Check if this reader can handle the source."""
|
|
204
|
+
return os.path.isdir(source)
|
|
205
|
+
|
|
206
|
+
def _should_exclude(self, name: str) -> bool:
|
|
207
|
+
"""Check if a file/directory should be excluded."""
|
|
208
|
+
import fnmatch
|
|
209
|
+
for pattern in self.exclude_patterns:
|
|
210
|
+
if fnmatch.fnmatch(name, pattern):
|
|
211
|
+
return True
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class GlobReader:
|
|
216
|
+
"""Reader for glob patterns."""
|
|
217
|
+
|
|
218
|
+
name: str = "glob"
|
|
219
|
+
supported_extensions: List[str] = []
|
|
220
|
+
|
|
221
|
+
def load(
|
|
222
|
+
self,
|
|
223
|
+
source: str,
|
|
224
|
+
*,
|
|
225
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
226
|
+
) -> List[Dict[str, Any]]:
|
|
227
|
+
"""Load files matching a glob pattern."""
|
|
228
|
+
from praisonaiagents.knowledge.readers import get_reader_registry
|
|
229
|
+
|
|
230
|
+
documents = []
|
|
231
|
+
registry = get_reader_registry()
|
|
232
|
+
|
|
233
|
+
for file_path in glob_module.glob(source, recursive=True):
|
|
234
|
+
if os.path.isfile(file_path):
|
|
235
|
+
reader = registry.get_for_source(file_path)
|
|
236
|
+
if reader and reader.name not in ("glob", "directory"):
|
|
237
|
+
file_metadata = (metadata or {}).copy()
|
|
238
|
+
file_metadata["glob_pattern"] = source
|
|
239
|
+
docs = reader.load(file_path, metadata=file_metadata)
|
|
240
|
+
documents.extend(docs)
|
|
241
|
+
|
|
242
|
+
return documents
|
|
243
|
+
|
|
244
|
+
def can_handle(self, source: str) -> bool:
|
|
245
|
+
"""Check if this is a glob pattern."""
|
|
246
|
+
return "*" in source or "?" in source or "[" in source
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class URLReader:
|
|
250
|
+
"""Reader for URLs (HTML pages)."""
|
|
251
|
+
|
|
252
|
+
name: str = "url"
|
|
253
|
+
supported_extensions: List[str] = []
|
|
254
|
+
|
|
255
|
+
def load(
|
|
256
|
+
self,
|
|
257
|
+
source: str,
|
|
258
|
+
*,
|
|
259
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
260
|
+
) -> List[Dict[str, Any]]:
|
|
261
|
+
"""Load content from a URL."""
|
|
262
|
+
from praisonaiagents.knowledge.readers import Document
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
import httpx
|
|
266
|
+
except ImportError:
|
|
267
|
+
try:
|
|
268
|
+
import requests as httpx
|
|
269
|
+
except ImportError:
|
|
270
|
+
logger.error("httpx or requests required for URL reading")
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
response = httpx.get(source, timeout=30, follow_redirects=True)
|
|
275
|
+
response.raise_for_status()
|
|
276
|
+
content = response.text
|
|
277
|
+
|
|
278
|
+
# Try to extract text from HTML
|
|
279
|
+
try:
|
|
280
|
+
from bs4 import BeautifulSoup
|
|
281
|
+
soup = BeautifulSoup(content, 'html.parser')
|
|
282
|
+
# Remove script and style elements
|
|
283
|
+
for script in soup(["script", "style"]):
|
|
284
|
+
script.decompose()
|
|
285
|
+
content = soup.get_text(separator='\n', strip=True)
|
|
286
|
+
except ImportError:
|
|
287
|
+
# Fall back to raw content
|
|
288
|
+
pass
|
|
289
|
+
|
|
290
|
+
doc_metadata = metadata or {}
|
|
291
|
+
doc_metadata.update({
|
|
292
|
+
"source": source,
|
|
293
|
+
"url": source,
|
|
294
|
+
"file_type": "html"
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
return [Document(content=content, metadata=doc_metadata)]
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Failed to fetch URL {source}: {e}")
|
|
300
|
+
return []
|
|
301
|
+
|
|
302
|
+
def can_handle(self, source: str) -> bool:
|
|
303
|
+
"""Check if this is a URL."""
|
|
304
|
+
return source.startswith(("http://", "https://"))
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class AutoReader:
|
|
308
|
+
"""
|
|
309
|
+
Automatic reader that detects source type and routes to appropriate reader.
|
|
310
|
+
|
|
311
|
+
Selection policy:
|
|
312
|
+
1. If LlamaIndex reader is installed + supports the type → use it
|
|
313
|
+
2. Else if MarkItDown supports → use it
|
|
314
|
+
3. Else use simple built-in reader
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
name: str = "auto"
|
|
318
|
+
supported_extensions: List[str] = [] # Handles everything
|
|
319
|
+
|
|
320
|
+
def load(
|
|
321
|
+
self,
|
|
322
|
+
source: str,
|
|
323
|
+
*,
|
|
324
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
325
|
+
) -> List[Dict[str, Any]]:
|
|
326
|
+
"""Load from any source type."""
|
|
327
|
+
from praisonaiagents.knowledge.readers import get_reader_registry, detect_source_kind
|
|
328
|
+
|
|
329
|
+
registry = get_reader_registry()
|
|
330
|
+
source_kind = detect_source_kind(source)
|
|
331
|
+
|
|
332
|
+
# Route based on source kind
|
|
333
|
+
if source_kind == "url":
|
|
334
|
+
reader = registry.get("url")
|
|
335
|
+
elif source_kind == "directory":
|
|
336
|
+
reader = registry.get("directory")
|
|
337
|
+
elif source_kind == "glob":
|
|
338
|
+
reader = registry.get("glob")
|
|
339
|
+
elif source_kind == "file":
|
|
340
|
+
# Find best reader for file type
|
|
341
|
+
reader = registry.get_for_source(source)
|
|
342
|
+
if reader and reader.name == "auto":
|
|
343
|
+
# Avoid infinite recursion - use markitdown or text
|
|
344
|
+
ext = os.path.splitext(source)[1].lower().lstrip(".")
|
|
345
|
+
if ext in MarkItDownReader.supported_extensions and _check_markitdown():
|
|
346
|
+
reader = registry.get("markitdown")
|
|
347
|
+
else:
|
|
348
|
+
reader = registry.get("text")
|
|
349
|
+
else:
|
|
350
|
+
# Unknown - try markitdown then text
|
|
351
|
+
if _check_markitdown():
|
|
352
|
+
reader = registry.get("markitdown")
|
|
353
|
+
else:
|
|
354
|
+
reader = registry.get("text")
|
|
355
|
+
|
|
356
|
+
if reader:
|
|
357
|
+
return reader.load(source, metadata=metadata)
|
|
358
|
+
|
|
359
|
+
logger.warning(f"No reader found for source: {source}")
|
|
360
|
+
return []
|
|
361
|
+
|
|
362
|
+
def can_handle(self, source: str) -> bool:
|
|
363
|
+
"""AutoReader can handle anything."""
|
|
364
|
+
return True
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def register_default_readers():
|
|
368
|
+
"""Register all default readers with the registry."""
|
|
369
|
+
from praisonaiagents.knowledge.readers import get_reader_registry
|
|
370
|
+
|
|
371
|
+
registry = get_reader_registry()
|
|
372
|
+
|
|
373
|
+
# Register text reader
|
|
374
|
+
registry.register("text", TextReader, ["txt", "text", "log"])
|
|
375
|
+
|
|
376
|
+
# Register markitdown reader
|
|
377
|
+
registry.register("markitdown", MarkItDownReader, MarkItDownReader.supported_extensions)
|
|
378
|
+
|
|
379
|
+
# Register directory reader
|
|
380
|
+
registry.register("directory", DirectoryReader)
|
|
381
|
+
|
|
382
|
+
# Register glob reader
|
|
383
|
+
registry.register("glob", GlobReader)
|
|
384
|
+
|
|
385
|
+
# Register URL reader
|
|
386
|
+
registry.register("url", URLReader)
|
|
387
|
+
|
|
388
|
+
# Register auto reader
|
|
389
|
+
registry.register("auto", AutoReader)
|
|
390
|
+
|
|
391
|
+
logger.debug("Registered default readers")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# Auto-register on import
|
|
395
|
+
register_default_readers()
|