langchain-agentx-python 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_agentx/__init__.py +46 -0
- langchain_agentx/command/__init__.py +28 -0
- langchain_agentx/command/builtin/__init__.py +25 -0
- langchain_agentx/command/builtin/clear.py +33 -0
- langchain_agentx/command/builtin/compact.py +33 -0
- langchain_agentx/command/builtin/memory.py +37 -0
- langchain_agentx/command/builtin/reload_plugins.py +42 -0
- langchain_agentx/command/context.py +30 -0
- langchain_agentx/command/dispatcher.py +183 -0
- langchain_agentx/command/registry.py +110 -0
- langchain_agentx/command/result.py +25 -0
- langchain_agentx/command/types.py +41 -0
- langchain_agentx/config/__init__.py +14 -0
- langchain_agentx/loop/__init__.py +47 -0
- langchain_agentx/loop/config/__init__.py +20 -0
- langchain_agentx/loop/config/agent_config.py +66 -0
- langchain_agentx/loop/config/agent_loop_config.py +72 -0
- langchain_agentx/loop/config/model_context_resolver.py +105 -0
- langchain_agentx/loop/config/runtime_settings.py +50 -0
- langchain_agentx/loop/config/token_estimator.py +133 -0
- langchain_agentx/loop/context/__init__.py +66 -0
- langchain_agentx/loop/context/blocking_guard.py +97 -0
- langchain_agentx/loop/context/compaction_service.py +60 -0
- langchain_agentx/loop/context/message_utils.py +56 -0
- langchain_agentx/loop/context/pipeline.py +127 -0
- langchain_agentx/loop/context/settings.py +103 -0
- langchain_agentx/loop/context/stages/__init__.py +29 -0
- langchain_agentx/loop/context/stages/autocompact.py +140 -0
- langchain_agentx/loop/context/stages/base.py +32 -0
- langchain_agentx/loop/context/stages/collapse.py +76 -0
- langchain_agentx/loop/context/stages/microcompact.py +76 -0
- langchain_agentx/loop/context/stages/noop.py +33 -0
- langchain_agentx/loop/context/stages/snip.py +71 -0
- langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
- langchain_agentx/loop/context/types.py +79 -0
- langchain_agentx/loop/exit/__init__.py +1 -0
- langchain_agentx/loop/exit/exit_logic.py +320 -0
- langchain_agentx/loop/exit/reason_codes.py +39 -0
- langchain_agentx/loop/graph/__init__.py +5 -0
- langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
- langchain_agentx/loop/graph/factory.py +1409 -0
- langchain_agentx/loop/graph/graph_edges.py +820 -0
- langchain_agentx/loop/hook/__init__.py +48 -0
- langchain_agentx/loop/hook/async_hook_runner.py +62 -0
- langchain_agentx/loop/hook/config.py +280 -0
- langchain_agentx/loop/hook/engine.py +321 -0
- langchain_agentx/loop/hook/executors/__init__.py +9 -0
- langchain_agentx/loop/hook/executors/agent.py +107 -0
- langchain_agentx/loop/hook/executors/command.py +230 -0
- langchain_agentx/loop/hook/executors/http.py +114 -0
- langchain_agentx/loop/hook/executors/prompt.py +92 -0
- langchain_agentx/loop/hook/graph_wiring.py +134 -0
- langchain_agentx/loop/hook/registry.py +262 -0
- langchain_agentx/loop/hook/trust.py +43 -0
- langchain_agentx/loop/hook/types.py +110 -0
- langchain_agentx/loop/injection/__init__.py +13 -0
- langchain_agentx/loop/injection/dedup.py +74 -0
- langchain_agentx/loop/loop_abort.py +36 -0
- langchain_agentx/loop/model/__init__.py +1 -0
- langchain_agentx/loop/model/model_node.py +648 -0
- langchain_agentx/loop/model/model_nodes.py +661 -0
- langchain_agentx/loop/model/orphan_tool_results.py +38 -0
- langchain_agentx/loop/model/retrier.py +307 -0
- langchain_agentx/loop/model/retry_bridge.py +58 -0
- langchain_agentx/loop/model/retry_events.py +35 -0
- langchain_agentx/loop/model/retry_policy.py +56 -0
- langchain_agentx/loop/model/schema_and_format.py +153 -0
- langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
- langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
- langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
- langchain_agentx/loop/prompt/__init__.py +95 -0
- langchain_agentx/loop/prompt/builder.py +61 -0
- langchain_agentx/loop/prompt/builtin.py +218 -0
- langchain_agentx/loop/prompt/compact.py +408 -0
- langchain_agentx/loop/prompt/sections.py +120 -0
- langchain_agentx/loop/runtime/__init__.py +19 -0
- langchain_agentx/loop/runtime/context.py +34 -0
- langchain_agentx/loop/runtime/context_factory.py +107 -0
- langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
- langchain_agentx/loop/subagent/__init__.py +53 -0
- langchain_agentx/loop/subagent/async_runner.py +215 -0
- langchain_agentx/loop/subagent/context.py +209 -0
- langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
- langchain_agentx/loop/subagent/graph.py +72 -0
- langchain_agentx/loop/subagent/orchestrator.py +391 -0
- langchain_agentx/loop/subagent/progress.py +30 -0
- langchain_agentx/loop/subagent/prompt.py +52 -0
- langchain_agentx/loop/subagent/runner.py +504 -0
- langchain_agentx/loop/subagent/transcript.py +172 -0
- langchain_agentx/memory/__init__.py +2 -0
- langchain_agentx/memory/instruction/__init__.py +12 -0
- langchain_agentx/memory/instruction/loader.py +325 -0
- langchain_agentx/memory/instruction/resolver.py +24 -0
- langchain_agentx/memory/instruction/runtime.py +83 -0
- langchain_agentx/memory/instruction/sections.py +83 -0
- langchain_agentx/memory/instruction/types.py +59 -0
- langchain_agentx/memory/memdir/__init__.py +77 -0
- langchain_agentx/memory/memdir/age.py +36 -0
- langchain_agentx/memory/memdir/agent_memory.py +380 -0
- langchain_agentx/memory/memdir/extractor.py +309 -0
- langchain_agentx/memory/memdir/loader.py +187 -0
- langchain_agentx/memory/memdir/paths.py +63 -0
- langchain_agentx/memory/memdir/recall.py +45 -0
- langchain_agentx/memory/memdir/runtime.py +43 -0
- langchain_agentx/memory/memdir/scan.py +135 -0
- langchain_agentx/memory/memdir/types.py +104 -0
- langchain_agentx/memory/session/__init__.py +76 -0
- langchain_agentx/memory/session/compact_bridge.py +208 -0
- langchain_agentx/memory/session/prompts.py +172 -0
- langchain_agentx/memory/session/session_memory.py +282 -0
- langchain_agentx/observability/__init__.py +67 -0
- langchain_agentx/observability/evaluation/__init__.py +17 -0
- langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
- langchain_agentx/observability/evaluation/checkers/base.py +34 -0
- langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
- langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
- langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
- langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
- langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
- langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
- langchain_agentx/observability/evaluation/service.py +102 -0
- langchain_agentx/observability/evaluation/state.py +32 -0
- langchain_agentx/observability/evaluation/store.py +258 -0
- langchain_agentx/observability/events/__init__.py +15 -0
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
- langchain_agentx/observability/logging/__init__.py +15 -0
- langchain_agentx/observability/logging/debug_burst.py +95 -0
- langchain_agentx/observability/logging/logging_config.py +178 -0
- langchain_agentx/observability/logging/logging_contract.py +65 -0
- langchain_agentx/observability/replay/__init__.py +35 -0
- langchain_agentx/observability/replay/cli.py +91 -0
- langchain_agentx/observability/replay/service.py +83 -0
- langchain_agentx/observability/replay/store.py +278 -0
- langchain_agentx/observability/replay/ui.py +47 -0
- langchain_agentx/observability/trace/__init__.py +25 -0
- langchain_agentx/observability/trace/collector.py +560 -0
- langchain_agentx/observability/trace/event_emitter.py +183 -0
- langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
- langchain_agentx/observability/trace/models.py +144 -0
- langchain_agentx/observability/trace/sqlite_store.py +873 -0
- langchain_agentx/observability/trace/trace_callback.py +295 -0
- langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
- langchain_agentx/plugin/__init__.py +26 -0
- langchain_agentx/plugin/builtin.py +53 -0
- langchain_agentx/plugin/config.py +113 -0
- langchain_agentx/plugin/loader.py +386 -0
- langchain_agentx/plugin/manifest.py +154 -0
- langchain_agentx/plugin/registries.py +211 -0
- langchain_agentx/plugin/types.py +142 -0
- langchain_agentx/provider/__init__.py +27 -0
- langchain_agentx/provider/anthropic.py +121 -0
- langchain_agentx/provider/compatible_chat_openai.py +86 -0
- langchain_agentx/provider/env.py +45 -0
- langchain_agentx/provider/model_profile.py +156 -0
- langchain_agentx/provider/openai.py +89 -0
- langchain_agentx/session/__init__.py +17 -0
- langchain_agentx/session/agent_session.py +320 -0
- langchain_agentx/session/conversation_factory.py +87 -0
- langchain_agentx/session/conversation_recovery.py +156 -0
- langchain_agentx/session/conversation_session.py +198 -0
- langchain_agentx/session/factory.py +143 -0
- langchain_agentx/session/protocol.py +25 -0
- langchain_agentx/task_runtime/__init__.py +113 -0
- langchain_agentx/task_runtime/core/__init__.py +51 -0
- langchain_agentx/task_runtime/core/ids.py +33 -0
- langchain_agentx/task_runtime/core/interfaces.py +115 -0
- langchain_agentx/task_runtime/core/notification_priority.py +19 -0
- langchain_agentx/task_runtime/core/types.py +136 -0
- langchain_agentx/task_runtime/integrations/__init__.py +33 -0
- langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
- langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
- langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
- langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
- langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
- langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
- langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
- langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
- langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
- langchain_agentx/task_runtime/output/__init__.py +5 -0
- langchain_agentx/task_runtime/output/sink.py +64 -0
- langchain_agentx/task_runtime/policy/__init__.py +11 -0
- langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
- langchain_agentx/task_runtime/queue/__init__.py +5 -0
- langchain_agentx/task_runtime/queue/in_memory.py +55 -0
- langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
- langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
- langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
- langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
- langchain_agentx/task_runtime/store/__init__.py +6 -0
- langchain_agentx/task_runtime/store/in_memory.py +81 -0
- langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
- langchain_agentx/task_runtime/tasks/__init__.py +76 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
- langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
- langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
- langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
- langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
- langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
- langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
- langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
- langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
- langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
- langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
- langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
- langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
- langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
- langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
- langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
- langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
- langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
- langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
- langchain_agentx/tool_runtime/__init__.py +90 -0
- langchain_agentx/tool_runtime/adapter.py +365 -0
- langchain_agentx/tool_runtime/base.py +319 -0
- langchain_agentx/tool_runtime/errors.py +190 -0
- langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
- langchain_agentx/tool_runtime/loader.py +195 -0
- langchain_agentx/tool_runtime/models.py +260 -0
- langchain_agentx/tool_runtime/permission_context.py +78 -0
- langchain_agentx/tool_runtime/pipeline.py +621 -0
- langchain_agentx/tool_runtime/policy.py +447 -0
- langchain_agentx/tool_runtime/registry.py +81 -0
- langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
- langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
- langchain_agentx/tool_runtime/resolvers/background.py +32 -0
- langchain_agentx/tool_runtime/resolvers/base.py +20 -0
- langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
- langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
- langchain_agentx/tool_runtime/session_store.py +132 -0
- langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
- langchain_agentx/tool_runtime/state_bridge.py +164 -0
- langchain_agentx/tools/__init__.py +26 -0
- langchain_agentx/tools/agent/__init__.py +9 -0
- langchain_agentx/tools/agent/backend.py +53 -0
- langchain_agentx/tools/agent/built_in/__init__.py +19 -0
- langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
- langchain_agentx/tools/agent/built_in/explore.py +80 -0
- langchain_agentx/tools/agent/built_in/general.py +57 -0
- langchain_agentx/tools/agent/built_in/plan.py +89 -0
- langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
- langchain_agentx/tools/agent/built_in/verification.py +120 -0
- langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
- langchain_agentx/tools/agent/cwd_resolution.py +119 -0
- langchain_agentx/tools/agent/limits.py +26 -0
- langchain_agentx/tools/agent/loader.py +270 -0
- langchain_agentx/tools/agent/models.py +85 -0
- langchain_agentx/tools/agent/prompt.py +120 -0
- langchain_agentx/tools/agent/registry/__init__.py +18 -0
- langchain_agentx/tools/agent/registry/config.py +29 -0
- langchain_agentx/tools/agent/registry/registry.py +47 -0
- langchain_agentx/tools/agent/scope.py +137 -0
- langchain_agentx/tools/agent/tool.py +256 -0
- langchain_agentx/tools/bash/__init__.py +9 -0
- langchain_agentx/tools/bash/ast_security.py +571 -0
- langchain_agentx/tools/bash/backend.py +1447 -0
- langchain_agentx/tools/bash/bash_hardening.py +734 -0
- langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
- langchain_agentx/tools/bash/cwd_reporter.py +95 -0
- langchain_agentx/tools/bash/limits.py +71 -0
- langchain_agentx/tools/bash/mode_validation.py +282 -0
- langchain_agentx/tools/bash/models.py +131 -0
- langchain_agentx/tools/bash/observability.py +148 -0
- langchain_agentx/tools/bash/output_utils.py +200 -0
- langchain_agentx/tools/bash/path_security.py +2429 -0
- langchain_agentx/tools/bash/prompt.py +68 -0
- langchain_agentx/tools/bash/read_only_validation.py +589 -0
- langchain_agentx/tools/bash/result_presenter.py +324 -0
- langchain_agentx/tools/bash/sandbox_decision.py +133 -0
- langchain_agentx/tools/bash/security.py +311 -0
- langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
- langchain_agentx/tools/bash/sed_validation.py +163 -0
- langchain_agentx/tools/bash/semantics.py +111 -0
- langchain_agentx/tools/bash/session_manager.py +205 -0
- langchain_agentx/tools/bash/session_runtime.py +290 -0
- langchain_agentx/tools/bash/shell_locator.py +191 -0
- langchain_agentx/tools/bash/task_runtime.py +91 -0
- langchain_agentx/tools/bash/tool.py +939 -0
- langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
- langchain_agentx/tools/glob/__init__.py +9 -0
- langchain_agentx/tools/glob/models.py +57 -0
- langchain_agentx/tools/glob/pagination.py +30 -0
- langchain_agentx/tools/glob/prompt.py +24 -0
- langchain_agentx/tools/glob/rg_list_backend.py +139 -0
- langchain_agentx/tools/glob/rg_pattern.py +44 -0
- langchain_agentx/tools/glob/tool.py +327 -0
- langchain_agentx/tools/grep/__init__.py +7 -0
- langchain_agentx/tools/grep/backend.py +375 -0
- langchain_agentx/tools/grep/models.py +127 -0
- langchain_agentx/tools/grep/prompt.py +30 -0
- langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
- langchain_agentx/tools/grep/tool.py +475 -0
- langchain_agentx/tools/read/__init__.py +9 -0
- langchain_agentx/tools/read/backend.py +415 -0
- langchain_agentx/tools/read/limits.py +67 -0
- langchain_agentx/tools/read/models.py +156 -0
- langchain_agentx/tools/read/prompt.py +73 -0
- langchain_agentx/tools/read/tool.py +494 -0
- langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
- langchain_agentx/tools/skill/__init__.py +4 -0
- langchain_agentx/tools/skill/argument_substitution.py +80 -0
- langchain_agentx/tools/skill/loader.py +196 -0
- langchain_agentx/tools/skill/models.py +88 -0
- langchain_agentx/tools/skill/policy.py +80 -0
- langchain_agentx/tools/skill/prompt.py +35 -0
- langchain_agentx/tools/skill/tool.py +222 -0
- langchain_agentx/utils/__init__.py +0 -0
- langchain_agentx/utils/cwd.py +124 -0
- langchain_agentx/utils/host_platform.py +112 -0
- langchain_agentx/utils/path_hierarchy.py +48 -0
- langchain_agentx/utils/path_user_input.py +66 -0
- langchain_agentx/utils/rg_executable.py +18 -0
- langchain_agentx/utils/subprocess_text.py +101 -0
- langchain_agentx/utils/temp_paths.py +77 -0
- langchain_agentx/utils/unc_path.py +25 -0
- langchain_agentx/utils/win_reserved_paths.py +51 -0
- langchain_agentx/workflow/__init__.py +7 -0
- langchain_agentx/workflow/base.py +97 -0
- langchain_agentx/workflow/batch.py +55 -0
- langchain_agentx/workflow/dag.py +54 -0
- langchain_agentx/workspace/__init__.py +13 -0
- langchain_agentx/workspace/config.py +140 -0
- langchain_agentx/workspace/path_key_normalizer.py +30 -0
- langchain_agentx/workspace/resolver.py +74 -0
- langchain_agentx/workspace/validators.py +41 -0
- langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
- langchain_agentx_python-0.1.dist-info/METADATA +513 -0
- langchain_agentx_python-0.1.dist-info/RECORD +354 -0
- langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
- langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""provider/model_profile.py — 模型能力参数配置对象与注册表。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
- ModelProfile:持有单个模型的能力参数(context_window / max_output_tokens / 默认采样参数等)
|
|
5
|
+
- ModelProfileRegistry:从「内置 + 外部」两层 yaml 加载,并按 model_id_prefix 做最长前缀匹配
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
- provider/openai.py、provider/anthropic.py:读取默认采样参数与输出上限(P1 接入)
|
|
9
|
+
- loop/config/model_context_resolver.py:解析 context_window(P2 接入)
|
|
10
|
+
|
|
11
|
+
当前裁剪范围:
|
|
12
|
+
- 仅负责参数加载与匹配;不负责 loop 业务判断、不负责 env/.env 加载、不做运行时热重载。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, ClassVar
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _builtin_profiles_path() -> Path:
|
|
26
|
+
# langchain_agentx/provider/model_profile.py -> langchain_agentx/config/model_profiles.yaml
|
|
27
|
+
return Path(__file__).resolve().parents[1] / "config" / "model_profiles.yaml"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class ModelProfile:
|
|
32
|
+
"""单个模型的能力参数(纯数据持有,无业务逻辑)。"""
|
|
33
|
+
|
|
34
|
+
model_id_prefix: str
|
|
35
|
+
context_window: int
|
|
36
|
+
max_output_tokens: int
|
|
37
|
+
provider: str # "claude" | "openai"
|
|
38
|
+
supports_cache: bool = False
|
|
39
|
+
default_temperature: float = 0.7
|
|
40
|
+
default_top_p: float | None = None
|
|
41
|
+
|
|
42
|
+
PROVIDERS: ClassVar[frozenset[str]] = frozenset({"claude", "openai"})
|
|
43
|
+
|
|
44
|
+
def __post_init__(self) -> None:
|
|
45
|
+
if self.provider not in self.PROVIDERS:
|
|
46
|
+
msg = f"invalid provider={self.provider!r}, must be one of {sorted(self.PROVIDERS)}"
|
|
47
|
+
raise ValueError(msg)
|
|
48
|
+
if self.context_window <= 0 or self.max_output_tokens <= 0:
|
|
49
|
+
raise ValueError("context_window/max_output_tokens must be positive")
|
|
50
|
+
if not self.model_id_prefix or not str(self.model_id_prefix).strip():
|
|
51
|
+
raise ValueError("model_id_prefix must be non-empty")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ModelProfileRegistry:
|
|
56
|
+
"""两层 yaml 加载 + model_id_prefix 最长前缀优先匹配。"""
|
|
57
|
+
|
|
58
|
+
profiles: dict[str, ModelProfile] = field(default_factory=dict)
|
|
59
|
+
defaults: ModelProfile = field(
|
|
60
|
+
default_factory=lambda: ModelProfile(
|
|
61
|
+
model_id_prefix="__default__",
|
|
62
|
+
context_window=32_000,
|
|
63
|
+
max_output_tokens=8_192,
|
|
64
|
+
provider="openai",
|
|
65
|
+
supports_cache=False,
|
|
66
|
+
default_temperature=0.7,
|
|
67
|
+
default_top_p=0.5,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_files(cls, *, agent_home_dir: Path | None = None) -> "ModelProfileRegistry":
|
|
73
|
+
"""从内置配置与可选外部配置构造一个独立 registry。"""
|
|
74
|
+
registry = cls()
|
|
75
|
+
registry.load(agent_home_dir=agent_home_dir)
|
|
76
|
+
return registry
|
|
77
|
+
|
|
78
|
+
def load(self, *, agent_home_dir: Path | None = None) -> None:
|
|
79
|
+
"""加载内置 yaml,再用外部 yaml 覆盖。"""
|
|
80
|
+
try:
|
|
81
|
+
import yaml # type: ignore[import-not-found]
|
|
82
|
+
except Exception:
|
|
83
|
+
# P0:缺依赖时回退 defaults;P1/P2 的接线会依赖此处输出可观测 warning
|
|
84
|
+
logger.warning("pyyaml 未安装,跳过 model_profiles.yaml 加载,使用 defaults 兜底")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
builtin_path = _builtin_profiles_path()
|
|
88
|
+
builtin_profiles, builtin_defaults = self._parse_yaml(yaml, builtin_path)
|
|
89
|
+
|
|
90
|
+
external_profiles: dict[str, ModelProfile] = {}
|
|
91
|
+
external_defaults: ModelProfile | None = None
|
|
92
|
+
if agent_home_dir is not None:
|
|
93
|
+
ext_path = agent_home_dir / "provider" / "model_profiles.yaml"
|
|
94
|
+
if ext_path.exists():
|
|
95
|
+
external_profiles, external_defaults = self._parse_yaml(yaml, ext_path)
|
|
96
|
+
logger.debug("已加载外部模型配置: %s (%d 条)", ext_path, len(external_profiles))
|
|
97
|
+
|
|
98
|
+
merged = {**builtin_profiles, **external_profiles}
|
|
99
|
+
self.defaults = external_defaults or builtin_defaults or self.defaults
|
|
100
|
+
self.profiles = merged
|
|
101
|
+
|
|
102
|
+
def get(self, model_id: str) -> ModelProfile:
|
|
103
|
+
"""按 model_id_prefix 做最长前缀优先匹配;未命中返回 defaults。"""
|
|
104
|
+
if not model_id:
|
|
105
|
+
return self.defaults
|
|
106
|
+
matched: ModelProfile | None = None
|
|
107
|
+
for p in self.profiles.values():
|
|
108
|
+
if model_id.startswith(p.model_id_prefix):
|
|
109
|
+
if matched is None or len(p.model_id_prefix) > len(matched.model_id_prefix):
|
|
110
|
+
matched = p
|
|
111
|
+
return matched or self.defaults
|
|
112
|
+
|
|
113
|
+
def _parse_yaml(
|
|
114
|
+
self, yaml_mod: Any, path: Path
|
|
115
|
+
) -> tuple[dict[str, ModelProfile], ModelProfile | None]:
|
|
116
|
+
try:
|
|
117
|
+
raw = yaml_mod.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning("model_profiles.yaml 解析失败 %s: %s", path, e)
|
|
120
|
+
return {}, None
|
|
121
|
+
|
|
122
|
+
defaults_profile: ModelProfile | None = None
|
|
123
|
+
if isinstance(raw, dict) and raw.get("defaults"):
|
|
124
|
+
defaults_profile = self._parse_profile({"model_id_prefix": "__default__", **raw["defaults"]})
|
|
125
|
+
|
|
126
|
+
result: dict[str, ModelProfile] = {}
|
|
127
|
+
for item in (raw.get("profiles") or []) if isinstance(raw, dict) else []:
|
|
128
|
+
if not isinstance(item, dict):
|
|
129
|
+
continue
|
|
130
|
+
try:
|
|
131
|
+
p = self._parse_profile(item)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.warning("跳过无效 profile 条目 %s: %s", item, e)
|
|
134
|
+
continue
|
|
135
|
+
result[p.model_id_prefix] = p
|
|
136
|
+
|
|
137
|
+
return result, defaults_profile
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def _parse_profile(item: dict[str, Any]) -> ModelProfile:
|
|
141
|
+
return ModelProfile(
|
|
142
|
+
model_id_prefix=str(item["model_id_prefix"]),
|
|
143
|
+
context_window=int(item["context_window"]),
|
|
144
|
+
max_output_tokens=int(item["max_output_tokens"]),
|
|
145
|
+
provider=str(item.get("provider", "openai")),
|
|
146
|
+
supports_cache=bool(item.get("supports_cache", False)),
|
|
147
|
+
default_temperature=float(item.get("default_temperature", 0.7)),
|
|
148
|
+
default_top_p=item.get("default_top_p", None),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
__all__ = [
|
|
153
|
+
"ModelProfile",
|
|
154
|
+
"ModelProfileRegistry",
|
|
155
|
+
]
|
|
156
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""provider/openai.py — OpenAI 兼容 ChatModel 工厂(可复用组合件)。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
- 从环境变量读取 OpenAI 兼容服务配置(key/base_url/model)。
|
|
5
|
+
- 组装并返回 CompatibleChatOpenAI。
|
|
6
|
+
|
|
7
|
+
约束:
|
|
8
|
+
- 不加载 .env;由调用方(例如 examples)负责把 .env 注入到环境变量。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
17
|
+
|
|
18
|
+
from .compatible_chat_openai import CompatibleChatOpenAI
|
|
19
|
+
from .env import get_env_first
|
|
20
|
+
from .model_profile import ModelProfileRegistry
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _sanitize_proxy_env_for_openai_compat() -> None:
|
|
24
|
+
"""部分 http 客户端不接受 socks:// 代理,移除异常 all_proxy。"""
|
|
25
|
+
for key in ("all_proxy", "ALL_PROXY"):
|
|
26
|
+
value = os.environ.get(key)
|
|
27
|
+
if not value:
|
|
28
|
+
continue
|
|
29
|
+
if value.strip().lower().startswith("socks://"):
|
|
30
|
+
os.environ.pop(key, None)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_chat_openai(
|
|
34
|
+
*,
|
|
35
|
+
model: str | None = None,
|
|
36
|
+
api_key: str | None = None,
|
|
37
|
+
base_url: str | None = None,
|
|
38
|
+
temperature: float | None = None,
|
|
39
|
+
max_tokens: int | None = None,
|
|
40
|
+
top_p: float | None = None,
|
|
41
|
+
streaming: bool = True,
|
|
42
|
+
model_profile_registry: ModelProfileRegistry | None = None,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> BaseChatModel:
|
|
45
|
+
_sanitize_proxy_env_for_openai_compat()
|
|
46
|
+
|
|
47
|
+
resolved_api_key = api_key or get_env_first(
|
|
48
|
+
"LANGCHAIN_AGENTX_OPENAI_API_KEY",
|
|
49
|
+
"OPENAI_API_KEY",
|
|
50
|
+
)
|
|
51
|
+
resolved_base_url = base_url or get_env_first(
|
|
52
|
+
"LANGCHAIN_AGENTX_OPENAI_BASE_URL",
|
|
53
|
+
"OPENAI_BASE_URL",
|
|
54
|
+
)
|
|
55
|
+
resolved_model = model or get_env_first("LANGCHAIN_AGENTX_MODEL") or "gpt-4o-mini"
|
|
56
|
+
profile = (
|
|
57
|
+
model_profile_registry.get(resolved_model)
|
|
58
|
+
if model_profile_registry is not None
|
|
59
|
+
else ModelProfileRegistry().defaults
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if not resolved_api_key or not resolved_base_url:
|
|
63
|
+
raise RuntimeError(
|
|
64
|
+
"缺少 OpenAI 兼容端点配置:请设置 OPENAI_API_KEY(或 LANGCHAIN_AGENTX_OPENAI_API_KEY)"
|
|
65
|
+
" 以及 OPENAI_BASE_URL(或 LANGCHAIN_AGENTX_OPENAI_BASE_URL)。"
|
|
66
|
+
" 默认模型名可通过 LANGCHAIN_AGENTX_MODEL 或 model 参数指定。"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
resolved_temperature = profile.default_temperature if temperature is None else float(temperature)
|
|
70
|
+
resolved_max_tokens = profile.max_output_tokens if max_tokens is None else int(max_tokens)
|
|
71
|
+
resolved_top_p = profile.default_top_p if top_p is None else float(top_p)
|
|
72
|
+
|
|
73
|
+
init_kwargs: dict[str, Any] = {
|
|
74
|
+
"model": resolved_model,
|
|
75
|
+
"api_key": resolved_api_key,
|
|
76
|
+
"base_url": resolved_base_url.rstrip("/"),
|
|
77
|
+
"temperature": resolved_temperature,
|
|
78
|
+
"max_tokens": resolved_max_tokens,
|
|
79
|
+
"streaming": streaming,
|
|
80
|
+
**kwargs,
|
|
81
|
+
}
|
|
82
|
+
if resolved_top_p is not None:
|
|
83
|
+
init_kwargs["top_p"] = resolved_top_p
|
|
84
|
+
|
|
85
|
+
return CompatibleChatOpenAI(**init_kwargs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["get_chat_openai"]
|
|
89
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Session 容器导出。"""
|
|
2
|
+
|
|
3
|
+
from .agent_session import AgentSession
|
|
4
|
+
from .conversation_recovery import load_session_messages
|
|
5
|
+
from .conversation_factory import create_conversation_session
|
|
6
|
+
from .conversation_session import ConversationSession
|
|
7
|
+
from .factory import create_agent_session
|
|
8
|
+
from .protocol import LoopContainer
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AgentSession",
|
|
12
|
+
"ConversationSession",
|
|
13
|
+
"LoopContainer",
|
|
14
|
+
"create_agent_session",
|
|
15
|
+
"create_conversation_session",
|
|
16
|
+
"load_session_messages",
|
|
17
|
+
]
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
session/agent_session.py — 进程级 Session 生命周期容器。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
负责触发进程级 SESSION_START/SESSION_END,封装多次 loop invoke,并维护跨 loop 状态。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
作为 Agent Loop 外层容器供 CLI/应用复用,对应 A01/A03 约定的 LoopContainer 协议。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
当前实现覆盖最小生命周期与 active_files 累积;memory manager 通过注入方式可选接入。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import inspect
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
from uuid import uuid4
|
|
20
|
+
from typing import Any, AsyncIterator, Callable
|
|
21
|
+
|
|
22
|
+
from langchain_core.messages import HumanMessage
|
|
23
|
+
from langgraph.graph.state import CompiledStateGraph
|
|
24
|
+
|
|
25
|
+
from ..command import CommandContext, CommandDispatcher, CommandRegistry
|
|
26
|
+
from ..command.builtin import (
|
|
27
|
+
make_clear_command,
|
|
28
|
+
make_compact_command,
|
|
29
|
+
make_memory_command,
|
|
30
|
+
make_reload_plugins_command,
|
|
31
|
+
)
|
|
32
|
+
from ..loop.context.compaction_service import default_loop_context_compaction
|
|
33
|
+
from ..loop.context.message_utils import total_estimated_tokens_for_messages
|
|
34
|
+
from ..loop.hook.types import HookContext, HookEvent
|
|
35
|
+
from ..memory.memdir.agent_memory import drain_pending_agent_memory_extraction
|
|
36
|
+
from ..memory.memdir.extractor import drain_pending_extraction
|
|
37
|
+
from ..plugin.loader import PluginLoader
|
|
38
|
+
from ..plugin.registries import AgentRegistry, CommandRegistry as PluginCommandRegistry, SkillRegistry
|
|
39
|
+
from ..plugin.types import PluginLoadResult
|
|
40
|
+
from ..loop.subagent.transcript import SubagentTranscriptStore
|
|
41
|
+
from ..workspace.config import AgentWorkspaceConfig
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AgentSession:
|
|
47
|
+
"""进程级 session 容器。"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
graph: CompiledStateGraph | None,
|
|
52
|
+
session_id: str,
|
|
53
|
+
workspace_cfg: AgentWorkspaceConfig,
|
|
54
|
+
hook_engine: Any,
|
|
55
|
+
*,
|
|
56
|
+
graph_factory: Callable[..., CompiledStateGraph] | None = None,
|
|
57
|
+
plugin_loader: PluginLoader | None = None,
|
|
58
|
+
transcript_writer: SubagentTranscriptStore | None = None,
|
|
59
|
+
container_type: str = "interactive",
|
|
60
|
+
enable_trace: bool = True,
|
|
61
|
+
) -> None:
|
|
62
|
+
self._graph = graph
|
|
63
|
+
self._graph_factory = graph_factory
|
|
64
|
+
self._session_id = session_id
|
|
65
|
+
self._workspace_cfg = workspace_cfg
|
|
66
|
+
self._hook_engine = hook_engine
|
|
67
|
+
self._active_files: list[str] = []
|
|
68
|
+
self._messages: list[Any] = []
|
|
69
|
+
self._container_type = container_type
|
|
70
|
+
self._enable_trace = enable_trace
|
|
71
|
+
self._transcript_writer = transcript_writer
|
|
72
|
+
self._last_transcript_uuid: str | None = None
|
|
73
|
+
self._session_memory_manager: Any | None = None
|
|
74
|
+
self._skill_registry = SkillRegistry()
|
|
75
|
+
self._command_registry = PluginCommandRegistry()
|
|
76
|
+
self._agent_registry = AgentRegistry()
|
|
77
|
+
self._runtime_command_registry = CommandRegistry()
|
|
78
|
+
self._register_builtin_commands()
|
|
79
|
+
self._dispatcher = CommandDispatcher(
|
|
80
|
+
self._runtime_command_registry,
|
|
81
|
+
plugin_command_registry=self._command_registry,
|
|
82
|
+
)
|
|
83
|
+
self._plugin_loader: PluginLoader | None
|
|
84
|
+
if not workspace_cfg.plugins_enabled:
|
|
85
|
+
self._plugin_loader = None
|
|
86
|
+
else:
|
|
87
|
+
self._plugin_loader = plugin_loader or PluginLoader(
|
|
88
|
+
workspace_config=workspace_cfg,
|
|
89
|
+
hook_engine=hook_engine,
|
|
90
|
+
skill_registry=self._skill_registry,
|
|
91
|
+
command_registry=self._command_registry,
|
|
92
|
+
agent_registry=self._agent_registry,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def __aenter__(self) -> "AgentSession":
|
|
96
|
+
if self._graph is None:
|
|
97
|
+
self._graph = self._build_graph()
|
|
98
|
+
if self._plugin_loader is not None:
|
|
99
|
+
load_result = await self._plugin_loader.load_all(container_type="interactive")
|
|
100
|
+
load_result.log_errors(logger)
|
|
101
|
+
await self._execute_hook(HookEvent.SESSION_START)
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
async def __aexit__(self, *_) -> None:
|
|
105
|
+
if self._session_memory_manager is not None:
|
|
106
|
+
await self._session_memory_manager.drain(timeout=30.0)
|
|
107
|
+
await drain_pending_extraction(timeout_ms=60_000)
|
|
108
|
+
await drain_pending_agent_memory_extraction(timeout_ms=60_000)
|
|
109
|
+
await self._execute_hook(HookEvent.SESSION_END)
|
|
110
|
+
if self._plugin_loader is not None:
|
|
111
|
+
await self._plugin_loader.unload_all()
|
|
112
|
+
|
|
113
|
+
async def run_loop(self, user_input: str) -> dict[str, Any]:
|
|
114
|
+
if self._graph is None:
|
|
115
|
+
raise RuntimeError("AgentSession graph is not initialized")
|
|
116
|
+
appended_message = HumanMessage(content=user_input)
|
|
117
|
+
self._messages.append(appended_message)
|
|
118
|
+
baseline_count = len(self._messages)
|
|
119
|
+
try:
|
|
120
|
+
result = await self._graph.ainvoke(
|
|
121
|
+
{
|
|
122
|
+
"messages": list(self._messages),
|
|
123
|
+
"_session_id": self._session_id,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
except Exception:
|
|
127
|
+
# 回滚本轮 append,避免异常路径重复累积 user 消息。
|
|
128
|
+
if self._messages and self._messages[-1] is appended_message:
|
|
129
|
+
self._messages.pop()
|
|
130
|
+
raise
|
|
131
|
+
if isinstance(result, dict) and isinstance(result.get("messages"), list):
|
|
132
|
+
new_messages = list(result["messages"])
|
|
133
|
+
self._messages = new_messages
|
|
134
|
+
self._append_transcript_message(appended_message)
|
|
135
|
+
for message in new_messages[baseline_count:]:
|
|
136
|
+
self._append_transcript_message(message)
|
|
137
|
+
self._active_files.extend(self._extract_accessed_files(result))
|
|
138
|
+
return dict(result)
|
|
139
|
+
|
|
140
|
+
async def stream_loop_events(
|
|
141
|
+
self,
|
|
142
|
+
user_input: str,
|
|
143
|
+
*,
|
|
144
|
+
config: dict[str, Any] | None = None,
|
|
145
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
146
|
+
"""
|
|
147
|
+
流式执行一轮 loop,复用 AgentSession 的消息上下文。
|
|
148
|
+
|
|
149
|
+
说明:
|
|
150
|
+
该方法用于需要 astream_events 的场景(如 SSE),执行完成后会尝试
|
|
151
|
+
从 on_chain_end(LangGraph) 事件回填消息列表,保持与 run_loop 一致的上下文累积语义。
|
|
152
|
+
"""
|
|
153
|
+
if self._graph is None:
|
|
154
|
+
raise RuntimeError("AgentSession graph is not initialized")
|
|
155
|
+
appended_message = HumanMessage(content=user_input)
|
|
156
|
+
self._messages.append(appended_message)
|
|
157
|
+
baseline_count = len(self._messages)
|
|
158
|
+
output_messages: list[Any] | None = None
|
|
159
|
+
try:
|
|
160
|
+
async for event in self._graph.astream_events(
|
|
161
|
+
{
|
|
162
|
+
"messages": list(self._messages),
|
|
163
|
+
"_session_id": self._session_id,
|
|
164
|
+
},
|
|
165
|
+
config=config,
|
|
166
|
+
version="v2",
|
|
167
|
+
):
|
|
168
|
+
if event.get("event") == "on_chain_end" and event.get("name") == "LangGraph":
|
|
169
|
+
data = event.get("data", {}) or {}
|
|
170
|
+
output = data.get("output")
|
|
171
|
+
if isinstance(output, dict) and isinstance(output.get("messages"), list):
|
|
172
|
+
output_messages = list(output["messages"])
|
|
173
|
+
yield event
|
|
174
|
+
except Exception:
|
|
175
|
+
if self._messages and self._messages[-1] is appended_message:
|
|
176
|
+
self._messages.pop()
|
|
177
|
+
raise
|
|
178
|
+
if output_messages is not None:
|
|
179
|
+
self._messages = output_messages
|
|
180
|
+
self._append_transcript_message(appended_message)
|
|
181
|
+
for message in output_messages[baseline_count:]:
|
|
182
|
+
self._append_transcript_message(message)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def active_files(self) -> list[str]:
|
|
186
|
+
return list(self._active_files)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def dispatcher(self) -> CommandDispatcher:
|
|
190
|
+
return self._dispatcher
|
|
191
|
+
|
|
192
|
+
def clear_context(self) -> None:
|
|
193
|
+
self._messages.clear()
|
|
194
|
+
|
|
195
|
+
async def compact_context(self) -> tuple[int, int]:
|
|
196
|
+
before_tokens = total_estimated_tokens_for_messages(self._messages, num_chars_per_token=4)
|
|
197
|
+
patch = default_loop_context_compaction().run({"messages": self._messages})
|
|
198
|
+
if isinstance(patch.get("messages"), list):
|
|
199
|
+
self._messages = list(patch["messages"])
|
|
200
|
+
after_tokens = total_estimated_tokens_for_messages(self._messages, num_chars_per_token=4)
|
|
201
|
+
if self._transcript_writer is not None:
|
|
202
|
+
self._append_compact_boundary(before_tokens=before_tokens, after_tokens=after_tokens)
|
|
203
|
+
return before_tokens, after_tokens
|
|
204
|
+
|
|
205
|
+
async def reload_plugins(self) -> PluginLoadResult:
|
|
206
|
+
if self._plugin_loader is None:
|
|
207
|
+
return PluginLoadResult(enabled=[], errors=[])
|
|
208
|
+
result = await self._plugin_loader.reload(container_type=self._container_type)
|
|
209
|
+
result.log_errors(logger)
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
def get_memory_summary(self, sub_cmd: str = "show") -> str:
|
|
213
|
+
memory_path = self._workspace_cfg.session_memory_path(self._session_id)
|
|
214
|
+
if sub_cmd == "clear":
|
|
215
|
+
if memory_path.exists():
|
|
216
|
+
memory_path.unlink()
|
|
217
|
+
return "记忆已清除"
|
|
218
|
+
if not memory_path.exists():
|
|
219
|
+
return "(暂无记忆摘要)"
|
|
220
|
+
content = memory_path.read_text(encoding="utf-8").strip()
|
|
221
|
+
return content or "(暂无记忆摘要)"
|
|
222
|
+
|
|
223
|
+
def _build_context(self) -> CommandContext:
|
|
224
|
+
return CommandContext(
|
|
225
|
+
session_id=self._session_id,
|
|
226
|
+
session=self,
|
|
227
|
+
workspace_cfg=self._workspace_cfg,
|
|
228
|
+
hook_engine=self._hook_engine,
|
|
229
|
+
plugin_loader=self._plugin_loader,
|
|
230
|
+
extra={"container_type": self._container_type},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
async def dispatch_command(self, user_input: str):
|
|
234
|
+
return await self._dispatcher.dispatch(user_input, self._build_context())
|
|
235
|
+
|
|
236
|
+
def list_commands(self) -> list[dict[str, object]]:
|
|
237
|
+
return self._runtime_command_registry.list_for_help()
|
|
238
|
+
|
|
239
|
+
async def _execute_hook(self, event: HookEvent) -> None:
|
|
240
|
+
ctx = HookContext(event=event, state={}, session_id=self._session_id)
|
|
241
|
+
# HookEngine 当前主接口是 execute;若未来扩展 execute_async,优先兼容新接口。
|
|
242
|
+
execute_async = getattr(self._hook_engine, "execute_async", None)
|
|
243
|
+
if callable(execute_async):
|
|
244
|
+
async_out = execute_async(ctx)
|
|
245
|
+
if inspect.isawaitable(async_out):
|
|
246
|
+
await async_out
|
|
247
|
+
# 一旦存在 execute_async,就不再回落执行 execute,避免双执行歧义。
|
|
248
|
+
return
|
|
249
|
+
execute = getattr(self._hook_engine, "execute", None)
|
|
250
|
+
if not callable(execute):
|
|
251
|
+
return
|
|
252
|
+
out = execute(ctx)
|
|
253
|
+
if inspect.isawaitable(out):
|
|
254
|
+
await out
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def _extract_accessed_files(result: Any) -> list[str]:
|
|
258
|
+
if not isinstance(result, dict):
|
|
259
|
+
return []
|
|
260
|
+
files = result.get("_accessed_files")
|
|
261
|
+
if not isinstance(files, list):
|
|
262
|
+
return []
|
|
263
|
+
return [item for item in files if isinstance(item, str)]
|
|
264
|
+
|
|
265
|
+
def _build_graph(self) -> CompiledStateGraph:
|
|
266
|
+
if self._graph_factory is None:
|
|
267
|
+
raise RuntimeError("AgentSession requires either graph or graph_factory")
|
|
268
|
+
getter = lambda: list(self._active_files)
|
|
269
|
+
return self._graph_factory(
|
|
270
|
+
active_files_getter=getter,
|
|
271
|
+
container_type=self._container_type,
|
|
272
|
+
enable_trace=self._enable_trace,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def _append_transcript_message(self, message: Any) -> None:
|
|
276
|
+
if self._transcript_writer is None:
|
|
277
|
+
return
|
|
278
|
+
record = self._serialize_message_for_transcript(message)
|
|
279
|
+
self._append_transcript_record(record)
|
|
280
|
+
|
|
281
|
+
def _append_compact_boundary(self, *, before_tokens: int, after_tokens: int) -> None:
|
|
282
|
+
record = {
|
|
283
|
+
"type": "compact_boundary",
|
|
284
|
+
"summary": f"compacted context {before_tokens}->{after_tokens}",
|
|
285
|
+
}
|
|
286
|
+
self._append_transcript_record(record)
|
|
287
|
+
|
|
288
|
+
def _append_transcript_record(self, payload: dict[str, Any]) -> None:
|
|
289
|
+
if self._transcript_writer is None:
|
|
290
|
+
return
|
|
291
|
+
uuid = str(uuid4())
|
|
292
|
+
record = {
|
|
293
|
+
**payload,
|
|
294
|
+
"uuid": uuid,
|
|
295
|
+
"logical_parent_uuid": self._last_transcript_uuid,
|
|
296
|
+
"ts": time.time(),
|
|
297
|
+
}
|
|
298
|
+
self._last_transcript_uuid = uuid
|
|
299
|
+
self._transcript_writer.append_message(self._session_id, record)
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def _serialize_message_for_transcript(message: Any) -> dict[str, Any]:
|
|
303
|
+
role = getattr(message, "type", type(message).__name__.lower())
|
|
304
|
+
content = getattr(message, "content", "")
|
|
305
|
+
if isinstance(content, list):
|
|
306
|
+
normalized_content: Any = [str(item) for item in content]
|
|
307
|
+
elif isinstance(content, str):
|
|
308
|
+
normalized_content = content
|
|
309
|
+
else:
|
|
310
|
+
normalized_content = str(content)
|
|
311
|
+
return {"type": str(role), "role": str(role), "content": normalized_content}
|
|
312
|
+
|
|
313
|
+
def _register_builtin_commands(self) -> None:
|
|
314
|
+
self._runtime_command_registry.register(make_clear_command())
|
|
315
|
+
self._runtime_command_registry.register(make_compact_command())
|
|
316
|
+
self._runtime_command_registry.register(make_memory_command())
|
|
317
|
+
self._runtime_command_registry.register(make_reload_plugins_command())
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
__all__ = ["AgentSession"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
session/conversation_factory.py — ConversationSession 入口工厂。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
统一组装 ConversationSession 所需 graph_factory、workspace 配置与 hook_engine。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
作为 Web/IM 对话场景创建 ConversationSession 的便捷入口。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅覆盖基础参数透传,不包含 HTTP 框架集成。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
from uuid import uuid4
|
|
19
|
+
|
|
20
|
+
from langchain_core.messages import BaseMessage
|
|
21
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
22
|
+
|
|
23
|
+
from ..loop.graph.factory import create_loop_agent
|
|
24
|
+
from ..loop.hook import HookEngine, HooksConfigSnapshot, HookRegistry, WorkspaceTrustChecker
|
|
25
|
+
from ..workspace import resolve_agent_workspace_config
|
|
26
|
+
from .conversation_session import ConversationSession
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_conversation_session(
|
|
30
|
+
model: str | BaseChatModel,
|
|
31
|
+
*,
|
|
32
|
+
conversation_id: str | None = None,
|
|
33
|
+
workspace_root: str | Path | None = None,
|
|
34
|
+
agent_home: str = ".langchain_agentx",
|
|
35
|
+
hooks: HookRegistry | None = None,
|
|
36
|
+
hook_engine: Any | None = None,
|
|
37
|
+
initial_messages: list[BaseMessage] | None = None,
|
|
38
|
+
permission_resolver: Any | None = None,
|
|
39
|
+
**loop_kwargs: Any,
|
|
40
|
+
) -> ConversationSession:
|
|
41
|
+
"""创建 ConversationSession。conversation_id 未传时自动生成。"""
|
|
42
|
+
resolved_id = conversation_id or f"conv-{uuid4().hex}"
|
|
43
|
+
workspace_cfg = resolve_agent_workspace_config(
|
|
44
|
+
workspace_root=workspace_root or Path.cwd(),
|
|
45
|
+
agent_home=agent_home,
|
|
46
|
+
)
|
|
47
|
+
if hook_engine is None:
|
|
48
|
+
snapshot = hooks.build_snapshot() if hooks is not None else HooksConfigSnapshot()
|
|
49
|
+
hook_engine = HookEngine(
|
|
50
|
+
snapshot,
|
|
51
|
+
trust_checker=WorkspaceTrustChecker(
|
|
52
|
+
headless=False,
|
|
53
|
+
workspace_trust_accepted=workspace_cfg.workspace_trust_accepted,
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
resolved_permission_resolver = permission_resolver
|
|
58
|
+
if resolved_permission_resolver is None and "permission_resolver" in loop_kwargs:
|
|
59
|
+
resolved_permission_resolver = loop_kwargs.pop("permission_resolver")
|
|
60
|
+
|
|
61
|
+
def _graph_factory(
|
|
62
|
+
session_id: str,
|
|
63
|
+
container_type: str = "conversation",
|
|
64
|
+
**_ignored: Any,
|
|
65
|
+
) -> Any:
|
|
66
|
+
return create_loop_agent(
|
|
67
|
+
model,
|
|
68
|
+
session_id=session_id,
|
|
69
|
+
workspace_root=workspace_cfg.workspace_root,
|
|
70
|
+
agent_home=workspace_cfg.agent_home,
|
|
71
|
+
workspace_trust_accepted=workspace_cfg.workspace_trust_accepted,
|
|
72
|
+
hooks=hooks,
|
|
73
|
+
permission_resolver=resolved_permission_resolver,
|
|
74
|
+
container_type=container_type,
|
|
75
|
+
**loop_kwargs,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return ConversationSession(
|
|
79
|
+
graph_factory=_graph_factory,
|
|
80
|
+
conversation_id=resolved_id,
|
|
81
|
+
workspace_cfg=workspace_cfg,
|
|
82
|
+
hook_engine=hook_engine,
|
|
83
|
+
initial_messages=list(initial_messages or []),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = ["create_conversation_session"]
|