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,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
plugin/registries.py — Plugin 能力注册表(Skill/Command/Agent)。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
管理插件能力索引与按插件来源的批量注销。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
PluginLoader 注册阶段写入,运行阶段由调用方按 key 查找能力。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅覆盖 Skill/Command/AgentDefinition;不含 MCP 注册管理。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .types import ComponentLoadFailedError, LoadedPlugin, PluginError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class SkillEntry:
|
|
24
|
+
key: str
|
|
25
|
+
plugin_source: str
|
|
26
|
+
path: Path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class CommandEntry:
|
|
31
|
+
key: str
|
|
32
|
+
plugin_source: str
|
|
33
|
+
name: str
|
|
34
|
+
path: Path
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class AgentDefinitionEntry:
|
|
39
|
+
key: str
|
|
40
|
+
plugin_source: str
|
|
41
|
+
name: str
|
|
42
|
+
path: Path
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SkillRegistry:
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
self._skills: dict[str, SkillEntry] = {}
|
|
48
|
+
self._by_plugin: dict[str, list[str]] = {}
|
|
49
|
+
|
|
50
|
+
def register(self, plugin: LoadedPlugin) -> list[PluginError]:
|
|
51
|
+
if plugin.skills_dir is None or not plugin.skills_dir.exists():
|
|
52
|
+
self._by_plugin[plugin.source] = []
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
errors: list[PluginError] = []
|
|
56
|
+
registered: list[str] = []
|
|
57
|
+
for skill_dir in sorted(plugin.skills_dir.iterdir()):
|
|
58
|
+
if not skill_dir.is_dir():
|
|
59
|
+
continue
|
|
60
|
+
skill_md = skill_dir / "SKILL.md"
|
|
61
|
+
if not skill_md.exists():
|
|
62
|
+
continue
|
|
63
|
+
skill_name = skill_dir.name
|
|
64
|
+
key = f"{plugin.source}:{skill_name}"
|
|
65
|
+
if key in self._skills:
|
|
66
|
+
errors.append(
|
|
67
|
+
ComponentLoadFailedError(
|
|
68
|
+
source=plugin.source,
|
|
69
|
+
plugin=plugin.name,
|
|
70
|
+
component="skills",
|
|
71
|
+
path=str(skill_md),
|
|
72
|
+
reason=f"duplicate skill key: {key}",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
continue
|
|
76
|
+
self._skills[key] = SkillEntry(key=key, plugin_source=plugin.source, path=skill_md)
|
|
77
|
+
registered.append(key)
|
|
78
|
+
self._by_plugin[plugin.source] = registered
|
|
79
|
+
return errors
|
|
80
|
+
|
|
81
|
+
def get(self, key: str) -> SkillEntry | None:
|
|
82
|
+
if key in self._skills:
|
|
83
|
+
return self._skills[key]
|
|
84
|
+
return self._find_by_short_name(key)
|
|
85
|
+
|
|
86
|
+
def unregister_plugin(self, plugin_source: str) -> None:
|
|
87
|
+
for key in self._by_plugin.pop(plugin_source, []):
|
|
88
|
+
self._skills.pop(key, None)
|
|
89
|
+
|
|
90
|
+
def list_all(self) -> list[SkillEntry]:
|
|
91
|
+
return list(self._skills.values())
|
|
92
|
+
|
|
93
|
+
def _find_by_short_name(self, short_name: str) -> SkillEntry | None:
|
|
94
|
+
matches = [entry for entry in self._skills.values() if entry.key.endswith(f":{short_name}")]
|
|
95
|
+
return matches[0] if len(matches) == 1 else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CommandRegistry:
|
|
99
|
+
def __init__(self) -> None:
|
|
100
|
+
self._commands: dict[str, CommandEntry] = {}
|
|
101
|
+
self._by_plugin: dict[str, list[str]] = {}
|
|
102
|
+
|
|
103
|
+
def register(self, plugin: LoadedPlugin) -> list[PluginError]:
|
|
104
|
+
if plugin.commands_dir is None or not plugin.commands_dir.exists():
|
|
105
|
+
self._by_plugin[plugin.source] = []
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
errors: list[PluginError] = []
|
|
109
|
+
registered: list[str] = []
|
|
110
|
+
for cmd_file in sorted(plugin.commands_dir.glob("*.md")):
|
|
111
|
+
cmd_name = cmd_file.stem
|
|
112
|
+
key = f"{plugin.source}:{cmd_name}"
|
|
113
|
+
if key in self._commands:
|
|
114
|
+
errors.append(
|
|
115
|
+
ComponentLoadFailedError(
|
|
116
|
+
source=plugin.source,
|
|
117
|
+
plugin=plugin.name,
|
|
118
|
+
component="commands",
|
|
119
|
+
path=str(cmd_file),
|
|
120
|
+
reason=f"duplicate command key: {key}",
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
continue
|
|
124
|
+
self._commands[key] = CommandEntry(
|
|
125
|
+
key=key,
|
|
126
|
+
plugin_source=plugin.source,
|
|
127
|
+
name=cmd_name,
|
|
128
|
+
path=cmd_file,
|
|
129
|
+
)
|
|
130
|
+
registered.append(key)
|
|
131
|
+
self._by_plugin[plugin.source] = registered
|
|
132
|
+
return errors
|
|
133
|
+
|
|
134
|
+
def get(self, name: str) -> CommandEntry | None:
|
|
135
|
+
if name in self._commands:
|
|
136
|
+
return self._commands[name]
|
|
137
|
+
return self._find_by_short_name(name)
|
|
138
|
+
|
|
139
|
+
def unregister_plugin(self, plugin_source: str) -> None:
|
|
140
|
+
for key in self._by_plugin.pop(plugin_source, []):
|
|
141
|
+
self._commands.pop(key, None)
|
|
142
|
+
|
|
143
|
+
def list_all(self) -> list[CommandEntry]:
|
|
144
|
+
return list(self._commands.values())
|
|
145
|
+
|
|
146
|
+
def _find_by_short_name(self, short_name: str) -> CommandEntry | None:
|
|
147
|
+
matches = [entry for entry in self._commands.values() if entry.key.endswith(f":{short_name}")]
|
|
148
|
+
return matches[0] if len(matches) == 1 else None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class AgentRegistry:
|
|
152
|
+
def __init__(self) -> None:
|
|
153
|
+
self._agents: dict[str, AgentDefinitionEntry] = {}
|
|
154
|
+
self._by_plugin: dict[str, list[str]] = {}
|
|
155
|
+
|
|
156
|
+
def register(self, plugin: LoadedPlugin) -> list[PluginError]:
|
|
157
|
+
if plugin.agents_dir is None or not plugin.agents_dir.exists():
|
|
158
|
+
self._by_plugin[plugin.source] = []
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
errors: list[PluginError] = []
|
|
162
|
+
registered: list[str] = []
|
|
163
|
+
for agent_file in sorted(plugin.agents_dir.glob("*.md")):
|
|
164
|
+
agent_name = agent_file.stem
|
|
165
|
+
key = f"{plugin.source}:{agent_name}"
|
|
166
|
+
if key in self._agents:
|
|
167
|
+
errors.append(
|
|
168
|
+
ComponentLoadFailedError(
|
|
169
|
+
source=plugin.source,
|
|
170
|
+
plugin=plugin.name,
|
|
171
|
+
component="agents",
|
|
172
|
+
path=str(agent_file),
|
|
173
|
+
reason=f"duplicate agent key: {key}",
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
continue
|
|
177
|
+
self._agents[key] = AgentDefinitionEntry(
|
|
178
|
+
key=key,
|
|
179
|
+
plugin_source=plugin.source,
|
|
180
|
+
name=agent_name,
|
|
181
|
+
path=agent_file,
|
|
182
|
+
)
|
|
183
|
+
registered.append(key)
|
|
184
|
+
self._by_plugin[plugin.source] = registered
|
|
185
|
+
return errors
|
|
186
|
+
|
|
187
|
+
def get(self, name: str) -> AgentDefinitionEntry | None:
|
|
188
|
+
if name in self._agents:
|
|
189
|
+
return self._agents[name]
|
|
190
|
+
return self._find_by_short_name(name)
|
|
191
|
+
|
|
192
|
+
def unregister_plugin(self, plugin_source: str) -> None:
|
|
193
|
+
for key in self._by_plugin.pop(plugin_source, []):
|
|
194
|
+
self._agents.pop(key, None)
|
|
195
|
+
|
|
196
|
+
def list_all(self) -> list[AgentDefinitionEntry]:
|
|
197
|
+
return list(self._agents.values())
|
|
198
|
+
|
|
199
|
+
def _find_by_short_name(self, short_name: str) -> AgentDefinitionEntry | None:
|
|
200
|
+
matches = [entry for entry in self._agents.values() if entry.key.endswith(f":{short_name}")]
|
|
201
|
+
return matches[0] if len(matches) == 1 else None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = [
|
|
205
|
+
"AgentDefinitionEntry",
|
|
206
|
+
"AgentRegistry",
|
|
207
|
+
"CommandEntry",
|
|
208
|
+
"CommandRegistry",
|
|
209
|
+
"SkillEntry",
|
|
210
|
+
"SkillRegistry",
|
|
211
|
+
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
plugin/types.py — Plugin 运行时数据类型定义。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
定义 LoadedPlugin、PluginError 判别联合与 PluginLoadResult。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
被 PluginLoader、各 Registry 与容器集成层共享。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅覆盖 P0/P1 数据类型,不含 MCP/依赖解析相关专用错误类型。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Literal
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class LoadedPlugin:
|
|
23
|
+
"""PluginLoader 加载后的运行时表示。"""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
version: str
|
|
28
|
+
path: str
|
|
29
|
+
source: str
|
|
30
|
+
enabled: bool = True
|
|
31
|
+
is_builtin: bool = False
|
|
32
|
+
skills_dir: Path | None = None
|
|
33
|
+
commands_dir: Path | None = None
|
|
34
|
+
agents_dir: Path | None = None
|
|
35
|
+
hooks_config: dict[str, Any] | None = None
|
|
36
|
+
mcp_servers: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
37
|
+
dependencies: list[str] = field(default_factory=list)
|
|
38
|
+
applicable_containers: list[str] = field(default_factory=list)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ManifestParseError:
|
|
43
|
+
type: Literal["manifest-parse-error"] = "manifest-parse-error"
|
|
44
|
+
source: str = ""
|
|
45
|
+
manifest_path: str = ""
|
|
46
|
+
parse_error: str = ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class ManifestValidationError:
|
|
51
|
+
type: Literal["manifest-validation-error"] = "manifest-validation-error"
|
|
52
|
+
source: str = ""
|
|
53
|
+
manifest_path: str = ""
|
|
54
|
+
validation_errors: list[str] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class ComponentLoadFailedError:
|
|
59
|
+
type: Literal["component-load-failed"] = "component-load-failed"
|
|
60
|
+
source: str = ""
|
|
61
|
+
plugin: str = ""
|
|
62
|
+
component: str = ""
|
|
63
|
+
path: str = ""
|
|
64
|
+
reason: str = ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class HookLoadFailedError:
|
|
69
|
+
type: Literal["hook-load-failed"] = "hook-load-failed"
|
|
70
|
+
source: str = ""
|
|
71
|
+
plugin: str = ""
|
|
72
|
+
hook_path: str = ""
|
|
73
|
+
reason: str = ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class GenericPluginError:
|
|
78
|
+
type: Literal["generic-error"] = "generic-error"
|
|
79
|
+
source: str = ""
|
|
80
|
+
plugin: str = ""
|
|
81
|
+
error: str = ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
PluginError = (
|
|
85
|
+
ManifestParseError
|
|
86
|
+
| ManifestValidationError
|
|
87
|
+
| ComponentLoadFailedError
|
|
88
|
+
| HookLoadFailedError
|
|
89
|
+
| GenericPluginError
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_plugin_error_message(error: PluginError) -> str:
|
|
94
|
+
"""将 PluginError 转换为可日志化消息。"""
|
|
95
|
+
if isinstance(error, ManifestParseError):
|
|
96
|
+
return (
|
|
97
|
+
f"[{error.source}] manifest parse error at {error.manifest_path}: "
|
|
98
|
+
f"{error.parse_error}"
|
|
99
|
+
)
|
|
100
|
+
if isinstance(error, ManifestValidationError):
|
|
101
|
+
details = ", ".join(error.validation_errors)
|
|
102
|
+
return (
|
|
103
|
+
f"[{error.source}] manifest validation error at {error.manifest_path}: "
|
|
104
|
+
f"{details}"
|
|
105
|
+
)
|
|
106
|
+
if isinstance(error, ComponentLoadFailedError):
|
|
107
|
+
return (
|
|
108
|
+
f"[{error.source}] {error.component} load failed at {error.path}: "
|
|
109
|
+
f"{error.reason}"
|
|
110
|
+
)
|
|
111
|
+
if isinstance(error, HookLoadFailedError):
|
|
112
|
+
return f"[{error.source}] hook load failed at {error.hook_path}: {error.reason}"
|
|
113
|
+
return f"[{error.source}] plugin error: {error.error}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class PluginLoadResult:
|
|
118
|
+
"""Plugin 加载结果。"""
|
|
119
|
+
|
|
120
|
+
enabled: list[LoadedPlugin]
|
|
121
|
+
errors: list[PluginError]
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def has_errors(self) -> bool:
|
|
125
|
+
return bool(self.errors)
|
|
126
|
+
|
|
127
|
+
def log_errors(self, logger: Any) -> None:
|
|
128
|
+
for error in self.errors:
|
|
129
|
+
logger.warning("plugin load error: %s", get_plugin_error_message(error))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"ComponentLoadFailedError",
|
|
134
|
+
"GenericPluginError",
|
|
135
|
+
"HookLoadFailedError",
|
|
136
|
+
"LoadedPlugin",
|
|
137
|
+
"ManifestParseError",
|
|
138
|
+
"ManifestValidationError",
|
|
139
|
+
"PluginError",
|
|
140
|
+
"PluginLoadResult",
|
|
141
|
+
"get_plugin_error_message",
|
|
142
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider layer for langchain_agentx.
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
对外提供模型 Provider 兼容封装入口。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
被 examples 与 loop/integration 侧导入,用于构建可替换的 ChatModel。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
- CompatibleChatOpenAI(OpenAI 兼容 reasoning_content 透传)
|
|
12
|
+
- get_chat_openai / get_chat_anthropic(从环境变量构造 ChatModel 的可复用组合件)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .anthropic import get_chat_anthropic
|
|
16
|
+
from .compatible_chat_openai import CompatibleChatOpenAI
|
|
17
|
+
from .model_profile import ModelProfile, ModelProfileRegistry
|
|
18
|
+
from .openai import get_chat_openai
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CompatibleChatOpenAI",
|
|
22
|
+
"get_chat_anthropic",
|
|
23
|
+
"get_chat_openai",
|
|
24
|
+
"ModelProfile",
|
|
25
|
+
"ModelProfileRegistry",
|
|
26
|
+
]
|
|
27
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""provider/anthropic.py — Anthropic ChatModel 工厂(可复用组合件)。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
- 从环境变量读取 Anthropic 配置(auth token/base_url/model)。
|
|
5
|
+
- 处理部分企业代理环境下 httpx 对 socks:// 的不兼容。
|
|
6
|
+
- 组装并返回 ChatAnthropic。
|
|
7
|
+
|
|
8
|
+
约束:
|
|
9
|
+
- 不加载 .env;由调用方(例如 examples)负责把 .env 注入到环境变量。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
18
|
+
|
|
19
|
+
from .env import get_env_first, parse_positive_int
|
|
20
|
+
from .model_profile import ModelProfileRegistry
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _sanitize_proxy_env_for_anthropic() -> None:
|
|
24
|
+
"""Anthropic/httpx 不接受 socks://,保留 http/https 代理并移除异常 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 _resolve_claude_max_tokens(explicit: int | None, *, profile_default: int) -> int:
|
|
34
|
+
if explicit is not None:
|
|
35
|
+
return int(explicit)
|
|
36
|
+
override = get_env_first("CLAUDE_MAX_OUTPUT_TOKENS", "CLAUDE_CODE_MAX_OUTPUT_TOKENS")
|
|
37
|
+
parsed = parse_positive_int(override)
|
|
38
|
+
return parsed if parsed is not None else profile_default
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _is_thinking_enabled(thinking_option: Any) -> bool:
|
|
42
|
+
if isinstance(thinking_option, bool):
|
|
43
|
+
return thinking_option
|
|
44
|
+
if isinstance(thinking_option, dict):
|
|
45
|
+
enabled = thinking_option.get("enabled")
|
|
46
|
+
if isinstance(enabled, bool):
|
|
47
|
+
return enabled
|
|
48
|
+
return bool(thinking_option)
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_chat_anthropic(
|
|
53
|
+
*,
|
|
54
|
+
model: str | None = None,
|
|
55
|
+
api_key: str | None = None,
|
|
56
|
+
base_url: str | None = None,
|
|
57
|
+
temperature: float | None = None,
|
|
58
|
+
max_tokens: int | None = None,
|
|
59
|
+
top_p: float | None = None,
|
|
60
|
+
streaming: bool = True,
|
|
61
|
+
model_profile_registry: ModelProfileRegistry | None = None,
|
|
62
|
+
**kwargs: Any,
|
|
63
|
+
) -> BaseChatModel:
|
|
64
|
+
from langchain_anthropic import ChatAnthropic # pyright: ignore[reportMissingImports]
|
|
65
|
+
|
|
66
|
+
_sanitize_proxy_env_for_anthropic()
|
|
67
|
+
|
|
68
|
+
resolved_api_key = api_key or get_env_first(
|
|
69
|
+
"LANGCHAIN_AGENTX_ANTHROPIC_AUTH_TOKEN",
|
|
70
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
71
|
+
"ANTHROPIC_API_KEY",
|
|
72
|
+
)
|
|
73
|
+
resolved_base_url = base_url or get_env_first(
|
|
74
|
+
"LANGCHAIN_AGENTX_ANTHROPIC_BASE_URL",
|
|
75
|
+
"ANTHROPIC_BASE_URL",
|
|
76
|
+
)
|
|
77
|
+
resolved_model = (
|
|
78
|
+
model
|
|
79
|
+
or get_env_first("LANGCHAIN_AGENTX_ANTHROPIC_MODEL", "ANTHROPIC_MODEL")
|
|
80
|
+
or "claude-sonnet-4-20250514"
|
|
81
|
+
)
|
|
82
|
+
profile = (
|
|
83
|
+
model_profile_registry.get(resolved_model)
|
|
84
|
+
if model_profile_registry is not None
|
|
85
|
+
else ModelProfileRegistry().defaults
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if not resolved_api_key or not resolved_base_url:
|
|
89
|
+
raise RuntimeError(
|
|
90
|
+
"缺少 Anthropic 配置:请设置 ANTHROPIC_AUTH_TOKEN(或 ANTHROPIC_API_KEY /"
|
|
91
|
+
" LANGCHAIN_AGENTX_ANTHROPIC_AUTH_TOKEN)以及 ANTHROPIC_BASE_URL"
|
|
92
|
+
"(或 LANGCHAIN_AGENTX_ANTHROPIC_BASE_URL)。默认模型名可通过"
|
|
93
|
+
" ANTHROPIC_MODEL / LANGCHAIN_AGENTX_ANTHROPIC_MODEL 或 model 参数指定。"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
resolved_max_tokens = _resolve_claude_max_tokens(
|
|
97
|
+
max_tokens,
|
|
98
|
+
profile_default=profile.max_output_tokens,
|
|
99
|
+
)
|
|
100
|
+
thinking_enabled = _is_thinking_enabled(kwargs.get("thinking"))
|
|
101
|
+
resolved_temperature = profile.default_temperature if temperature is None else float(temperature)
|
|
102
|
+
resolved_top_p = profile.default_top_p if top_p is None else float(top_p)
|
|
103
|
+
|
|
104
|
+
anthropic_kwargs: dict[str, Any] = {
|
|
105
|
+
"model": resolved_model,
|
|
106
|
+
"anthropic_api_key": resolved_api_key,
|
|
107
|
+
"anthropic_api_url": resolved_base_url.rstrip("/"),
|
|
108
|
+
"max_tokens": resolved_max_tokens,
|
|
109
|
+
"streaming": streaming,
|
|
110
|
+
**kwargs,
|
|
111
|
+
}
|
|
112
|
+
if resolved_top_p is not None:
|
|
113
|
+
anthropic_kwargs["top_p"] = resolved_top_p
|
|
114
|
+
if not thinking_enabled:
|
|
115
|
+
anthropic_kwargs["temperature"] = resolved_temperature
|
|
116
|
+
|
|
117
|
+
return ChatAnthropic(**anthropic_kwargs)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = ["get_chat_anthropic"]
|
|
121
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CompatibleChatOpenAI
|
|
3
|
+
====================
|
|
4
|
+
|
|
5
|
+
为 **任意 OpenAI 兼容聊天服务** 提供一个轻量级的 LangChain ChatModel 封装,
|
|
6
|
+
在不 fork `langchain-openai` 的前提下:
|
|
7
|
+
|
|
8
|
+
- 复用官方 `ChatOpenAI` 的绝大部分行为(重试、流式、usage 统计等)
|
|
9
|
+
- 额外把流式响应中的 `reasoning_content` 注入到
|
|
10
|
+
`AIMessageChunk.additional_kwargs["reasoning_content"]`,让下游组件按
|
|
11
|
+
LangChain v2 标准路径解析 reasoning。
|
|
12
|
+
- 在组请求体时把历史 ``AIMessage.additional_kwargs["reasoning_content"]`` 写回
|
|
13
|
+
``messages[].reasoning_content``(``langchain_openai`` 默认不落该字段)。
|
|
14
|
+
部分 OpenAI 兼容思考模型要求多轮必须把上一轮 reasoning 原样带回,否则会 400。
|
|
15
|
+
|
|
16
|
+
与 loop 终局契约:
|
|
17
|
+
若 HTTP/鉴权等错误在 invoke/ainvoke 阶段以异常抛出,
|
|
18
|
+
由 model_nodes 统一合成带 response_metadata.langchain_agentx_api_error/error 的 AIMessage。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any, Dict, Type
|
|
24
|
+
|
|
25
|
+
from langchain_core.language_models import LanguageModelInput
|
|
26
|
+
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessageChunk
|
|
27
|
+
from langchain_core.outputs import ChatGenerationChunk
|
|
28
|
+
from langchain_openai import ChatOpenAI
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CompatibleChatOpenAI(ChatOpenAI):
|
|
32
|
+
"""OpenAI 兼容 ChatModel 封装(支持 reasoning_content)。"""
|
|
33
|
+
|
|
34
|
+
def _get_request_payload(
|
|
35
|
+
self,
|
|
36
|
+
input_: LanguageModelInput,
|
|
37
|
+
*,
|
|
38
|
+
stop: list[str] | None = None,
|
|
39
|
+
**kwargs: Any,
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
"""与基类一致,并为 chat/completions 的 assistant 历史补上 ``reasoning_content``。"""
|
|
42
|
+
lc_messages = self._convert_input(input_).to_messages()
|
|
43
|
+
payload = super()._get_request_payload(input_, stop=stop, **kwargs)
|
|
44
|
+
serialized = payload.get("messages")
|
|
45
|
+
if not isinstance(serialized, list) or len(serialized) != len(lc_messages):
|
|
46
|
+
return payload
|
|
47
|
+
for msg_dict, lc_msg in zip(serialized, lc_messages):
|
|
48
|
+
if not isinstance(lc_msg, AIMessage):
|
|
49
|
+
continue
|
|
50
|
+
rc = lc_msg.additional_kwargs.get("reasoning_content")
|
|
51
|
+
if isinstance(rc, str) and rc:
|
|
52
|
+
msg_dict["reasoning_content"] = rc
|
|
53
|
+
return payload
|
|
54
|
+
|
|
55
|
+
def _convert_chunk_to_generation_chunk(
|
|
56
|
+
self,
|
|
57
|
+
chunk: Dict[str, Any],
|
|
58
|
+
default_chunk_class: Type[BaseMessageChunk],
|
|
59
|
+
base_generation_info: Dict[str, Any] | None,
|
|
60
|
+
) -> ChatGenerationChunk | None:
|
|
61
|
+
gen_chunk = super()._convert_chunk_to_generation_chunk(
|
|
62
|
+
chunk, default_chunk_class, base_generation_info
|
|
63
|
+
)
|
|
64
|
+
if gen_chunk is None:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
message = gen_chunk.message
|
|
68
|
+
if not isinstance(message, AIMessageChunk):
|
|
69
|
+
return gen_chunk
|
|
70
|
+
|
|
71
|
+
choices = chunk.get("choices", []) or chunk.get("chunk", {}).get("choices", [])
|
|
72
|
+
if not choices:
|
|
73
|
+
return gen_chunk
|
|
74
|
+
|
|
75
|
+
delta = choices[0].get("delta") or {}
|
|
76
|
+
rc = delta.get("reasoning_content")
|
|
77
|
+
if isinstance(rc, str) and rc:
|
|
78
|
+
ak = getattr(message, "additional_kwargs", None) or {}
|
|
79
|
+
ak["reasoning_content"] = rc
|
|
80
|
+
message.additional_kwargs = ak
|
|
81
|
+
|
|
82
|
+
return gen_chunk
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = ["CompatibleChatOpenAI"]
|
|
86
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""provider/env.py — provider 工厂的环境变量解析工具。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
为 provider 工厂函数提供一致的 env 读取与布尔/数值解析。
|
|
5
|
+
|
|
6
|
+
约束:
|
|
7
|
+
- 只读取 os.environ,不加载 .env(示例层负责)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_env_first(*names: str) -> str | None:
|
|
16
|
+
for name in names:
|
|
17
|
+
v = os.environ.get(name)
|
|
18
|
+
if v and str(v).strip():
|
|
19
|
+
return str(v).strip()
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_positive_int(raw: str | None) -> int | None:
|
|
24
|
+
if raw is None:
|
|
25
|
+
return None
|
|
26
|
+
try:
|
|
27
|
+
value = int(str(raw).strip())
|
|
28
|
+
except ValueError:
|
|
29
|
+
return None
|
|
30
|
+
return value if value > 0 else None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_bool(raw: str | None) -> bool | None:
|
|
34
|
+
if raw is None:
|
|
35
|
+
return None
|
|
36
|
+
v = str(raw).strip().lower()
|
|
37
|
+
if v in {"1", "true", "yes", "y", "on"}:
|
|
38
|
+
return True
|
|
39
|
+
if v in {"0", "false", "no", "n", "off"}:
|
|
40
|
+
return False
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["get_env_first", "parse_bool", "parse_positive_int"]
|
|
45
|
+
|