aria-code 0.167.0__py3-none-win_amd64.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.
- aria_bots/__init__.py +7 -0
- aria_bots/channels/README.md +97 -0
- aria_bots/channels/__init__.py +1 -0
- aria_bots/channels/approvals.py +263 -0
- aria_bots/channels/conversation.py +280 -0
- aria_bots/channels/digest.py +186 -0
- aria_bots/channels/registry.py +95 -0
- aria_bots/engine.py +144 -0
- aria_bots/feishu_bot.py +1385 -0
- aria_bots/logistics/__init__.py +1 -0
- aria_bots/logistics/carriers.py +253 -0
- aria_bots/logistics/freight.py +154 -0
- aria_bots/logistics/inventory.py +242 -0
- aria_bots/logistics/mcp_server.py +156 -0
- aria_bots/logistics/records.py +119 -0
- aria_bots/logistics/tenancy.py +152 -0
- aria_bots/market.py +91 -0
- aria_bots/relay_client.py +326 -0
- aria_bots/telegram_bot.py +299 -0
- aria_code/__init__.py +111 -0
- aria_code/_bin/aria-native.exe +0 -0
- aria_code/_bin/aria-runtime.exe +0 -0
- aria_code/_version.py +20 -0
- aria_code/account_memory.py +296 -0
- aria_code/account_session.py +166 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +49 -0
- aria_code/agents/base.py +329 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +315 -0
- aria_code/agents/financial/fundamental.py +178 -0
- aria_code/agents/financial/macro.py +111 -0
- aria_code/agents/financial/news.py +231 -0
- aria_code/agents/financial/risk.py +146 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +277 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +392 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +168 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +500 -0
- aria_code/agents/video/__init__.py +16 -0
- aria_code/agents/video/director.py +376 -0
- aria_code/agents/video/qa_agent.py +143 -0
- aria_code/agents/video/script_doctor.py +112 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/navigation.schema.json +110 -0
- aria_code/agents/warehouse/task_agent.py +172 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +9 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/approvals.py +11 -0
- aria_code/apps/channels/conversation.py +11 -0
- aria_code/apps/channels/digest.py +11 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +11 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/app_server.py +656 -0
- aria_code/apps/cli/auto_update.py +125 -0
- aria_code/apps/cli/bootstrap.py +206 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/chat_turn.py +1327 -0
- aria_code/apps/cli/cloud_models.py +231 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +34 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +378 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +215 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
- aria_code/apps/cli/commands/broker_cmds.py +1236 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +189 -0
- aria_code/apps/cli/commands/core_cmds.py +2748 -0
- aria_code/apps/cli/commands/data_cmds.py +521 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/engineering_cmds.py +399 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +544 -0
- aria_code/apps/cli/commands/market_cmds.py +1419 -0
- aria_code/apps/cli/commands/market_context.py +432 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2374 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
- aria_code/apps/cli/commands/report.py +476 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +723 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
- aria_code/apps/cli/commands/workflow_cmds.py +629 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +158 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/conversation_policy.py +53 -0
- aria_code/apps/cli/deterministic.py +200 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/earnings_view.py +172 -0
- aria_code/apps/cli/endpoint_migration.py +64 -0
- aria_code/apps/cli/exec_events.py +255 -0
- aria_code/apps/cli/file_references.py +155 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +239 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
- aria_code/apps/cli/handlers/market_handlers.py +2570 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/headless.py +352 -0
- aria_code/apps/cli/helpers.py +326 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +310 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +305 -0
- aria_code/apps/cli/intent_shadow.py +72 -0
- aria_code/apps/cli/intent_signals.py +347 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +550 -0
- aria_code/apps/cli/main.py +39 -0
- aria_code/apps/cli/market_hours.py +100 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/mixin_binding.py +32 -0
- aria_code/apps/cli/model_catalog.py +652 -0
- aria_code/apps/cli/model_probe.py +169 -0
- aria_code/apps/cli/model_retirement.py +65 -0
- aria_code/apps/cli/native_branding.py +12 -0
- aria_code/apps/cli/native_bridge.py +118 -0
- aria_code/apps/cli/native_entry.py +58 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +78 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +71 -0
- aria_code/apps/cli/prompts/select.py +193 -0
- aria_code/apps/cli/prompts/system_prompts.py +562 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +775 -0
- aria_code/apps/cli/providers/chat_routing.py +139 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
- aria_code/apps/cli/providers/runtime_bridge.py +586 -0
- aria_code/apps/cli/providers/vertexai_stream.py +592 -0
- aria_code/apps/cli/release_download.py +159 -0
- aria_code/apps/cli/review_runner.py +111 -0
- aria_code/apps/cli/runtime_consumer.py +769 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_refresh.py +123 -0
- aria_code/apps/cli/session_store.py +125 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/task_isolation.py +211 -0
- aria_code/apps/cli/terminal_chrome.py +412 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +932 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +113 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +181 -0
- aria_code/apps/cli/tools/market_tools.py +626 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +656 -0
- aria_code/apps/cli/tools/write_tools.py +961 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/transactions.py +269 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +257 -0
- aria_code/apps/cli/updater.py +373 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1663 -0
- aria_code/apps/cli/web_login.py +136 -0
- aria_code/apps/cli/workflow_runner.py +127 -0
- aria_code/apps/cli/workspace_route.py +121 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +5244 -0
- aria_code/aria_daemon.py +1372 -0
- aria_code/aria_feishu_bot.py +16 -0
- aria_code/aria_mcp_server.py +61 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_telegram_bot.py +11 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +16 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1985 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +190 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_server.py +276 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sec_statements.py +217 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/doctor.py +860 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/_finance_core/__init__.py +5 -0
- aria_code/domain/_finance_core/backtest_engine.py +325 -0
- aria_code/domain/_finance_core/performance_stats.py +101 -0
- aria_code/domain/backtest_engine.py +75 -0
- aria_code/domain/backtest_report.py +488 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/performance_stats.py +6 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/behavior.py +113 -0
- aria_code/evals/gap.py +93 -0
- aria_code/evals/harness.py +949 -0
- aria_code/evals/review_probe.py +132 -0
- aria_code/evals/runner.py +425 -0
- aria_code/evals/scorecard.py +322 -0
- aria_code/evals/summary.py +125 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +459 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +744 -0
- aria_code/memory_manager.py +273 -0
- aria_code/model_capability.py +512 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +26 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +58 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +232 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +162 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1705 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +393 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +75 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +304 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +137 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +160 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/_paths.py +92 -0
- aria_code/packages/aria_tools/registry.py +129 -0
- aria_code/packages/quant_engine/__init__.py +33 -0
- aria_code/packages/quant_engine/_namespace.py +24 -0
- aria_code/packages/quant_engine/backtest/__init__.py +10 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +102 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +76 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/healthcare/__init__.py +40 -0
- aria_code/packs/logistics/__init__.py +171 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/google_video_provider.py +256 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/arthera_gateway.py +90 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +137 -0
- aria_code/providers/llm/openai_compat.py +350 -0
- aria_code/providers/llm/registry.py +457 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +549 -0
- aria_code/providers/ollama_models.py +79 -0
- aria_code/providers/openai_params.py +32 -0
- aria_code/providers/tool_messages.py +100 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/review_service.py +497 -0
- aria_code/runtime/__init__.py +176 -0
- aria_code/runtime/acceptance.py +632 -0
- aria_code/runtime/agent_loop.py +1851 -0
- aria_code/runtime/agent_profiles.py +75 -0
- aria_code/runtime/approval.py +71 -0
- aria_code/runtime/baseline.py +219 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +481 -0
- aria_code/runtime/consult.py +65 -0
- aria_code/runtime/contract.py +282 -0
- aria_code/runtime/delivery.py +432 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +167 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/native_index.py +153 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/processes.py +343 -0
- aria_code/runtime/project_graph.py +614 -0
- aria_code/runtime/repo_map.py +712 -0
- aria_code/runtime/research_bundle.py +105 -0
- aria_code/runtime/review.py +269 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +522 -0
- aria_code/runtime/scoped_runs.py +113 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/semantic_diff.py +171 -0
- aria_code/runtime/subagent.py +721 -0
- aria_code/runtime/symbol_edit.py +117 -0
- aria_code/runtime/task_brief.py +46 -0
- aria_code/runtime/task_context.py +145 -0
- aria_code/runtime/task_ledger.py +108 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/task_outcome.py +33 -0
- aria_code/runtime/task_verification.py +266 -0
- aria_code/runtime/task_worktree.py +448 -0
- aria_code/runtime/tool_executor.py +329 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/transactions.py +216 -0
- aria_code/runtime/windows_job.py +115 -0
- aria_code/runtime/workflows.py +222 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +27 -0
- aria_code/safety/client_isolation.py +178 -0
- aria_code/safety/permissions.py +339 -0
- aria_code/safety/risk.py +699 -0
- aria_code/safety/sandbox.py +148 -0
- aria_code/safety/service.py +102 -0
- aria_code/service_endpoints.py +14 -0
- aria_code/setup_wizard.py +684 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +509 -0
- aria_code/tools/data_analysis_tools.py +831 -0
- aria_code/tools/enterprise_finance_tools.py +202 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +880 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3488 -0
- aria_code/tools/logistics_carriers.py +11 -0
- aria_code/tools/logistics_inventory.py +11 -0
- aria_code/tools/logistics_io.py +11 -0
- aria_code/tools/logistics_tenancy.py +11 -0
- aria_code/tools/logistics_tools.py +11 -0
- aria_code/tools/macro_tools.py +412 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +662 -0
- aria_code/tools/spreadsheet_tools.py +399 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/tools/video_tools.py +500 -0
- aria_code/training/__init__.py +1 -0
- aria_code/training/compare.py +117 -0
- aria_code/training/prelude_commands.py +111 -0
- aria_code/training/prelude_eval.py +390 -0
- aria_code/training/prelude_real.py +183 -0
- aria_code/training/prelude_train.py +248 -0
- aria_code/training/sft_dataset.py +230 -0
- aria_code/training/trajectory_dataset.py +263 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +404 -0
- aria_code/ui/completer.py +571 -0
- aria_code/ui/console.py +285 -0
- aria_code/ui/input_box.py +505 -0
- aria_code/ui/mentions.py +206 -0
- aria_code/ui/picker.py +248 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/actions.py +297 -0
- aria_code/ui/render/finance.py +1732 -0
- aria_code/ui/render/market.py +253 -0
- aria_code/ui/render/output.py +954 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +367 -0
- aria_code/ui/robot.py +239 -0
- aria_code/ui/startup_dashboard.py +110 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +518 -0
- aria_code/video_workflow/__init__.py +8 -0
- aria_code/video_workflow/__main__.py +61 -0
- aria_code/video_workflow/engines/__init__.py +18 -0
- aria_code/video_workflow/engines/base.py +69 -0
- aria_code/video_workflow/engines/custom_model.py +204 -0
- aria_code/video_workflow/engines/google_veo.py +94 -0
- aria_code/video_workflow/engines/local_engine.py +71 -0
- aria_code/video_workflow/engines/registry.py +98 -0
- aria_code/video_workflow/google_models.py +278 -0
- aria_code/video_workflow/music/__init__.py +5 -0
- aria_code/video_workflow/music/foley_service.py +132 -0
- aria_code/video_workflow/music/service.py +212 -0
- aria_code/video_workflow/pipeline.py +620 -0
- aria_code/video_workflow/plan.py +202 -0
- aria_code/video_workflow/session.py +297 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +219 -0
- aria_code/workspace/verify.py +167 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.167.0.dist-info/METADATA +330 -0
- aria_code-0.167.0.dist-info/RECORD +601 -0
- aria_code-0.167.0.dist-info/WHEEL +5 -0
- aria_code-0.167.0.dist-info/entry_points.txt +5 -0
- aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.167.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file_analysis_tools.py — 多格式文件解析与内容提取层
|
|
3
|
+
=====================================================
|
|
4
|
+
支持格式:
|
|
5
|
+
PDF — pdfplumber (优先) 或 pypdf
|
|
6
|
+
Word/DOCX — python-docx
|
|
7
|
+
Excel/XLSX — openpyxl + pandas
|
|
8
|
+
CSV/TSV — pandas
|
|
9
|
+
JSON/JSONL — 内置 json
|
|
10
|
+
Markdown/TXT — 直接读取
|
|
11
|
+
图片 — PIL/Pillow 元数据 + base64 (发给视觉模型)
|
|
12
|
+
HTML — BeautifulSoup4 提取正文
|
|
13
|
+
代码文件 — 语法感知提取 (py/js/ts/go/java/cpp 等)
|
|
14
|
+
|
|
15
|
+
全部函数返回统一格式:
|
|
16
|
+
{"success": bool, "type": str, "content": str, "metadata": dict, ...}
|
|
17
|
+
|
|
18
|
+
依赖安装(可选,按需安装):
|
|
19
|
+
pip install pdfplumber python-docx openpyxl pandas pillow beautifulsoup4
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import base64
|
|
25
|
+
import importlib
|
|
26
|
+
import json
|
|
27
|
+
import logging
|
|
28
|
+
import mimetypes
|
|
29
|
+
import os
|
|
30
|
+
import re
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# ── Optional imports ──────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def _try(mod):
|
|
40
|
+
try:
|
|
41
|
+
return __import__(mod)
|
|
42
|
+
except ImportError:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
# Detect available parsers at module load time (fast, no exceptions in hot path)
|
|
46
|
+
_pdfplumber = _try("pdfplumber")
|
|
47
|
+
_pypdf = _try("pypdf")
|
|
48
|
+
_docx_mod = _try("docx")
|
|
49
|
+
_openpyxl = _try("openpyxl")
|
|
50
|
+
_pd = _try("pandas")
|
|
51
|
+
_bs4 = _try("bs4")
|
|
52
|
+
_PIL = _try("PIL")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def refresh_optional_parsers() -> Dict[str, bool]:
|
|
56
|
+
"""Refresh optional parser imports after an in-session package install.
|
|
57
|
+
|
|
58
|
+
``/install`` uses the running interpreter, but this module historically
|
|
59
|
+
cached failed imports for the lifetime of the process. That made a
|
|
60
|
+
successful install look ineffective until Aria was restarted.
|
|
61
|
+
"""
|
|
62
|
+
# Rebinding happens through globals() below, which needs no `global`.
|
|
63
|
+
importlib.invalidate_caches()
|
|
64
|
+
module_names = {
|
|
65
|
+
"_pdfplumber": "pdfplumber",
|
|
66
|
+
"_pypdf": "pypdf",
|
|
67
|
+
"_docx_mod": "docx",
|
|
68
|
+
"_openpyxl": "openpyxl",
|
|
69
|
+
"_pd": "pandas",
|
|
70
|
+
"_bs4": "bs4",
|
|
71
|
+
"_PIL": "PIL",
|
|
72
|
+
}
|
|
73
|
+
namespace = globals()
|
|
74
|
+
for variable, module_name in module_names.items():
|
|
75
|
+
if namespace.get(variable) is None:
|
|
76
|
+
namespace[variable] = _try(module_name)
|
|
77
|
+
return {
|
|
78
|
+
"pdfplumber": _pdfplumber is not None,
|
|
79
|
+
"pypdf": _pypdf is not None,
|
|
80
|
+
"python-docx": _docx_mod is not None,
|
|
81
|
+
"pandas": _pd is not None,
|
|
82
|
+
"openpyxl": _openpyxl is not None,
|
|
83
|
+
"beautifulsoup4": _bs4 is not None,
|
|
84
|
+
"Pillow": _PIL is not None,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# ── Data structures ───────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class FileContent:
|
|
91
|
+
"""Normalised result of parsing any file."""
|
|
92
|
+
success: bool
|
|
93
|
+
file_type: str # "pdf" | "docx" | "xlsx" | "csv" | "json" | "image" | "code" | "text"
|
|
94
|
+
path: str
|
|
95
|
+
filename: str
|
|
96
|
+
size_kb: float
|
|
97
|
+
content: str # Extracted text (may be truncated)
|
|
98
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
99
|
+
tables: List[List[List[Any]]] = field(default_factory=list) # nested: [sheet[row[cell]]]
|
|
100
|
+
images_b64: List[str] = field(default_factory=list) # base64-encoded embedded images
|
|
101
|
+
error: Optional[str] = None
|
|
102
|
+
truncated: bool = False
|
|
103
|
+
char_count: int = 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ── Size / truncation limits ──────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
MAX_TEXT_CHARS = 80_000 # ~20k tokens — safe for most context windows
|
|
109
|
+
MAX_TABLE_ROWS = 200 # per sheet
|
|
110
|
+
MAX_PAGES = 50 # PDF page limit
|
|
111
|
+
SUMMARY_CHARS = 3_000 # for quick summary mode
|
|
112
|
+
|
|
113
|
+
# ── Main dispatcher ───────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
def parse_file(path_str: str, max_chars: int = MAX_TEXT_CHARS,
|
|
116
|
+
include_images: bool = False) -> FileContent:
|
|
117
|
+
"""
|
|
118
|
+
Parse any supported file and return normalised FileContent.
|
|
119
|
+
|
|
120
|
+
path_str: absolute or ~-relative path
|
|
121
|
+
max_chars: truncate text extraction at this many chars
|
|
122
|
+
include_images: whether to base64-encode embedded images (expensive)
|
|
123
|
+
"""
|
|
124
|
+
refresh_optional_parsers()
|
|
125
|
+
path = Path(path_str).expanduser().resolve()
|
|
126
|
+
if not path.exists():
|
|
127
|
+
return FileContent(False, "unknown", str(path), path.name, 0,
|
|
128
|
+
"", error=f"文件不存在: {path}")
|
|
129
|
+
if not path.is_file():
|
|
130
|
+
return FileContent(False, "unknown", str(path), path.name, 0,
|
|
131
|
+
"", error=f"不是文件: {path}")
|
|
132
|
+
|
|
133
|
+
size_kb = path.stat().st_size / 1024
|
|
134
|
+
if size_kb > 100 * 1024: # 100 MB guard
|
|
135
|
+
return FileContent(False, "unknown", str(path), path.name, size_kb,
|
|
136
|
+
"", error="文件过大(>100MB),请使用更小的文件")
|
|
137
|
+
|
|
138
|
+
suffix = path.suffix.lstrip(".").lower()
|
|
139
|
+
_DISPATCH = {
|
|
140
|
+
"pdf": _parse_pdf,
|
|
141
|
+
"docx": _parse_docx,
|
|
142
|
+
"doc": _parse_docx,
|
|
143
|
+
"xlsx": _parse_excel,
|
|
144
|
+
"xls": _parse_excel,
|
|
145
|
+
"csv": _parse_csv,
|
|
146
|
+
"tsv": _parse_csv,
|
|
147
|
+
"json": _parse_json,
|
|
148
|
+
"jsonl": _parse_json,
|
|
149
|
+
"html": _parse_html,
|
|
150
|
+
"htm": _parse_html,
|
|
151
|
+
"xml": _parse_html,
|
|
152
|
+
"md": _parse_text,
|
|
153
|
+
"txt": _parse_text,
|
|
154
|
+
"rst": _parse_text,
|
|
155
|
+
"log": _parse_text,
|
|
156
|
+
"yaml": _parse_text,
|
|
157
|
+
"yml": _parse_text,
|
|
158
|
+
"toml": _parse_text,
|
|
159
|
+
"ini": _parse_text,
|
|
160
|
+
"env": _parse_text,
|
|
161
|
+
"png": _parse_image,
|
|
162
|
+
"jpg": _parse_image,
|
|
163
|
+
"jpeg": _parse_image,
|
|
164
|
+
"gif": _parse_image,
|
|
165
|
+
"webp": _parse_image,
|
|
166
|
+
"bmp": _parse_image,
|
|
167
|
+
"mp4": _parse_video,
|
|
168
|
+
"mov": _parse_video,
|
|
169
|
+
"avi": _parse_video,
|
|
170
|
+
"mkv": _parse_video,
|
|
171
|
+
"webm": _parse_video,
|
|
172
|
+
"m4v": _parse_video,
|
|
173
|
+
}
|
|
174
|
+
# Code files
|
|
175
|
+
_CODE_EXT = {"py","js","ts","tsx","jsx","go","java","c","cpp","h","hpp",
|
|
176
|
+
"rs","rb","php","swift","kt","scala","sh","bash","zsh",
|
|
177
|
+
"sql","r","m","cs","vb","lua","perl","ps1"}
|
|
178
|
+
if suffix in _CODE_EXT:
|
|
179
|
+
fn = _parse_code
|
|
180
|
+
else:
|
|
181
|
+
fn = _DISPATCH.get(suffix, _parse_text)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
result = fn(path, max_chars=max_chars, include_images=include_images)
|
|
185
|
+
result.char_count = len(result.content)
|
|
186
|
+
return result
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.exception("parse_file failed for %s", path)
|
|
189
|
+
return FileContent(False, suffix, str(path), path.name, size_kb,
|
|
190
|
+
"", error=f"解析失败: {e}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ── LLM-callable tool ─────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
# Keep the returned text small so a big document can't blow the context window
|
|
196
|
+
# (the agent can re-call with a larger max_chars when it needs more).
|
|
197
|
+
_TOOL_MAX_CHARS = 6000
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def tool_analyze_file(params: dict) -> dict:
|
|
201
|
+
"""Parse and analyse a local file so the agent can read it autonomously.
|
|
202
|
+
|
|
203
|
+
Supports pdf / docx / xls(x) / csv / json / html / markdown / code / images
|
|
204
|
+
(png·jpg·gif·webp·bmp). Image files are queued for the vision model so a
|
|
205
|
+
vision-capable model can actually see them. Returns extracted text (capped)
|
|
206
|
+
plus metadata; set ``max_chars`` higher to pull more of a long document.
|
|
207
|
+
"""
|
|
208
|
+
path = str(params.get("path") or params.get("file_path") or "").strip()
|
|
209
|
+
if not path:
|
|
210
|
+
return {"success": False, "error": "path is required"}
|
|
211
|
+
try:
|
|
212
|
+
max_chars = int(params.get("max_chars", _TOOL_MAX_CHARS) or _TOOL_MAX_CHARS)
|
|
213
|
+
except (TypeError, ValueError):
|
|
214
|
+
max_chars = _TOOL_MAX_CHARS
|
|
215
|
+
max_chars = max(500, min(max_chars, MAX_TEXT_CHARS))
|
|
216
|
+
|
|
217
|
+
fc = parse_file(path, max_chars=max_chars, include_images=True)
|
|
218
|
+
if not fc.success:
|
|
219
|
+
return {"success": False, "path": path, "error": fc.error or "解析失败"}
|
|
220
|
+
|
|
221
|
+
# Bind the image to this result rather than a process-wide pending slot.
|
|
222
|
+
raw = fc.images_b64[0] if fc.images_b64 else ""
|
|
223
|
+
if "base64," in raw:
|
|
224
|
+
raw = raw.split("base64,", 1)[1]
|
|
225
|
+
vision = {"_vision_image": raw} if raw else {}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
"success": True,
|
|
229
|
+
"path": fc.path,
|
|
230
|
+
"filename": fc.filename,
|
|
231
|
+
"file_type": fc.file_type,
|
|
232
|
+
"size_kb": round(fc.size_kb, 1),
|
|
233
|
+
"char_count": fc.char_count,
|
|
234
|
+
"truncated": fc.truncated,
|
|
235
|
+
"n_tables": len(fc.tables),
|
|
236
|
+
"metadata": fc.metadata,
|
|
237
|
+
"vision_attached": bool(raw),
|
|
238
|
+
**vision,
|
|
239
|
+
"content": fc.content,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ── PDF ───────────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
def _parse_pdf(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
246
|
+
size_kb = path.stat().st_size / 1024
|
|
247
|
+
meta: Dict[str, Any] = {"pages": 0}
|
|
248
|
+
text_parts = []
|
|
249
|
+
tables = []
|
|
250
|
+
|
|
251
|
+
if _pdfplumber:
|
|
252
|
+
import pdfplumber
|
|
253
|
+
with pdfplumber.open(str(path)) as pdf:
|
|
254
|
+
meta["pages"] = len(pdf.pages)
|
|
255
|
+
meta["pdf_info"] = dict(pdf.metadata or {})
|
|
256
|
+
for i, page in enumerate(pdf.pages[:MAX_PAGES]):
|
|
257
|
+
page_text = (page.extract_text() or "").strip()
|
|
258
|
+
if page_text:
|
|
259
|
+
text_parts.append(f"[第{i+1}页]\n{page_text}")
|
|
260
|
+
# Tables
|
|
261
|
+
for tbl in (page.extract_tables() or []):
|
|
262
|
+
if tbl:
|
|
263
|
+
tables.append(tbl)
|
|
264
|
+
elif _pypdf:
|
|
265
|
+
import pypdf
|
|
266
|
+
reader = pypdf.PdfReader(str(path))
|
|
267
|
+
meta["pages"] = len(reader.pages)
|
|
268
|
+
for i, page in enumerate(reader.pages[:MAX_PAGES]):
|
|
269
|
+
t = page.extract_text() or ""
|
|
270
|
+
if t.strip():
|
|
271
|
+
text_parts.append(f"[第{i+1}页]\n{t.strip()}")
|
|
272
|
+
else:
|
|
273
|
+
return FileContent(False, "pdf", str(path), path.name, size_kb, "",
|
|
274
|
+
error="未安装 PDF 解析库,请运行: pip install pdfplumber")
|
|
275
|
+
|
|
276
|
+
full_text = "\n\n".join(text_parts)
|
|
277
|
+
truncated = len(full_text) > max_chars
|
|
278
|
+
return FileContent(True, "pdf", str(path), path.name, size_kb,
|
|
279
|
+
full_text[:max_chars], meta, tables[:20],
|
|
280
|
+
truncated=truncated)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ── DOCX ──────────────────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
def _parse_docx(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
286
|
+
size_kb = path.stat().st_size / 1024
|
|
287
|
+
if not _docx_mod:
|
|
288
|
+
return FileContent(False, "docx", str(path), path.name, size_kb, "",
|
|
289
|
+
error="未安装 python-docx,请运行: pip install python-docx")
|
|
290
|
+
import docx
|
|
291
|
+
doc = docx.Document(str(path))
|
|
292
|
+
|
|
293
|
+
parts = []
|
|
294
|
+
tables = []
|
|
295
|
+
|
|
296
|
+
# Core properties
|
|
297
|
+
meta: Dict[str, Any] = {}
|
|
298
|
+
try:
|
|
299
|
+
cp = doc.core_properties
|
|
300
|
+
meta = {
|
|
301
|
+
"author": cp.author,
|
|
302
|
+
"created": str(cp.created)[:10] if cp.created else "",
|
|
303
|
+
"modified": str(cp.modified)[:10] if cp.modified else "",
|
|
304
|
+
"title": cp.title or "",
|
|
305
|
+
"subject": cp.subject or "",
|
|
306
|
+
}
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
# Paragraphs
|
|
311
|
+
for para in doc.paragraphs:
|
|
312
|
+
t = para.text.strip()
|
|
313
|
+
if not t:
|
|
314
|
+
continue
|
|
315
|
+
# Heading levels
|
|
316
|
+
if para.style.name.startswith("Heading"):
|
|
317
|
+
level = para.style.name.replace("Heading ", "")
|
|
318
|
+
parts.append(f"\n{'#' * int(level) if level.isdigit() else '##'} {t}")
|
|
319
|
+
else:
|
|
320
|
+
parts.append(t)
|
|
321
|
+
|
|
322
|
+
# Tables
|
|
323
|
+
for tbl in doc.tables:
|
|
324
|
+
rows = []
|
|
325
|
+
for row in tbl.rows[:MAX_TABLE_ROWS]:
|
|
326
|
+
rows.append([cell.text.strip() for cell in row.cells])
|
|
327
|
+
if rows:
|
|
328
|
+
tables.append(rows)
|
|
329
|
+
# Inline text representation
|
|
330
|
+
header = " | ".join(rows[0])
|
|
331
|
+
sep = " | ".join(["---"] * len(rows[0]))
|
|
332
|
+
body = "\n".join(" | ".join(r) for r in rows[1:MAX_TABLE_ROWS])
|
|
333
|
+
parts.append(f"\n[表格]\n{header}\n{sep}\n{body}")
|
|
334
|
+
|
|
335
|
+
full_text = "\n".join(parts)
|
|
336
|
+
truncated = len(full_text) > max_chars
|
|
337
|
+
meta["paragraphs"] = len(doc.paragraphs)
|
|
338
|
+
meta["tables"] = len(doc.tables)
|
|
339
|
+
return FileContent(True, "docx", str(path), path.name, size_kb,
|
|
340
|
+
full_text[:max_chars], meta, tables[:10],
|
|
341
|
+
truncated=truncated)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# ── Excel ─────────────────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
def _parse_excel(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
347
|
+
size_kb = path.stat().st_size / 1024
|
|
348
|
+
if not _pd:
|
|
349
|
+
return FileContent(False, "xlsx", str(path), path.name, size_kb, "",
|
|
350
|
+
error="未安装 pandas,请运行: pip install pandas openpyxl")
|
|
351
|
+
|
|
352
|
+
import pandas as pd
|
|
353
|
+
try:
|
|
354
|
+
sheets = pd.read_excel(str(path), sheet_name=None, engine="openpyxl",
|
|
355
|
+
nrows=MAX_TABLE_ROWS)
|
|
356
|
+
except Exception as e:
|
|
357
|
+
return FileContent(False, "xlsx", str(path), path.name, size_kb, "",
|
|
358
|
+
error=f"读取 Excel 失败: {e}")
|
|
359
|
+
|
|
360
|
+
parts = []
|
|
361
|
+
tables = []
|
|
362
|
+
meta: Dict[str, Any] = {"sheets": list(sheets.keys()), "sheet_count": len(sheets)}
|
|
363
|
+
|
|
364
|
+
for sname, df in sheets.items():
|
|
365
|
+
df = df.fillna("").astype(str)
|
|
366
|
+
rows = [list(df.columns)] + df.values.tolist()
|
|
367
|
+
tables.append(rows)
|
|
368
|
+
meta[f"sheet_{sname}_shape"] = f"{len(df)} 行 × {len(df.columns)} 列"
|
|
369
|
+
|
|
370
|
+
# Text representation (first 50 rows shown)
|
|
371
|
+
header = " | ".join(str(c) for c in df.columns)
|
|
372
|
+
sep = " | ".join(["---"] * len(df.columns))
|
|
373
|
+
body_rows = df.head(50).values.tolist()
|
|
374
|
+
body = "\n".join(" | ".join(str(v)[:30] for v in r) for r in body_rows)
|
|
375
|
+
parts.append(f"\n[Sheet: {sname}] ({len(df)} 行 × {len(df.columns)} 列)\n"
|
|
376
|
+
f"{header}\n{sep}\n{body}")
|
|
377
|
+
|
|
378
|
+
# Basic stats for numeric columns
|
|
379
|
+
num_cols = df.select_dtypes(include="number") if hasattr(df, "select_dtypes") else None
|
|
380
|
+
if num_cols is not None and not num_cols.empty:
|
|
381
|
+
stats_lines = []
|
|
382
|
+
for col in list(num_cols.columns)[:5]:
|
|
383
|
+
s = num_cols[col].describe()
|
|
384
|
+
stats_lines.append(f" {col}: 均值={s.get('mean','')} 最大={s.get('max','')} 最小={s.get('min','')}")
|
|
385
|
+
if stats_lines:
|
|
386
|
+
parts.append("[数值统计]\n" + "\n".join(stats_lines))
|
|
387
|
+
|
|
388
|
+
full_text = "\n".join(parts)
|
|
389
|
+
truncated = len(full_text) > max_chars
|
|
390
|
+
return FileContent(True, "xlsx", str(path), path.name, size_kb,
|
|
391
|
+
full_text[:max_chars], meta, tables,
|
|
392
|
+
truncated=truncated)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# ── CSV ───────────────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
def _parse_csv(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
398
|
+
size_kb = path.stat().st_size / 1024
|
|
399
|
+
if not _pd:
|
|
400
|
+
# Fallback: plain text
|
|
401
|
+
return _parse_text(path, max_chars, include_images)
|
|
402
|
+
|
|
403
|
+
import pandas as pd
|
|
404
|
+
# Detect delimiter
|
|
405
|
+
delim = "\t" if path.suffix.lower() == ".tsv" else ","
|
|
406
|
+
try:
|
|
407
|
+
df = pd.read_csv(str(path), sep=delim, nrows=MAX_TABLE_ROWS,
|
|
408
|
+
encoding="utf-8", on_bad_lines="skip")
|
|
409
|
+
except Exception:
|
|
410
|
+
try:
|
|
411
|
+
df = pd.read_csv(str(path), sep=delim, nrows=MAX_TABLE_ROWS,
|
|
412
|
+
encoding="gbk", on_bad_lines="skip")
|
|
413
|
+
except Exception as e:
|
|
414
|
+
return FileContent(False, "csv", str(path), path.name, size_kb, "",
|
|
415
|
+
error=f"CSV 读取失败: {e}")
|
|
416
|
+
|
|
417
|
+
meta: Dict[str, Any] = {
|
|
418
|
+
"rows": len(df),
|
|
419
|
+
"columns": len(df.columns),
|
|
420
|
+
"col_names": list(df.columns)[:30],
|
|
421
|
+
}
|
|
422
|
+
try:
|
|
423
|
+
desc = df.describe(include="all").to_string()
|
|
424
|
+
meta["stats_preview"] = desc[:1000]
|
|
425
|
+
except Exception:
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
rows = [list(df.columns)] + df.values.tolist()
|
|
429
|
+
header = " | ".join(str(c) for c in df.columns)
|
|
430
|
+
sep = " | ".join(["---"] * len(df.columns))
|
|
431
|
+
body = "\n".join(" | ".join(str(v)[:25] for v in r) for r in df.head(80).values.tolist())
|
|
432
|
+
full_text = f"[CSV: {path.name}] {len(df)} 行 × {len(df.columns)} 列\n{header}\n{sep}\n{body}"
|
|
433
|
+
truncated = len(full_text) > max_chars
|
|
434
|
+
return FileContent(True, "csv", str(path), path.name, size_kb,
|
|
435
|
+
full_text[:max_chars], meta, [rows[:MAX_TABLE_ROWS]],
|
|
436
|
+
truncated=truncated)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ── JSON ──────────────────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
def _parse_json(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
442
|
+
size_kb = path.stat().st_size / 1024
|
|
443
|
+
suffix = path.suffix.lower()
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
if suffix == ".jsonl":
|
|
447
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
448
|
+
records = []
|
|
449
|
+
for line in lines[:500]:
|
|
450
|
+
line = line.strip()
|
|
451
|
+
if line:
|
|
452
|
+
try: records.append(json.loads(line))
|
|
453
|
+
except Exception: pass
|
|
454
|
+
data = records
|
|
455
|
+
meta = {"format": "JSONL", "records": len(records), "sample": records[:3]}
|
|
456
|
+
else:
|
|
457
|
+
raw = path.read_text(encoding="utf-8")
|
|
458
|
+
data = json.loads(raw)
|
|
459
|
+
meta = {"format": "JSON", "type": type(data).__name__}
|
|
460
|
+
if isinstance(data, list):
|
|
461
|
+
meta["length"] = len(data)
|
|
462
|
+
meta["sample"] = data[:3]
|
|
463
|
+
elif isinstance(data, dict):
|
|
464
|
+
meta["keys"] = list(data.keys())[:20]
|
|
465
|
+
|
|
466
|
+
# Pretty-print (truncated)
|
|
467
|
+
text = json.dumps(data, ensure_ascii=False, indent=2)
|
|
468
|
+
truncated = len(text) > max_chars
|
|
469
|
+
return FileContent(True, "json", str(path), path.name, size_kb,
|
|
470
|
+
text[:max_chars], meta, truncated=truncated)
|
|
471
|
+
except Exception as e:
|
|
472
|
+
return FileContent(False, "json", str(path), path.name, size_kb, "",
|
|
473
|
+
error=f"JSON 解析失败: {e}")
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ── HTML ──────────────────────────────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
def _parse_html(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
479
|
+
size_kb = path.stat().st_size / 1024
|
|
480
|
+
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
481
|
+
meta: Dict[str, Any] = {}
|
|
482
|
+
|
|
483
|
+
if _bs4:
|
|
484
|
+
from bs4 import BeautifulSoup
|
|
485
|
+
soup = BeautifulSoup(raw, "html.parser")
|
|
486
|
+
# Remove scripts/styles
|
|
487
|
+
for tag in soup(["script", "style", "nav", "footer", "header"]):
|
|
488
|
+
tag.decompose()
|
|
489
|
+
title = soup.find("title")
|
|
490
|
+
meta["title"] = title.get_text().strip() if title else ""
|
|
491
|
+
# Get main content
|
|
492
|
+
main = soup.find("main") or soup.find("article") or soup.find("body") or soup
|
|
493
|
+
text = main.get_text(separator="\n").strip()
|
|
494
|
+
# Collapse blank lines
|
|
495
|
+
text = re.sub(r"\n{3,}", "\n\n", text)
|
|
496
|
+
else:
|
|
497
|
+
# Simple tag stripping
|
|
498
|
+
text = re.sub(r"<[^>]+>", " ", raw)
|
|
499
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
500
|
+
|
|
501
|
+
truncated = len(text) > max_chars
|
|
502
|
+
return FileContent(True, "html", str(path), path.name, size_kb,
|
|
503
|
+
text[:max_chars], meta, truncated=truncated)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# ── Plain Text / Markdown / Code ──────────────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
def _parse_text(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
509
|
+
size_kb = path.stat().st_size / 1024
|
|
510
|
+
for enc in ("utf-8", "gbk", "latin-1"):
|
|
511
|
+
try:
|
|
512
|
+
text = path.read_text(encoding=enc)
|
|
513
|
+
break
|
|
514
|
+
except UnicodeDecodeError:
|
|
515
|
+
continue
|
|
516
|
+
else:
|
|
517
|
+
text = path.read_bytes().decode("utf-8", errors="replace")
|
|
518
|
+
|
|
519
|
+
lines = text.count("\n")
|
|
520
|
+
meta = {"lines": lines, "encoding": "utf-8"}
|
|
521
|
+
truncated = len(text) > max_chars
|
|
522
|
+
return FileContent(True, "text", str(path), path.name, size_kb,
|
|
523
|
+
text[:max_chars], meta, truncated=truncated)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _parse_code(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
527
|
+
size_kb = path.stat().st_size / 1024
|
|
528
|
+
for enc in ("utf-8", "gbk", "latin-1"):
|
|
529
|
+
try:
|
|
530
|
+
text = path.read_text(encoding=enc)
|
|
531
|
+
break
|
|
532
|
+
except UnicodeDecodeError:
|
|
533
|
+
continue
|
|
534
|
+
else:
|
|
535
|
+
text = path.read_bytes().decode("utf-8", errors="replace")
|
|
536
|
+
|
|
537
|
+
suffix = path.suffix.lstrip(".").lower()
|
|
538
|
+
lines = text.splitlines()
|
|
539
|
+
meta: Dict[str, Any] = {
|
|
540
|
+
"language": suffix,
|
|
541
|
+
"lines": len(lines),
|
|
542
|
+
"blank_lines": sum(1 for l in lines if not l.strip()),
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# Extract function/class names
|
|
546
|
+
_PATTERNS = {
|
|
547
|
+
"py": (r"^(?:async\s+)?def\s+(\w+)", r"^class\s+(\w+)"),
|
|
548
|
+
"js": (r"function\s+(\w+)\s*\(", r"class\s+(\w+)"),
|
|
549
|
+
"ts": (r"function\s+(\w+)\s*\(", r"class\s+(\w+)", r"interface\s+(\w+)"),
|
|
550
|
+
"go": (r"^func\s+\(?[^)]*\)?\s*(\w+)\s*\(", r"^type\s+(\w+)\s+struct"),
|
|
551
|
+
"java": (r"(?:public|private|protected)?\s+\w+\s+(\w+)\s*\(", r"class\s+(\w+)"),
|
|
552
|
+
"rs": (r"^(?:pub\s+)?fn\s+(\w+)", r"^(?:pub\s+)?struct\s+(\w+)"),
|
|
553
|
+
}
|
|
554
|
+
patterns = _PATTERNS.get(suffix, [])
|
|
555
|
+
symbols = []
|
|
556
|
+
for pat in patterns:
|
|
557
|
+
found = re.findall(pat, text, re.MULTILINE)
|
|
558
|
+
symbols.extend(found[:20])
|
|
559
|
+
if symbols:
|
|
560
|
+
meta["symbols"] = list(dict.fromkeys(symbols))[:30]
|
|
561
|
+
|
|
562
|
+
truncated = len(text) > max_chars
|
|
563
|
+
return FileContent(True, "code", str(path), path.name, size_kb,
|
|
564
|
+
text[:max_chars], meta, truncated=truncated)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
# ── Image ─────────────────────────────────────────────────────────────────────
|
|
568
|
+
|
|
569
|
+
def _parse_image(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
570
|
+
size_kb = path.stat().st_size / 1024
|
|
571
|
+
suffix = path.suffix.lstrip(".").lower()
|
|
572
|
+
mime = {"png":"image/png","jpg":"image/jpeg","jpeg":"image/jpeg",
|
|
573
|
+
"gif":"image/gif","webp":"image/webp","bmp":"image/bmp"}.get(suffix,"image/png")
|
|
574
|
+
meta: Dict[str, Any] = {"mime": mime, "size_kb": round(size_kb, 1)}
|
|
575
|
+
|
|
576
|
+
if _PIL:
|
|
577
|
+
from PIL import Image
|
|
578
|
+
try:
|
|
579
|
+
with Image.open(str(path)) as img:
|
|
580
|
+
meta["width"] = img.width
|
|
581
|
+
meta["height"] = img.height
|
|
582
|
+
meta["mode"] = img.mode
|
|
583
|
+
meta["format"] = img.format
|
|
584
|
+
if hasattr(img, "_getexif") and img._getexif():
|
|
585
|
+
meta["has_exif"] = True
|
|
586
|
+
except Exception:
|
|
587
|
+
pass
|
|
588
|
+
|
|
589
|
+
# Base64 for vision models
|
|
590
|
+
images_b64 = []
|
|
591
|
+
if include_images and size_kb < 10 * 1024: # < 10MB
|
|
592
|
+
try:
|
|
593
|
+
b64 = base64.b64encode(path.read_bytes()).decode()
|
|
594
|
+
images_b64 = [f"data:{mime};base64,{b64}"]
|
|
595
|
+
except Exception:
|
|
596
|
+
pass
|
|
597
|
+
|
|
598
|
+
text = (f"[图片文件: {path.name}]\n"
|
|
599
|
+
f"尺寸: {meta.get('width','?')}×{meta.get('height','?')} px\n"
|
|
600
|
+
f"格式: {meta.get('format',suffix.upper())}\n"
|
|
601
|
+
f"大小: {size_kb:.1f} KB")
|
|
602
|
+
return FileContent(True, "image", str(path), path.name, size_kb,
|
|
603
|
+
text, meta, images_b64=images_b64)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _parse_video(path: Path, max_chars: int, include_images: bool) -> FileContent:
|
|
607
|
+
"""Video: metadata + evenly-spaced keyframes (base64) for vision models.
|
|
608
|
+
|
|
609
|
+
Needs opencv-python; degrades to metadata-only with an install hint when it
|
|
610
|
+
is absent (it is an optional 'video' extra, not a core dependency).
|
|
611
|
+
"""
|
|
612
|
+
size_kb = path.stat().st_size / 1024
|
|
613
|
+
suffix = path.suffix.lstrip(".").lower()
|
|
614
|
+
meta: Dict[str, Any] = {"mime": f"video/{suffix}", "size_kb": round(size_kb, 1)}
|
|
615
|
+
|
|
616
|
+
try:
|
|
617
|
+
import cv2 # type: ignore
|
|
618
|
+
except Exception:
|
|
619
|
+
return FileContent(
|
|
620
|
+
True, "video", str(path), path.name, size_kb,
|
|
621
|
+
(f"[视频文件: {path.name}] {size_kb/1024:.1f} MB\n"
|
|
622
|
+
"未安装 opencv-python,无法抽帧分析。安装后重试: "
|
|
623
|
+
"pip install opencv-python 或 /install opencv-python"),
|
|
624
|
+
meta,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
images_b64: List[str] = []
|
|
628
|
+
try:
|
|
629
|
+
cap = cv2.VideoCapture(str(path))
|
|
630
|
+
fps = float(cap.get(cv2.CAP_PROP_FPS) or 0)
|
|
631
|
+
frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
|
|
632
|
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 0)
|
|
633
|
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 0)
|
|
634
|
+
duration = frames / fps if fps else 0.0
|
|
635
|
+
meta.update({"fps": round(fps, 2), "frames": frames, "width": width,
|
|
636
|
+
"height": height, "duration_sec": round(duration, 1)})
|
|
637
|
+
|
|
638
|
+
# Sample keyframes between 10%–90% (skip black intros/outros). Only when
|
|
639
|
+
# images are requested — decoding frames is expensive.
|
|
640
|
+
if include_images and frames > 0:
|
|
641
|
+
import base64
|
|
642
|
+
n = min(4, frames)
|
|
643
|
+
if n == 1:
|
|
644
|
+
positions = [frames // 2]
|
|
645
|
+
else:
|
|
646
|
+
positions = [int(frames * (0.1 + 0.8 * i / (n - 1))) for i in range(n)]
|
|
647
|
+
for fi in positions:
|
|
648
|
+
cap.set(cv2.CAP_PROP_POS_FRAMES, fi)
|
|
649
|
+
ok, frame = cap.read()
|
|
650
|
+
if not ok:
|
|
651
|
+
continue
|
|
652
|
+
ok2, buf = cv2.imencode(".jpg", frame)
|
|
653
|
+
if ok2:
|
|
654
|
+
images_b64.append("data:image/jpeg;base64," + base64.b64encode(buf.tobytes()).decode())
|
|
655
|
+
cap.release()
|
|
656
|
+
except Exception as e:
|
|
657
|
+
return FileContent(True, "video", str(path), path.name, size_kb,
|
|
658
|
+
f"[视频文件: {path.name}] 读取失败: {e}", meta)
|
|
659
|
+
|
|
660
|
+
text = (f"[视频文件: {path.name}]\n"
|
|
661
|
+
f"时长: {meta.get('duration_sec','?')} 秒 帧率: {meta.get('fps','?')} fps\n"
|
|
662
|
+
f"分辨率: {meta.get('width','?')}×{meta.get('height','?')} 总帧数: {meta.get('frames','?')}\n"
|
|
663
|
+
f"已抽取 {len(images_b64)} 个关键帧供视觉模型分析。")
|
|
664
|
+
return FileContent(True, "video", str(path), path.name, size_kb,
|
|
665
|
+
text, meta, images_b64=images_b64)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
# ── 多层分析提示词生成器 ──────────────────────────────────────────────────────
|
|
669
|
+
|
|
670
|
+
def build_analysis_prompt(fc: FileContent, layer: int = 1,
|
|
671
|
+
domain: str = "auto",
|
|
672
|
+
question: str = "") -> str:
|
|
673
|
+
"""
|
|
674
|
+
为不同分析层次生成系统/用户提示词。
|
|
675
|
+
|
|
676
|
+
layer 1 — 快速摘要 (300 字以内,Who/What/When/Why/Key metrics)
|
|
677
|
+
layer 2 — 深度分析 (结构、要点、数据模式、异常值)
|
|
678
|
+
layer 3 — 领域洞察 (财务/法律/技术/学术 — 由 domain 控制)
|
|
679
|
+
layer 4 — 行动建议 (风险点、优化建议、下一步)
|
|
680
|
+
"""
|
|
681
|
+
type_zh = {
|
|
682
|
+
"pdf": "PDF 文档",
|
|
683
|
+
"docx": "Word 文档",
|
|
684
|
+
"xlsx": "Excel 表格",
|
|
685
|
+
"csv": "CSV 数据文件",
|
|
686
|
+
"json": "JSON 数据",
|
|
687
|
+
"image": "图片",
|
|
688
|
+
"code": "代码文件",
|
|
689
|
+
"text": "文本文件",
|
|
690
|
+
"html": "网页文件",
|
|
691
|
+
}.get(fc.file_type, "文件")
|
|
692
|
+
|
|
693
|
+
trunc_note = "\n\n⚠️ 注意:文件内容已被截断(超出上下文限制),以下为前段内容。" if fc.truncated else ""
|
|
694
|
+
meta_summary = _format_meta(fc)
|
|
695
|
+
|
|
696
|
+
# Auto-detect domain from content
|
|
697
|
+
if domain == "auto":
|
|
698
|
+
domain = _detect_domain(fc)
|
|
699
|
+
|
|
700
|
+
_DOMAIN_CONTEXT = {
|
|
701
|
+
"finance": "你是资深财务分析师,专注财报分析、现金流、盈利质量、风险敞口。",
|
|
702
|
+
"legal": "你是法律顾问,关注合同条款、风险条款、义务约束、免责声明。",
|
|
703
|
+
"tech": "你是高级软件工程师,评估代码质量、架构、安全、性能、可维护性。",
|
|
704
|
+
"research":"你是研究员,提炼论文方法论、数据、结论、局限性及引用。",
|
|
705
|
+
"realty": "你是不动产分析师,关注物业数据、租金、估值、市场趋势、合规风险。",
|
|
706
|
+
"medical": "你是医学顾问,总结诊断信息、治疗方案、用药风险(不构成诊断建议)。",
|
|
707
|
+
"general": "你是专业文档分析师,全面理解文件内容。",
|
|
708
|
+
}
|
|
709
|
+
domain_ctx = _DOMAIN_CONTEXT.get(domain, _DOMAIN_CONTEXT["general"])
|
|
710
|
+
|
|
711
|
+
layers = {
|
|
712
|
+
1: f"""请对以下{type_zh}进行**快速摘要分析**(300字以内):
|
|
713
|
+
{meta_summary}
|
|
714
|
+
要求:
|
|
715
|
+
1. 用一句话说明文件核心主题
|
|
716
|
+
2. 列出 3-5 个关键发现/数据点
|
|
717
|
+
3. 标注文件覆盖的时间范围(如有)
|
|
718
|
+
4. 指出最重要的结论或结果
|
|
719
|
+
{trunc_note}
|
|
720
|
+
|
|
721
|
+
---文件内容---
|
|
722
|
+
{fc.content[:SUMMARY_CHARS]}""",
|
|
723
|
+
|
|
724
|
+
2: f"""请对以下{type_zh}进行**深度内容分析**:
|
|
725
|
+
{meta_summary}
|
|
726
|
+
分析维度:
|
|
727
|
+
1. **结构分析** — 文件章节/字段组织,逻辑流程
|
|
728
|
+
2. **数据要点** — 关键数字、趋势、比较(列表格式)
|
|
729
|
+
3. **异常与亮点** — 与常规预期显著不同的点
|
|
730
|
+
4. **信息完整性** — 是否有缺失、矛盾或模糊内容
|
|
731
|
+
5. **数据质量** — 若为数据文件:空值率、一致性
|
|
732
|
+
{trunc_note}
|
|
733
|
+
|
|
734
|
+
---文件内容---
|
|
735
|
+
{fc.content}""",
|
|
736
|
+
|
|
737
|
+
3: f"""{domain_ctx}
|
|
738
|
+
|
|
739
|
+
请对以下{type_zh}进行**领域专项分析**:
|
|
740
|
+
{meta_summary}
|
|
741
|
+
重点关注:
|
|
742
|
+
1. **核心指标解读** — 本领域最重要的量化指标及其含义
|
|
743
|
+
2. **潜在风险** — 文件中反映或隐含的风险点
|
|
744
|
+
3. **与行业基准的偏差** — 什么是正常水平,当前数据如何?
|
|
745
|
+
4. **合规/规范性** — 是否符合本领域常规标准
|
|
746
|
+
5. **深层逻辑** — 表面数据背后的原因/驱动因素
|
|
747
|
+
{trunc_note}
|
|
748
|
+
|
|
749
|
+
---文件内容---
|
|
750
|
+
{fc.content}""",
|
|
751
|
+
|
|
752
|
+
4: f"""{domain_ctx}
|
|
753
|
+
|
|
754
|
+
基于以下{type_zh}的内容,请给出**可执行的行动建议**:
|
|
755
|
+
{meta_summary}
|
|
756
|
+
要求:
|
|
757
|
+
1. **立即行动** — 需要立刻处理的事项(按优先级)
|
|
758
|
+
2. **改进建议** — 中期可以优化的方面
|
|
759
|
+
3. **风险预警** — 需要关注但未必立刻行动的隐患
|
|
760
|
+
4. **问题清单** — 文件不清晰/需要补充的 3-5 个问题
|
|
761
|
+
5. **下一步** — 建议的后续分析或决策步骤
|
|
762
|
+
{trunc_note}
|
|
763
|
+
|
|
764
|
+
---文件内容---
|
|
765
|
+
{fc.content[:max(len(fc.content)//2, SUMMARY_CHARS)]}""",
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
base_prompt = layers.get(layer, layers[1])
|
|
769
|
+
if question:
|
|
770
|
+
base_prompt = (f"关于以下{type_zh},用户提问:\n\n**{question}**\n\n"
|
|
771
|
+
f"{meta_summary}{trunc_note}\n\n---文件内容---\n{fc.content}")
|
|
772
|
+
return base_prompt
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _format_meta(fc: FileContent) -> str:
|
|
776
|
+
"""Format metadata for prompt header."""
|
|
777
|
+
lines = [f"文件名: {fc.filename}", f"类型: {fc.file_type.upper()}",
|
|
778
|
+
f"大小: {fc.size_kb:.1f} KB"]
|
|
779
|
+
for k, v in fc.metadata.items():
|
|
780
|
+
if k in ("pages","rows","columns","lines","language","sheets",
|
|
781
|
+
"records","length","sheet_count","paragraphs","tables",
|
|
782
|
+
"title","author","created"):
|
|
783
|
+
lines.append(f"{k}: {v}")
|
|
784
|
+
if fc.tables:
|
|
785
|
+
lines.append(f"包含表格: {len(fc.tables)} 个")
|
|
786
|
+
return " ".join(lines[:8])
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _detect_domain(fc: FileContent) -> str:
|
|
790
|
+
"""Auto-detect analysis domain from content keywords."""
|
|
791
|
+
text_lower = fc.content[:2000].lower()
|
|
792
|
+
scores = {
|
|
793
|
+
"finance": sum(text_lower.count(k) for k in ["revenue","profit","ebitda","cashflow",
|
|
794
|
+
"净利润","营收","现金流","资产负债","毛利率","eps","roe","pe","财报"]),
|
|
795
|
+
"legal": sum(text_lower.count(k) for k in ["合同","甲方","乙方","违约","协议",
|
|
796
|
+
"条款","liability","indemnify","agreement","breach"]),
|
|
797
|
+
"tech": sum(text_lower.count(k) for k in ["def ","class ","function","import",
|
|
798
|
+
"select ","return ","var ","const ","type "]) + (2 if fc.file_type=="code" else 0),
|
|
799
|
+
"research": sum(text_lower.count(k) for k in ["abstract","methodology","conclusion",
|
|
800
|
+
"hypothesis","摘要","结论","方法","样本量","显著性","p-value"]),
|
|
801
|
+
"realty": sum(text_lower.count(k) for k in ["租金","房价","物业","不动产","reit",
|
|
802
|
+
"产权","容积率","建筑面积","房地产","地块"]),
|
|
803
|
+
}
|
|
804
|
+
best = max(scores, key=scores.get)
|
|
805
|
+
return best if scores[best] >= 2 else "general"
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
# ── Session file store (used by /file command) ────────────────────────────────
|
|
809
|
+
|
|
810
|
+
class FileSession:
|
|
811
|
+
"""
|
|
812
|
+
Holds loaded files for the current REPL session.
|
|
813
|
+
Provides multi-turn Q&A context injection.
|
|
814
|
+
"""
|
|
815
|
+
def __init__(self):
|
|
816
|
+
self._files: Dict[str, FileContent] = {} # name → FileContent
|
|
817
|
+
self._active: Optional[str] = None
|
|
818
|
+
|
|
819
|
+
def load(self, path_str: str, include_images: bool = False) -> FileContent:
|
|
820
|
+
fc = parse_file(path_str, include_images=include_images)
|
|
821
|
+
if fc.success:
|
|
822
|
+
self._files[fc.filename] = fc
|
|
823
|
+
self._active = fc.filename
|
|
824
|
+
return fc
|
|
825
|
+
|
|
826
|
+
def get_active(self) -> Optional[FileContent]:
|
|
827
|
+
if self._active and self._active in self._files:
|
|
828
|
+
return self._files[self._active]
|
|
829
|
+
return None
|
|
830
|
+
|
|
831
|
+
def set_active(self, name: str) -> bool:
|
|
832
|
+
if name in self._files:
|
|
833
|
+
self._active = name
|
|
834
|
+
return True
|
|
835
|
+
# Partial match
|
|
836
|
+
for key in self._files:
|
|
837
|
+
if name.lower() in key.lower():
|
|
838
|
+
self._active = key
|
|
839
|
+
return True
|
|
840
|
+
return False
|
|
841
|
+
|
|
842
|
+
def list_files(self) -> List[Dict[str, Any]]:
|
|
843
|
+
result = []
|
|
844
|
+
for name, fc in self._files.items():
|
|
845
|
+
result.append({
|
|
846
|
+
"filename": name,
|
|
847
|
+
"type": fc.file_type,
|
|
848
|
+
"size_kb": round(fc.size_kb, 1),
|
|
849
|
+
"chars": fc.char_count,
|
|
850
|
+
"active": name == self._active,
|
|
851
|
+
"truncated": fc.truncated,
|
|
852
|
+
})
|
|
853
|
+
return result
|
|
854
|
+
|
|
855
|
+
def build_context_block(self, max_chars: int = 12_000) -> str:
|
|
856
|
+
"""Return a context block to inject into the system prompt."""
|
|
857
|
+
fc = self.get_active()
|
|
858
|
+
if not fc:
|
|
859
|
+
return ""
|
|
860
|
+
meta = _format_meta(fc)
|
|
861
|
+
content_preview = fc.content[:max_chars]
|
|
862
|
+
trunc = f"\n[内容已截断至前 {max_chars} 字符]" if fc.truncated or len(fc.content) > max_chars else ""
|
|
863
|
+
return (f"\n\n---已加载文件: {fc.filename}---\n"
|
|
864
|
+
f"{meta}\n\n{content_preview}{trunc}\n---文件结束---")
|
|
865
|
+
|
|
866
|
+
def clear(self, name: Optional[str] = None):
|
|
867
|
+
if name:
|
|
868
|
+
self._files.pop(name, None)
|
|
869
|
+
if self._active == name:
|
|
870
|
+
self._active = next(iter(self._files), None)
|
|
871
|
+
else:
|
|
872
|
+
self._files.clear()
|
|
873
|
+
self._active = None
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# ── Dependency check ──────────────────────────────────────────────────────────
|
|
877
|
+
|
|
878
|
+
def check_parsers() -> Dict[str, bool]:
|
|
879
|
+
return refresh_optional_parsers()
|
|
880
|
+
|