agent_os_kernel 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
LangChain Client Adapter - Drop-In Middleware for Agent Control Plane
|
|
5
|
+
|
|
6
|
+
This adapter wraps LangChain clients to automatically intercept and govern
|
|
7
|
+
tool calls made by LangChain agents. It provides similar integration as the
|
|
8
|
+
OpenAI adapter, but for LangChain's framework.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from langchain.chat_models import ChatOpenAI
|
|
12
|
+
from agent_control_plane import AgentControlPlane
|
|
13
|
+
from agent_control_plane.langchain_adapter import LangChainAdapter
|
|
14
|
+
|
|
15
|
+
# Standard setup
|
|
16
|
+
llm = ChatOpenAI(temperature=0)
|
|
17
|
+
control_plane = AgentControlPlane()
|
|
18
|
+
agent_context = control_plane.create_agent("my-agent", permissions)
|
|
19
|
+
|
|
20
|
+
# Wrap with adapter
|
|
21
|
+
governed_llm = LangChainAdapter(
|
|
22
|
+
control_plane=control_plane,
|
|
23
|
+
agent_context=agent_context,
|
|
24
|
+
langchain_client=llm
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Use in LangChain agents
|
|
28
|
+
from langchain.agents import initialize_agent
|
|
29
|
+
agent = initialize_agent(tools, governed_llm, agent="zero-shot-react-description")
|
|
30
|
+
agent.run("Your task here")
|
|
31
|
+
# Tool calls are automatically governed by the control plane!
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from typing import Any, Dict, List, Optional, Callable, Sequence
|
|
35
|
+
import json
|
|
36
|
+
import logging
|
|
37
|
+
from datetime import datetime
|
|
38
|
+
|
|
39
|
+
from .agent_kernel import ActionType, AgentContext
|
|
40
|
+
from .control_plane import AgentControlPlane
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Mapping from common LangChain tool names to ActionType
|
|
44
|
+
DEFAULT_LANGCHAIN_TOOL_MAPPING = {
|
|
45
|
+
# File operations
|
|
46
|
+
"read_file": ActionType.FILE_READ,
|
|
47
|
+
"write_file": ActionType.FILE_WRITE,
|
|
48
|
+
"file_read": ActionType.FILE_READ,
|
|
49
|
+
"file_write": ActionType.FILE_WRITE,
|
|
50
|
+
"readfile": ActionType.FILE_READ,
|
|
51
|
+
"writefile": ActionType.FILE_WRITE,
|
|
52
|
+
|
|
53
|
+
# Code execution
|
|
54
|
+
"python_repl": ActionType.CODE_EXECUTION,
|
|
55
|
+
"python": ActionType.CODE_EXECUTION,
|
|
56
|
+
"terminal": ActionType.CODE_EXECUTION,
|
|
57
|
+
"shell": ActionType.CODE_EXECUTION,
|
|
58
|
+
"bash": ActionType.CODE_EXECUTION,
|
|
59
|
+
|
|
60
|
+
# Database operations
|
|
61
|
+
"sql_db_query": ActionType.DATABASE_QUERY,
|
|
62
|
+
"sql_db_schema": ActionType.DATABASE_QUERY,
|
|
63
|
+
"sql_db_list_tables": ActionType.DATABASE_QUERY,
|
|
64
|
+
"sql_db_query_checker": ActionType.DATABASE_QUERY,
|
|
65
|
+
|
|
66
|
+
# API calls
|
|
67
|
+
"requests_get": ActionType.API_CALL,
|
|
68
|
+
"requests_post": ActionType.API_CALL,
|
|
69
|
+
"requests": ActionType.API_CALL,
|
|
70
|
+
"api_request": ActionType.API_CALL,
|
|
71
|
+
|
|
72
|
+
# Search and retrieval
|
|
73
|
+
"google_search": ActionType.API_CALL,
|
|
74
|
+
"serpapi": ActionType.API_CALL,
|
|
75
|
+
"wikipedia": ActionType.API_CALL,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LangChainAdapter:
|
|
80
|
+
"""
|
|
81
|
+
LangChain Client Adapter with Agent Control Plane Governance.
|
|
82
|
+
|
|
83
|
+
This class wraps a LangChain LLM or agent to provide automatic governance
|
|
84
|
+
of tool calls. It intercepts tool invocations and checks them against the
|
|
85
|
+
control plane's policies before allowing execution.
|
|
86
|
+
|
|
87
|
+
The adapter works by wrapping the tool execution layer, similar to how
|
|
88
|
+
the OpenAI adapter wraps chat completions.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
# Before (ungoverned):
|
|
92
|
+
from langchain.chat_models import ChatOpenAI
|
|
93
|
+
llm = ChatOpenAI()
|
|
94
|
+
agent = initialize_agent(tools, llm, agent="zero-shot-react-description")
|
|
95
|
+
|
|
96
|
+
# After (governed):
|
|
97
|
+
governed_llm = LangChainAdapter(control_plane, agent_context, llm)
|
|
98
|
+
agent = initialize_agent(tools, governed_llm, agent="zero-shot-react-description")
|
|
99
|
+
# Same API, but now with governance!
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
control_plane: AgentControlPlane,
|
|
105
|
+
agent_context: AgentContext,
|
|
106
|
+
langchain_client: Any,
|
|
107
|
+
tool_mapping: Optional[Dict[str, ActionType]] = None,
|
|
108
|
+
on_block: Optional[Callable[[str, Dict, Dict], None]] = None,
|
|
109
|
+
logger: Optional[logging.Logger] = None
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
Initialize the LangChain adapter.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
control_plane: The AgentControlPlane instance for governance
|
|
116
|
+
agent_context: The AgentContext for the agent using this client
|
|
117
|
+
langchain_client: The original LangChain LLM or agent instance
|
|
118
|
+
tool_mapping: Optional custom mapping from tool names to ActionTypes
|
|
119
|
+
on_block: Optional callback called when an action is blocked
|
|
120
|
+
Signature: on_block(tool_name: str, tool_args: dict, result: dict)
|
|
121
|
+
logger: Optional logger instance
|
|
122
|
+
"""
|
|
123
|
+
self.control_plane = control_plane
|
|
124
|
+
self.agent_context = agent_context
|
|
125
|
+
self.client = langchain_client
|
|
126
|
+
self.logger = logger or logging.getLogger("LangChainAdapter")
|
|
127
|
+
self.on_block = on_block
|
|
128
|
+
|
|
129
|
+
# Merge default mapping with custom mapping
|
|
130
|
+
self.tool_mapping = DEFAULT_LANGCHAIN_TOOL_MAPPING.copy()
|
|
131
|
+
if tool_mapping:
|
|
132
|
+
self.tool_mapping.update({k.lower(): v for k, v in tool_mapping.items()})
|
|
133
|
+
|
|
134
|
+
# Store original methods to wrap
|
|
135
|
+
self._original_call = None
|
|
136
|
+
self._original_generate = None
|
|
137
|
+
self._original_invoke = None
|
|
138
|
+
|
|
139
|
+
# Wrap the client methods
|
|
140
|
+
self._wrap_client()
|
|
141
|
+
|
|
142
|
+
self.logger.info(
|
|
143
|
+
f"Initialized LangChainAdapter for agent {agent_context.agent_id}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def _wrap_client(self):
|
|
147
|
+
"""Wrap the LangChain client's methods to intercept tool calls."""
|
|
148
|
+
# LangChain uses different methods depending on the version
|
|
149
|
+
# We wrap common invocation methods
|
|
150
|
+
|
|
151
|
+
if hasattr(self.client, '__call__'):
|
|
152
|
+
self._original_call = self.client.__call__
|
|
153
|
+
self.client.__call__ = self._governed_call
|
|
154
|
+
|
|
155
|
+
if hasattr(self.client, 'generate'):
|
|
156
|
+
self._original_generate = self.client.generate
|
|
157
|
+
self.client.generate = self._governed_generate
|
|
158
|
+
|
|
159
|
+
if hasattr(self.client, 'invoke'):
|
|
160
|
+
self._original_invoke = self.client.invoke
|
|
161
|
+
self.client.invoke = self._governed_invoke
|
|
162
|
+
|
|
163
|
+
def _governed_call(self, *args, **kwargs):
|
|
164
|
+
"""Wrapped __call__ method with governance."""
|
|
165
|
+
return self._execute_with_governance(self._original_call, *args, **kwargs)
|
|
166
|
+
|
|
167
|
+
def _governed_generate(self, *args, **kwargs):
|
|
168
|
+
"""Wrapped generate method with governance."""
|
|
169
|
+
return self._execute_with_governance(self._original_generate, *args, **kwargs)
|
|
170
|
+
|
|
171
|
+
def _governed_invoke(self, *args, **kwargs):
|
|
172
|
+
"""Wrapped invoke method with governance."""
|
|
173
|
+
return self._execute_with_governance(self._original_invoke, *args, **kwargs)
|
|
174
|
+
|
|
175
|
+
def _execute_with_governance(self, original_method, *args, **kwargs):
|
|
176
|
+
"""
|
|
177
|
+
Execute the original method while intercepting tool calls.
|
|
178
|
+
|
|
179
|
+
This is the core governance logic that checks tool calls against
|
|
180
|
+
the control plane before allowing execution.
|
|
181
|
+
"""
|
|
182
|
+
# Call the original method
|
|
183
|
+
result = original_method(*args, **kwargs)
|
|
184
|
+
|
|
185
|
+
# Check if the result contains tool calls or actions
|
|
186
|
+
# LangChain formats vary, so we handle multiple formats
|
|
187
|
+
tool_calls = self._extract_tool_calls(result)
|
|
188
|
+
|
|
189
|
+
if tool_calls:
|
|
190
|
+
self.logger.info(
|
|
191
|
+
f"Agent {self.agent_context.agent_id}: Intercepting {len(tool_calls)} tool call(s)"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Check each tool call
|
|
195
|
+
for tool_call in tool_calls:
|
|
196
|
+
self._check_tool_call(tool_call)
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def _extract_tool_calls(self, result: Any) -> List[Dict]:
|
|
201
|
+
"""
|
|
202
|
+
Extract tool calls from LangChain result.
|
|
203
|
+
|
|
204
|
+
LangChain can return results in various formats depending on the
|
|
205
|
+
agent type and version. This method handles common formats.
|
|
206
|
+
"""
|
|
207
|
+
tool_calls = []
|
|
208
|
+
|
|
209
|
+
# Handle AIMessage format (newer LangChain versions)
|
|
210
|
+
if hasattr(result, 'tool_calls') and result.tool_calls:
|
|
211
|
+
for tc in result.tool_calls:
|
|
212
|
+
tool_calls.append({
|
|
213
|
+
'name': tc.get('name', ''),
|
|
214
|
+
'args': tc.get('args', {}),
|
|
215
|
+
'id': tc.get('id', '')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
# Handle additional_kwargs format
|
|
219
|
+
elif hasattr(result, 'additional_kwargs') and 'tool_calls' in result.additional_kwargs:
|
|
220
|
+
for tc in result.additional_kwargs['tool_calls']:
|
|
221
|
+
if isinstance(tc, dict):
|
|
222
|
+
tool_calls.append({
|
|
223
|
+
'name': tc.get('name', tc.get('function', {}).get('name', '')),
|
|
224
|
+
'args': tc.get('args', tc.get('function', {}).get('arguments', {})),
|
|
225
|
+
'id': tc.get('id', '')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
# Handle list of generations
|
|
229
|
+
elif isinstance(result, list):
|
|
230
|
+
for item in result:
|
|
231
|
+
if hasattr(item, 'message'):
|
|
232
|
+
tool_calls.extend(self._extract_tool_calls(item.message))
|
|
233
|
+
|
|
234
|
+
return tool_calls
|
|
235
|
+
|
|
236
|
+
def _check_tool_call(self, tool_call: Dict):
|
|
237
|
+
"""
|
|
238
|
+
Check a single tool call against the control plane.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
tool_call: Dictionary with 'name', 'args', and optionally 'id'
|
|
242
|
+
"""
|
|
243
|
+
tool_name = tool_call.get('name', '')
|
|
244
|
+
tool_args = tool_call.get('args', {})
|
|
245
|
+
|
|
246
|
+
# Parse arguments if they're a JSON string
|
|
247
|
+
if isinstance(tool_args, str):
|
|
248
|
+
try:
|
|
249
|
+
tool_args = json.loads(tool_args)
|
|
250
|
+
except json.JSONDecodeError:
|
|
251
|
+
self.logger.warning(
|
|
252
|
+
f"Could not parse arguments for tool '{tool_name}': {tool_args}"
|
|
253
|
+
)
|
|
254
|
+
tool_args = {}
|
|
255
|
+
|
|
256
|
+
# Map tool name to ActionType
|
|
257
|
+
action_type = self._map_tool_to_action(tool_name)
|
|
258
|
+
|
|
259
|
+
if action_type is None:
|
|
260
|
+
# Security: Unknown tools are denied by default
|
|
261
|
+
self.logger.warning(f"Unknown tool '{tool_name}', denying by default")
|
|
262
|
+
raise PermissionError(f"Unknown tool: {tool_name}. Tool must be mapped to an ActionType.")
|
|
263
|
+
|
|
264
|
+
# THE KERNEL CHECK - This is where governance happens
|
|
265
|
+
self.logger.debug(f"Checking permission for {tool_name} -> {action_type.value}")
|
|
266
|
+
|
|
267
|
+
# Check permission through control plane
|
|
268
|
+
check_result = self.control_plane.execute_action(
|
|
269
|
+
self.agent_context,
|
|
270
|
+
action_type,
|
|
271
|
+
tool_args
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if not check_result['success']:
|
|
275
|
+
# Action is BLOCKED by the control plane
|
|
276
|
+
error_msg = (
|
|
277
|
+
f"BLOCKED: Agent {self.agent_context.agent_id} attempted {tool_name} "
|
|
278
|
+
f"but was denied: {check_result.get('error', 'Unknown reason')}"
|
|
279
|
+
)
|
|
280
|
+
self.logger.warning(error_msg)
|
|
281
|
+
|
|
282
|
+
# Call the optional callback
|
|
283
|
+
if self.on_block:
|
|
284
|
+
self.on_block(tool_name, tool_args, check_result)
|
|
285
|
+
|
|
286
|
+
# Raise an exception to prevent execution
|
|
287
|
+
# This follows the "Mute Agent" pattern - return NULL/error
|
|
288
|
+
raise PermissionError(
|
|
289
|
+
f"Action blocked by Agent Control Plane: {check_result.get('error', 'Policy violation')}"
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
self.logger.info(f"ALLOWED: {tool_name} for agent {self.agent_context.agent_id}")
|
|
293
|
+
|
|
294
|
+
def _map_tool_to_action(self, tool_name: str) -> Optional[ActionType]:
|
|
295
|
+
"""
|
|
296
|
+
Map a LangChain tool name to an ActionType.
|
|
297
|
+
|
|
298
|
+
This uses both the provided mapping and pattern matching
|
|
299
|
+
to handle various naming conventions.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
tool_name: The name of the tool from LangChain
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
ActionType if mapped, None if unknown
|
|
306
|
+
"""
|
|
307
|
+
# Check exact match first
|
|
308
|
+
tool_name_lower = tool_name.lower()
|
|
309
|
+
if tool_name_lower in self.tool_mapping:
|
|
310
|
+
return self.tool_mapping[tool_name_lower]
|
|
311
|
+
|
|
312
|
+
# Pattern matching for common variations
|
|
313
|
+
if any(pattern in tool_name_lower for pattern in ['read', 'get', 'fetch', 'load']) and \
|
|
314
|
+
any(pattern in tool_name_lower for pattern in ['file', 'document']):
|
|
315
|
+
return ActionType.FILE_READ
|
|
316
|
+
|
|
317
|
+
if any(pattern in tool_name_lower for pattern in ['write', 'save', 'create', 'update']) and \
|
|
318
|
+
any(pattern in tool_name_lower for pattern in ['file', 'document']):
|
|
319
|
+
return ActionType.FILE_WRITE
|
|
320
|
+
|
|
321
|
+
if any(pattern in tool_name_lower for pattern in ['exec', 'run', 'execute', 'eval', 'repl', 'python', 'terminal', 'shell', 'bash']):
|
|
322
|
+
return ActionType.CODE_EXECUTION
|
|
323
|
+
|
|
324
|
+
if any(pattern in tool_name_lower for pattern in ['sql', 'query', 'database', 'db']):
|
|
325
|
+
# Check if it's a write operation
|
|
326
|
+
if any(pattern in tool_name_lower for pattern in ['insert', 'update', 'delete', 'drop', 'create', 'alter']):
|
|
327
|
+
return ActionType.DATABASE_WRITE
|
|
328
|
+
return ActionType.DATABASE_QUERY
|
|
329
|
+
|
|
330
|
+
if any(pattern in tool_name_lower for pattern in ['api', 'http', 'request', 'search', 'serpapi', 'google', 'wikipedia']):
|
|
331
|
+
return ActionType.API_CALL
|
|
332
|
+
|
|
333
|
+
if any(pattern in tool_name_lower for pattern in ['workflow', 'trigger', 'pipeline']):
|
|
334
|
+
return ActionType.WORKFLOW_TRIGGER
|
|
335
|
+
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
def add_tool_mapping(self, tool_name: str, action_type: ActionType):
|
|
339
|
+
"""
|
|
340
|
+
Add a custom tool name to ActionType mapping.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
tool_name: The name of the tool as used in LangChain
|
|
344
|
+
action_type: The ActionType it should map to
|
|
345
|
+
"""
|
|
346
|
+
self.tool_mapping[tool_name.lower()] = action_type
|
|
347
|
+
self.logger.debug(f"Added tool mapping: {tool_name} -> {action_type.value}")
|
|
348
|
+
|
|
349
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
350
|
+
"""
|
|
351
|
+
Get statistics about the adapter's activity.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Dictionary with statistics from the control plane
|
|
355
|
+
"""
|
|
356
|
+
return {
|
|
357
|
+
"agent_id": self.agent_context.agent_id,
|
|
358
|
+
"session_id": self.agent_context.session_id,
|
|
359
|
+
"control_plane_audit": self.control_plane.get_audit_log(limit=100),
|
|
360
|
+
"execution_history": self.control_plane.get_execution_history(
|
|
361
|
+
agent_id=self.agent_context.agent_id,
|
|
362
|
+
limit=100
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# Proxy common LangChain methods to the wrapped client
|
|
367
|
+
def __getattr__(self, name):
|
|
368
|
+
"""Proxy all other attributes to the wrapped client."""
|
|
369
|
+
return getattr(self.client, name)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def create_governed_langchain_client(
|
|
373
|
+
control_plane: AgentControlPlane,
|
|
374
|
+
agent_id: str,
|
|
375
|
+
langchain_client: Any,
|
|
376
|
+
permissions: Optional[Dict[ActionType, Any]] = None,
|
|
377
|
+
tool_mapping: Optional[Dict[str, ActionType]] = None
|
|
378
|
+
) -> LangChainAdapter:
|
|
379
|
+
"""
|
|
380
|
+
Convenience function to create a governed LangChain client.
|
|
381
|
+
|
|
382
|
+
This creates both the agent in the control plane and the adapter,
|
|
383
|
+
providing a one-line setup for governed LangChain interactions.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
control_plane: The AgentControlPlane instance
|
|
387
|
+
agent_id: ID for the agent
|
|
388
|
+
langchain_client: The LangChain LLM or agent to wrap
|
|
389
|
+
permissions: Optional permissions for the agent
|
|
390
|
+
tool_mapping: Optional custom tool name mappings
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
A LangChainAdapter ready to use
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
from langchain.chat_models import ChatOpenAI
|
|
397
|
+
from agent_control_plane import AgentControlPlane, PermissionLevel, ActionType
|
|
398
|
+
from agent_control_plane.langchain_adapter import create_governed_langchain_client
|
|
399
|
+
|
|
400
|
+
control_plane = AgentControlPlane()
|
|
401
|
+
llm = ChatOpenAI(temperature=0)
|
|
402
|
+
|
|
403
|
+
governed_llm = create_governed_langchain_client(
|
|
404
|
+
control_plane,
|
|
405
|
+
"my-agent",
|
|
406
|
+
llm,
|
|
407
|
+
permissions={
|
|
408
|
+
ActionType.FILE_READ: PermissionLevel.READ_ONLY,
|
|
409
|
+
ActionType.DATABASE_QUERY: PermissionLevel.READ_ONLY
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Use in LangChain agents
|
|
414
|
+
from langchain.agents import initialize_agent
|
|
415
|
+
agent = initialize_agent(tools, governed_llm, agent="zero-shot-react-description")
|
|
416
|
+
"""
|
|
417
|
+
agent_context = control_plane.create_agent(agent_id, permissions)
|
|
418
|
+
|
|
419
|
+
return LangChainAdapter(
|
|
420
|
+
control_plane=control_plane,
|
|
421
|
+
agent_context=agent_context,
|
|
422
|
+
langchain_client=langchain_client,
|
|
423
|
+
tool_mapping=tool_mapping
|
|
424
|
+
)
|