android-midscene-automation 0.2.4 → 1.1.1
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.
- package/CHANGELOG.md +10 -0
- package/README.md +159 -29
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +4 -2
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +12 -0
- package/src/App.vue +10 -5
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +340 -121
- package/src/types.ts +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Failure classification and circuit breaking for video model calls.
|
|
16
|
+
|
|
17
|
+
Thin media-aware extension of :mod:`artemis.llm.reliability`, which owns the
|
|
18
|
+
generic taxonomy and circuit breaker. This module adds the media-processing
|
|
19
|
+
category and the ``should_split`` recovery decision (bisecting long clips).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from enum import StrEnum
|
|
26
|
+
|
|
27
|
+
from artemis.llm.reliability import (
|
|
28
|
+
CircuitBreaker,
|
|
29
|
+
FailureCategory,
|
|
30
|
+
classify_failure,
|
|
31
|
+
extract_status_code,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VideoFailureCategory(StrEnum):
|
|
36
|
+
RATE_LIMIT = "rate_limit"
|
|
37
|
+
PROVIDER_UNAVAILABLE = "provider_unavailable"
|
|
38
|
+
TIMEOUT = "timeout"
|
|
39
|
+
CONNECTION = "connection"
|
|
40
|
+
AUTHENTICATION = "authentication"
|
|
41
|
+
BAD_REQUEST = "bad_request"
|
|
42
|
+
MEDIA_PROCESSING = "media_processing"
|
|
43
|
+
CANCELLED = "cancelled"
|
|
44
|
+
UNKNOWN = "unknown"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class VideoFailure:
|
|
49
|
+
category: VideoFailureCategory
|
|
50
|
+
retryable: bool
|
|
51
|
+
should_split: bool
|
|
52
|
+
should_fallback: bool
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_MEDIA_MARKERS = ("ffmpeg", "codec", "corrupt", "invalid video", "invalid audio", "media")
|
|
56
|
+
_AGENTIC_REJECTION_MARKERS = ("processing", "agentic")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SubAgentAnswerExhausted(RuntimeError):
|
|
60
|
+
"""The sub-agent exhausted its turns without a valid submit_answer.
|
|
61
|
+
|
|
62
|
+
The caller records a failed segment so a later claim can retry it. The shared
|
|
63
|
+
classifier permits retries, splitting, and fallback for this failure.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, turns: int, reason: str):
|
|
67
|
+
self.turns = turns
|
|
68
|
+
self.reason = reason
|
|
69
|
+
super().__init__(
|
|
70
|
+
f"submit_answer was not accepted within {turns} turn(s); last rejection: {reason}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AgenticVideoDegraded(RuntimeError):
|
|
75
|
+
"""Return an interval to the coordinator after switching to static mode.
|
|
76
|
+
|
|
77
|
+
The coordinator splits oversized clips and retries smaller clips as-is.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_agentic_rejection(failure: VideoFailure, error: BaseException) -> bool:
|
|
82
|
+
"""True when a bad request is the API refusing agentic video processing.
|
|
83
|
+
|
|
84
|
+
Only an HTTP 400 whose message mentions ``processing`` / ``agentic``
|
|
85
|
+
qualifies; other bad requests (413 payload too large, 404, ...) keep the
|
|
86
|
+
shared handling.
|
|
87
|
+
"""
|
|
88
|
+
if failure.category is not VideoFailureCategory.BAD_REQUEST:
|
|
89
|
+
return False
|
|
90
|
+
if extract_status_code(error) != 400:
|
|
91
|
+
return False
|
|
92
|
+
message = str(error).lower()
|
|
93
|
+
return any(marker in message for marker in _AGENTIC_REJECTION_MARKERS)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def classify_video_failure(error: BaseException) -> VideoFailure:
|
|
97
|
+
"""Convert provider/transport/media exceptions into recovery decisions."""
|
|
98
|
+
if isinstance(error, AgenticVideoDegraded):
|
|
99
|
+
return VideoFailure(VideoFailureCategory.BAD_REQUEST, False, True, False)
|
|
100
|
+
generic = classify_failure(error)
|
|
101
|
+
message = str(error).lower()
|
|
102
|
+
has_media_marker = any(marker in message for marker in _MEDIA_MARKERS)
|
|
103
|
+
|
|
104
|
+
# Media markers take precedence over generic connection / bad-request /
|
|
105
|
+
# unknown classification (rate-limit, unavailable, timeout, and auth
|
|
106
|
+
# signals still win, matching the original ordering of checks).
|
|
107
|
+
if has_media_marker and generic.category in {
|
|
108
|
+
FailureCategory.CONNECTION,
|
|
109
|
+
FailureCategory.BAD_REQUEST,
|
|
110
|
+
FailureCategory.UNKNOWN,
|
|
111
|
+
}:
|
|
112
|
+
return VideoFailure(VideoFailureCategory.MEDIA_PROCESSING, False, True, True)
|
|
113
|
+
|
|
114
|
+
category = VideoFailureCategory(generic.category.value)
|
|
115
|
+
should_split = category in {VideoFailureCategory.TIMEOUT, VideoFailureCategory.UNKNOWN} or (
|
|
116
|
+
category is VideoFailureCategory.BAD_REQUEST and extract_status_code(error) == 413
|
|
117
|
+
)
|
|
118
|
+
# The video pipeline keeps its historical behavior of falling back to the
|
|
119
|
+
# universal analyzer even for bad requests (a different provider may accept
|
|
120
|
+
# the same media payload).
|
|
121
|
+
should_fallback = generic.should_fallback or category is VideoFailureCategory.BAD_REQUEST
|
|
122
|
+
if category is VideoFailureCategory.CANCELLED:
|
|
123
|
+
should_fallback = False
|
|
124
|
+
return VideoFailure(category, generic.retryable, should_split, should_fallback)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class VideoCircuitBreaker(CircuitBreaker):
|
|
128
|
+
"""Context-scoped circuit breaker keyed by provider/model identity."""
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Universal (OpenAI-compatible / LangChain) execution paths for the video analyzer.
|
|
16
|
+
|
|
17
|
+
Extracted from ``video_analyzer.py`` as a pure structural split. Patchable
|
|
18
|
+
collaborators (``extract_keyframes_from_video``, ``extract_audio_from_video``,
|
|
19
|
+
semaphores, retry helper, ...) are looked up late through the facade module so
|
|
20
|
+
``mock.patch("artemis.agents.video_analyzer.video_analyzer.<name>")`` targets
|
|
21
|
+
keep working.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import base64
|
|
26
|
+
import glob
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
import re
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from langchain_core.messages import (
|
|
32
|
+
BaseMessage,
|
|
33
|
+
HumanMessage,
|
|
34
|
+
SystemMessage,
|
|
35
|
+
ToolMessage,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from artemis.agents.video_analyzer import video_analyzer as _va
|
|
39
|
+
from artemis.agents.video_analyzer.reliability import (
|
|
40
|
+
SubAgentAnswerExhausted,
|
|
41
|
+
classify_video_failure,
|
|
42
|
+
)
|
|
43
|
+
from artemis.data_engine.trace import CURRENT_TRACE_ID, TraceSpan
|
|
44
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
45
|
+
|
|
46
|
+
logger = get_logger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def exec_single_chunk_universal(
|
|
50
|
+
analyzer,
|
|
51
|
+
compressed_path: Path,
|
|
52
|
+
raw_path: Path,
|
|
53
|
+
start_time: float,
|
|
54
|
+
end_time: float | None,
|
|
55
|
+
actual_start: float,
|
|
56
|
+
prompt_with_context: str,
|
|
57
|
+
specific_query: str,
|
|
58
|
+
force_fallback: bool = False,
|
|
59
|
+
) -> str:
|
|
60
|
+
"""Executes sub-agent chunk analysis via keyframe extraction and LangChain ChatModel."""
|
|
61
|
+
keyframes = _gather_universal_frames(analyzer, raw_path, start_time, end_time, actual_start)
|
|
62
|
+
|
|
63
|
+
user_blocks, audio_block = await _build_universal_user_blocks(
|
|
64
|
+
analyzer, keyframes, actual_start, prompt_with_context, raw_path
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
messages = [
|
|
68
|
+
SystemMessage(content=analyzer.sub_system_prompt),
|
|
69
|
+
HumanMessage(content=user_blocks),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
trace_id = CURRENT_TRACE_ID.get()
|
|
73
|
+
if analyzer.ctx.data_engine and trace_id:
|
|
74
|
+
analyzer.ctx.data_engine.record_trace(
|
|
75
|
+
type="llm_call",
|
|
76
|
+
name="video_sub_agent_universal",
|
|
77
|
+
payload={
|
|
78
|
+
"num_keyframes": len(keyframes),
|
|
79
|
+
"prompt": prompt_with_context,
|
|
80
|
+
"system_instruction": analyzer.sub_system_prompt,
|
|
81
|
+
},
|
|
82
|
+
parent_trace_id=trace_id,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
response = await _invoke_universal_with_visual_retry(
|
|
86
|
+
analyzer, messages, user_blocks, audio_block, force_fallback
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
final_summary, final_analysis, timeline_events = _extract_submit_payload(response)
|
|
90
|
+
|
|
91
|
+
await _persist_universal_events(
|
|
92
|
+
analyzer,
|
|
93
|
+
timeline_events,
|
|
94
|
+
start_time,
|
|
95
|
+
end_time,
|
|
96
|
+
actual_start,
|
|
97
|
+
raw_path,
|
|
98
|
+
specific_query,
|
|
99
|
+
final_summary,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
end_str = f"{end_time:.1f}s" if end_time is not None else "unknown"
|
|
103
|
+
return (
|
|
104
|
+
f"[from {start_time:.1f}s to {end_str}] Summary: {final_summary} Analysis: {final_analysis}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _gather_universal_frames(
|
|
109
|
+
analyzer,
|
|
110
|
+
raw_path: Path,
|
|
111
|
+
start_time: float,
|
|
112
|
+
end_time: float | None,
|
|
113
|
+
actual_start: float,
|
|
114
|
+
) -> list:
|
|
115
|
+
"""Extracts uniform keyframes plus dense action-window frames, merged by timestamp."""
|
|
116
|
+
# Universal providers consume stills rather than the uploaded clip. Use
|
|
117
|
+
# the raw extracted segment so slowdown/transcode PTS never skew labels.
|
|
118
|
+
keyframes = _va.extract_keyframes_from_video(
|
|
119
|
+
raw_path,
|
|
120
|
+
fps=1.0,
|
|
121
|
+
max_frames=30,
|
|
122
|
+
max_dimension=1080,
|
|
123
|
+
)
|
|
124
|
+
dense_offsets = analyzer._dense_action_offsets(
|
|
125
|
+
start_time,
|
|
126
|
+
end_time if end_time is not None else start_time,
|
|
127
|
+
actual_start,
|
|
128
|
+
)
|
|
129
|
+
targeted_frames = _va.extract_frames_at_timestamps(
|
|
130
|
+
raw_path,
|
|
131
|
+
dense_offsets,
|
|
132
|
+
max_frames=analyzer.max_dense_action_frames,
|
|
133
|
+
max_dimension=1080,
|
|
134
|
+
)
|
|
135
|
+
merged_frames = {round(timestamp, 3): data for timestamp, data in keyframes}
|
|
136
|
+
merged_frames.update({round(timestamp, 3): data for timestamp, data in targeted_frames})
|
|
137
|
+
return sorted(merged_frames.items())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async def _build_universal_user_blocks(
|
|
141
|
+
analyzer,
|
|
142
|
+
keyframes: list,
|
|
143
|
+
actual_start: float,
|
|
144
|
+
prompt_with_context: str,
|
|
145
|
+
raw_path: Path,
|
|
146
|
+
) -> tuple[list[dict[str, Any]], dict[str, Any] | None]:
|
|
147
|
+
"""Builds the multimodal user content blocks (text, frames, optional audio)."""
|
|
148
|
+
user_blocks: list[dict[str, Any]] = [{"type": "text", "text": prompt_with_context}]
|
|
149
|
+
|
|
150
|
+
for ts_sec, frame_bytes in keyframes:
|
|
151
|
+
b64_str = base64.b64encode(frame_bytes).decode("utf-8")
|
|
152
|
+
abs_time = actual_start + ts_sec
|
|
153
|
+
user_blocks.append(
|
|
154
|
+
{
|
|
155
|
+
"type": "text",
|
|
156
|
+
"text": f"--- Video Keyframe at {abs_time:.1f}s ---",
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
user_blocks.append(
|
|
160
|
+
{
|
|
161
|
+
"type": "image_url",
|
|
162
|
+
"image_url": {"url": f"data:image/jpeg;base64,{b64_str}"},
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
audio_block = None
|
|
167
|
+
try:
|
|
168
|
+
async with _va.TRANSCODE_SEMAPHORE:
|
|
169
|
+
audio_path = await _va.extract_audio_from_video(raw_path)
|
|
170
|
+
analyzer.local_files_to_cleanup.add(audio_path)
|
|
171
|
+
analyzer.local_dirs_to_cleanup.add(audio_path.parent)
|
|
172
|
+
audio_block = analyzer._audio_content_block(audio_path)
|
|
173
|
+
user_blocks.append(audio_block)
|
|
174
|
+
except Exception as audio_error:
|
|
175
|
+
logger.info(f"No usable audio track for universal video chunk: {audio_error}")
|
|
176
|
+
|
|
177
|
+
return user_blocks, audio_block
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def _invoke_universal_with_visual_retry(
|
|
181
|
+
analyzer,
|
|
182
|
+
messages: list[BaseMessage],
|
|
183
|
+
user_blocks: list[dict[str, Any]],
|
|
184
|
+
audio_block: dict[str, Any] | None,
|
|
185
|
+
force_fallback: bool,
|
|
186
|
+
):
|
|
187
|
+
"""Invokes the universal model, retrying visually when audio input is rejected."""
|
|
188
|
+
with TraceSpan(name="universal_sub_agent_call") as span:
|
|
189
|
+
try:
|
|
190
|
+
response = await analyzer._invoke_universal_model(
|
|
191
|
+
messages,
|
|
192
|
+
[_va.UNIVERSAL_SUBMIT_ANSWER_TOOL],
|
|
193
|
+
timeout=analyzer.model_call_timeout_seconds,
|
|
194
|
+
label="Universal video sub-agent",
|
|
195
|
+
force_fallback=force_fallback,
|
|
196
|
+
)
|
|
197
|
+
except Exception as multimodal_error:
|
|
198
|
+
failure = classify_video_failure(multimodal_error)
|
|
199
|
+
if audio_block is None or failure.category.value not in {
|
|
200
|
+
"bad_request",
|
|
201
|
+
"media_processing",
|
|
202
|
+
}:
|
|
203
|
+
raise
|
|
204
|
+
logger.warning(
|
|
205
|
+
"Configured video model rejected audio input; retrying this chunk visually"
|
|
206
|
+
)
|
|
207
|
+
visual_blocks = [block for block in user_blocks if block is not audio_block]
|
|
208
|
+
messages = [
|
|
209
|
+
SystemMessage(content=analyzer.sub_system_prompt),
|
|
210
|
+
HumanMessage(content=visual_blocks),
|
|
211
|
+
]
|
|
212
|
+
response = await analyzer._invoke_universal_model(
|
|
213
|
+
messages,
|
|
214
|
+
[_va.UNIVERSAL_SUBMIT_ANSWER_TOOL],
|
|
215
|
+
timeout=analyzer.model_call_timeout_seconds,
|
|
216
|
+
label="Universal visual-only sub-agent",
|
|
217
|
+
force_fallback=force_fallback,
|
|
218
|
+
)
|
|
219
|
+
span.result = f"Received response (tool_calls={len(getattr(response, 'tool_calls', []))})"
|
|
220
|
+
return response
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _extract_submit_payload(response) -> tuple[str, str, list]:
|
|
224
|
+
"""Extract a summary, analysis, and events from the model response.
|
|
225
|
+
|
|
226
|
+
Use the first submit_answer call if its summary is non-empty. Otherwise,
|
|
227
|
+
fall back to response text, using its first 200 characters as the summary
|
|
228
|
+
and dropping any events from the rejected call. Raise SubAgentAnswerExhausted
|
|
229
|
+
if neither source provides an answer.
|
|
230
|
+
"""
|
|
231
|
+
for tc in getattr(response, "tool_calls", None) or []:
|
|
232
|
+
if tc.get("name") != "submit_answer":
|
|
233
|
+
continue
|
|
234
|
+
args = tc.get("args") or {}
|
|
235
|
+
final_summary = str(args.get("summary", "")).strip().replace("\n", " ")
|
|
236
|
+
if not final_summary:
|
|
237
|
+
break
|
|
238
|
+
final_analysis = (
|
|
239
|
+
str(args.get("analysis", "")).strip().replace("\n", " ") or "No analysis provided."
|
|
240
|
+
)
|
|
241
|
+
return final_summary, final_analysis, list(args.get("timeline_events") or [])
|
|
242
|
+
|
|
243
|
+
raw_content = getattr(response, "content", None)
|
|
244
|
+
text_content = str(raw_content).strip() if raw_content else ""
|
|
245
|
+
if text_content:
|
|
246
|
+
return text_content[:200].replace("\n", " "), text_content, []
|
|
247
|
+
|
|
248
|
+
raise SubAgentAnswerExhausted(
|
|
249
|
+
1, "the model neither called submit_answer with a summary nor replied with text"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async def _persist_universal_events(
|
|
254
|
+
analyzer,
|
|
255
|
+
timeline_events: list,
|
|
256
|
+
start_time: float,
|
|
257
|
+
end_time: float | None,
|
|
258
|
+
actual_start: float,
|
|
259
|
+
raw_path: Path,
|
|
260
|
+
specific_query: str,
|
|
261
|
+
final_summary: str,
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Processes timeline events & saves screenshots for the blackboard ledger."""
|
|
264
|
+
for event in timeline_events:
|
|
265
|
+
if not isinstance(event, dict):
|
|
266
|
+
continue
|
|
267
|
+
try:
|
|
268
|
+
v_ts = float(event.get("verification_timestamp_secs", start_time))
|
|
269
|
+
conf = float(event.get("confidence_score", 0.8))
|
|
270
|
+
event_start = float(event.get("start_time", start_time))
|
|
271
|
+
event_end = float(
|
|
272
|
+
event.get(
|
|
273
|
+
"end_time",
|
|
274
|
+
end_time if end_time is not None else event_start,
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
except (TypeError, ValueError):
|
|
278
|
+
logger.warning(f"Ignoring malformed universal video event: {event}")
|
|
279
|
+
continue
|
|
280
|
+
interval_end = end_time if end_time is not None else max(event_end, v_ts)
|
|
281
|
+
if (
|
|
282
|
+
not 0.0 <= conf <= 1.0
|
|
283
|
+
or event_end < event_start
|
|
284
|
+
or event_start < start_time
|
|
285
|
+
or event_end > interval_end
|
|
286
|
+
or v_ts < start_time
|
|
287
|
+
or v_ts > interval_end
|
|
288
|
+
):
|
|
289
|
+
logger.warning(f"Ignoring out-of-range universal video event: {event}")
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
screenshot_path_str = None
|
|
293
|
+
try:
|
|
294
|
+
rel_offset = max(0.0, v_ts - actual_start)
|
|
295
|
+
out_dir = _va.get_temp_dir("video_analyzer")
|
|
296
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
297
|
+
|
|
298
|
+
existing_imgs = glob.glob(f"{out_dir}/img_*.jpg")
|
|
299
|
+
max_idx = -1
|
|
300
|
+
for img in existing_imgs:
|
|
301
|
+
m = re.search(r"img_(\d+)\.jpg$", img)
|
|
302
|
+
if m:
|
|
303
|
+
max_idx = max(max_idx, int(m.group(1)))
|
|
304
|
+
next_idx = max_idx + 1
|
|
305
|
+
shot_path = out_dir / f"img_{next_idx}.jpg"
|
|
306
|
+
|
|
307
|
+
ffmpeg_cmd = [
|
|
308
|
+
"ffmpeg",
|
|
309
|
+
"-ss",
|
|
310
|
+
str(rel_offset),
|
|
311
|
+
"-i",
|
|
312
|
+
str(raw_path),
|
|
313
|
+
"-vframes",
|
|
314
|
+
"1",
|
|
315
|
+
"-q:v",
|
|
316
|
+
"2",
|
|
317
|
+
"-y",
|
|
318
|
+
str(shot_path),
|
|
319
|
+
]
|
|
320
|
+
proc = await asyncio.create_subprocess_exec(
|
|
321
|
+
*ffmpeg_cmd,
|
|
322
|
+
stdout=asyncio.subprocess.PIPE,
|
|
323
|
+
stderr=asyncio.subprocess.PIPE,
|
|
324
|
+
)
|
|
325
|
+
await proc.communicate()
|
|
326
|
+
if shot_path.exists():
|
|
327
|
+
analyzer.local_files_to_cleanup.add(shot_path)
|
|
328
|
+
screenshot_path_str = str(shot_path)
|
|
329
|
+
except Exception as fe:
|
|
330
|
+
logger.warning(f"Failed extracting keyframe for event {v_ts}s: {fe}")
|
|
331
|
+
|
|
332
|
+
entry = {
|
|
333
|
+
"start": event_start,
|
|
334
|
+
"end": event_end,
|
|
335
|
+
"target": specific_query,
|
|
336
|
+
"summary": event.get("transcription", final_summary),
|
|
337
|
+
"confidence_score": conf,
|
|
338
|
+
}
|
|
339
|
+
if screenshot_path_str:
|
|
340
|
+
entry["screenshot"] = screenshot_path_str
|
|
341
|
+
analyzer._record_blackboard_entry(entry)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
async def exec_analyze_audio_universal(
|
|
345
|
+
analyzer,
|
|
346
|
+
audio_path: Path,
|
|
347
|
+
start_time: float,
|
|
348
|
+
end_time: float | None,
|
|
349
|
+
actual_start: float,
|
|
350
|
+
prompt_with_context: str,
|
|
351
|
+
specific_query: str,
|
|
352
|
+
force_fallback: bool = False,
|
|
353
|
+
) -> str:
|
|
354
|
+
"""Executes audio analysis via Universal LangChain ChatModel."""
|
|
355
|
+
messages = [
|
|
356
|
+
SystemMessage(content=analyzer.audio_system_prompt),
|
|
357
|
+
HumanMessage(
|
|
358
|
+
content=[
|
|
359
|
+
{"type": "text", "text": prompt_with_context},
|
|
360
|
+
analyzer._audio_content_block(audio_path),
|
|
361
|
+
]
|
|
362
|
+
),
|
|
363
|
+
]
|
|
364
|
+
response = await analyzer._invoke_universal_model(
|
|
365
|
+
messages,
|
|
366
|
+
[_va.UNIVERSAL_SUBMIT_ANSWER_TOOL],
|
|
367
|
+
timeout=analyzer.model_call_timeout_seconds,
|
|
368
|
+
label="Universal audio sub-agent",
|
|
369
|
+
force_fallback=force_fallback,
|
|
370
|
+
)
|
|
371
|
+
# Same resolution order as the video chunk (raises SubAgentAnswerExhausted
|
|
372
|
+
# when the model produced nothing usable); audio has no timeline events.
|
|
373
|
+
final_summary, final_analysis, _ = _extract_submit_payload(response)
|
|
374
|
+
|
|
375
|
+
end_val = end_time if end_time is not None else "unknown"
|
|
376
|
+
analyzer._record_blackboard_entry(
|
|
377
|
+
{
|
|
378
|
+
"start": start_time,
|
|
379
|
+
"end": end_val,
|
|
380
|
+
"target": specific_query,
|
|
381
|
+
"summary": final_summary,
|
|
382
|
+
"confidence_score": 0.8,
|
|
383
|
+
},
|
|
384
|
+
modality="audio",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
end_str = f"{end_time:.1f}s" if end_time is not None else "unknown"
|
|
388
|
+
return (
|
|
389
|
+
f"[from {start_time:.1f}s to {end_str}] Audio Summary: {final_summary}"
|
|
390
|
+
f" Analysis: {final_analysis}"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
async def run_universal(
|
|
395
|
+
analyzer,
|
|
396
|
+
time_description: str,
|
|
397
|
+
purpose: str,
|
|
398
|
+
system_prompt: str,
|
|
399
|
+
*,
|
|
400
|
+
force_fallback: bool = False,
|
|
401
|
+
) -> tuple[str, str]:
|
|
402
|
+
"""Runs VideoAnalyzer main reasoning loop using Universal LangChain BaseChatModel."""
|
|
403
|
+
messages: list[BaseMessage] = [
|
|
404
|
+
SystemMessage(content=system_prompt),
|
|
405
|
+
HumanMessage(
|
|
406
|
+
content=(
|
|
407
|
+
f"Time description: {time_description}\n"
|
|
408
|
+
f"Purpose: {purpose}\n"
|
|
409
|
+
f"Persistent video blackboard: {analyzer.blackboard_ledger}\n"
|
|
410
|
+
"Reuse sufficient prior evidence and do not request an identical "
|
|
411
|
+
"interval/query analysis again.\n"
|
|
412
|
+
"Please coordinate video metadata extraction and sub-agent analysis."
|
|
413
|
+
)
|
|
414
|
+
),
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
max_iterations = 6
|
|
418
|
+
iterations = 0
|
|
419
|
+
agent_outcome = ""
|
|
420
|
+
status = "success"
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
while iterations < max_iterations:
|
|
424
|
+
iterations += 1
|
|
425
|
+
response = await _va._invoke_with_retry(
|
|
426
|
+
lambda: analyzer._invoke_universal_model(
|
|
427
|
+
messages,
|
|
428
|
+
_va.UNIVERSAL_MAIN_TOOLS,
|
|
429
|
+
timeout=300,
|
|
430
|
+
label="Universal video coordinator",
|
|
431
|
+
force_fallback=force_fallback,
|
|
432
|
+
),
|
|
433
|
+
"Universal video coordinator",
|
|
434
|
+
)
|
|
435
|
+
messages.append(response)
|
|
436
|
+
|
|
437
|
+
if not getattr(response, "tool_calls", None):
|
|
438
|
+
agent_outcome = str(response.content)
|
|
439
|
+
break
|
|
440
|
+
|
|
441
|
+
for tc in response.tool_calls:
|
|
442
|
+
t_name = tc["name"]
|
|
443
|
+
t_args = tc.get("args", {})
|
|
444
|
+
t_id = tc.get("id", f"call_{iterations}")
|
|
445
|
+
logger.info(f"Executing universal tool '{t_name}' with args {t_args}")
|
|
446
|
+
|
|
447
|
+
res = await _execute_universal_tool(analyzer, t_name, t_args)
|
|
448
|
+
|
|
449
|
+
messages.append(ToolMessage(tool_call_id=t_id, name=t_name, content=str(res)))
|
|
450
|
+
|
|
451
|
+
if not agent_outcome:
|
|
452
|
+
agent_outcome = "Video analyzer completed all turns without error."
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
logger.error(f"Universal video analyzer failed: {e}")
|
|
456
|
+
agent_outcome = f"Video analysis failed: {e}"
|
|
457
|
+
status = "error"
|
|
458
|
+
|
|
459
|
+
analyzer._record_reliability_metrics()
|
|
460
|
+
return agent_outcome, status
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
async def _execute_universal_tool(analyzer, t_name: str, t_args: dict) -> str:
|
|
464
|
+
"""Dispatches one universal coordinator tool call, converting errors to text."""
|
|
465
|
+
try:
|
|
466
|
+
if t_name == "extract_segment_metadata":
|
|
467
|
+
res = await analyzer.exec_extract_segment_metadata(
|
|
468
|
+
t_args.get("start_time"), t_args.get("end_time")
|
|
469
|
+
)
|
|
470
|
+
elif t_name == "spawn_sub_agent":
|
|
471
|
+
res = await analyzer.exec_spawn_sub_agent(
|
|
472
|
+
t_args.get("start_time"),
|
|
473
|
+
t_args.get("end_time"),
|
|
474
|
+
t_args.get("specific_query"),
|
|
475
|
+
)
|
|
476
|
+
elif t_name == "analyze_audio_only":
|
|
477
|
+
res = await analyzer.exec_analyze_audio_only(
|
|
478
|
+
t_args.get("start_time"),
|
|
479
|
+
t_args.get("end_time"),
|
|
480
|
+
t_args.get("specific_query"),
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
res = f"Error: Tool '{t_name}' is not recognized."
|
|
484
|
+
except Exception as tool_err:
|
|
485
|
+
logger.error(f"Error executing tool '{t_name}': {tool_err}")
|
|
486
|
+
res = f"Error executing tool '{t_name}': {tool_err}"
|
|
487
|
+
return res
|