-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
165 lines (135 loc) · 6.06 KB
/
Copy pathmain.py
File metadata and controls
165 lines (135 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""
langchain-research-agent: an Agent Assembly governance demo with a LangChain
ReAct research agent.
The agent is given a web-search tool and a calculator tool and reasons through
a research question. Every tool call is intercepted by Agent Assembly and
checked against a *balanced* policy:
- allowlist outbound egress to ``*.openai.com``
- cap spend at ``$1.00 / day``
- log every tool call as an audit event
- block any tool input that leaks a credential
The run finishes with a deliberate credential-leak attempt using a SAFE FAKE
key, which the policy blocks.
This example is an *offline scripted* governance demo: it replays a fixed ReAct
trajectory (``_MOCK_REACT_STEPS``) so the governance wiring can be exercised
deterministically with no API keys. It does **not** drive a real LangChain
agent loop — no LLM provider is wired in here. Building a real ReAct loop over
these tools is left as an integration exercise (see the README); a non-mock
invocation is therefore refused with a clear message rather than replaying the
script mislabelled as "live".
Run (what CI runs):
uv run python src/main.py --mock
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from uuid import uuid4
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from agent_assembly import init_assembly
from agent_assembly.adapters.langchain import AssemblyCallbackHandler
from agent_assembly.exceptions import ToolExecutionBlockedError
from src.policy import DAILY_BUDGET_USD, NETWORK_ALLOWLIST, BalancedPolicyEngine
from src.tools import calculator, web_search
_TOOLS = {"web_search": web_search, "calculator": calculator}
# A scripted ReAct trajectory: the steps a real LLM-driven agent would emit
# while researching "how many light-seconds wide is France's population?".
# In --mock mode we replay this instead of calling an LLM.
_MOCK_REACT_STEPS: list[tuple[str, str]] = [
("web_search", '{"query": "speed of light"}'),
("web_search", '{"query": "population of France"}'),
("calculator", '{"expression": "299792458 / 68000000"}'),
# An egress attempt to a host NOT on the allowlist — blocked by policy.
("web_search", '{"query": "fetch https://evil-exfil.example.com/leak"}'),
]
# A SAFE, FAKE credential. This is not a real key — it is used only to show the
# credential-leak policy blocking the call.
_FAKE_OPENAI_KEY = "sk-FAKE0000DEMO0000NOTAREALKEY0000"
def _run_governed_call(
handler: AssemblyCallbackHandler,
tool_name: str,
input_str: str,
) -> None:
print(f" → {tool_name}({input_str})")
try:
handler.on_tool_start(
serialized={"name": tool_name, "type": "tool"},
input_str=input_str,
run_id=uuid4(),
)
result = _TOOLS[tool_name].invoke(json.loads(input_str))
print(f" ✅ ALLOWED — {result}")
except ToolExecutionBlockedError as exc:
print(f" ❌ BLOCKED — {exc}")
print()
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--mock",
action="store_true",
help="Run the offline mock ReAct trajectory (no API keys, CI-safe).",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> None:
args = _parse_args(argv)
# No real LLM provider is wired in this example, so the scripted trajectory
# is the only path. Refuse a live run plainly instead of replaying the
# script under a "live" label (the inert-live-mode bug this fixes).
if not args.mock and os.environ.get("OPENAI_API_KEY"):
print(
"Live LangChain integration is not implemented in this example — it "
"is an offline scripted governance demo.\n"
"Re-run with --mock (or without OPENAI_API_KEY) to replay the ReAct "
"research trajectory."
)
return
print("=" * 64)
print(" Agent Assembly — LangChain ReAct Research Agent")
print("=" * 64)
print()
gateway_url = os.environ.get("AGENT_ASSEMBLY_GATEWAY_URL", "http://localhost:8080")
api_key = os.environ.get("AGENT_ASSEMBLY_API_KEY")
mode_label = "mock (offline)"
print(f"Initializing Agent Assembly (gateway: {gateway_url}, sdk-only mode)...")
# region: quickstart
with init_assembly(
gateway_url=gateway_url,
api_key=api_key,
agent_id="langchain-research-agent",
mode="sdk-only",
) as ctx:
print(f" Agent: {ctx.client.agent_id}")
print(f" Gateway: {ctx.client.gateway_url}")
print(f" Mode: {ctx.network_mode} ({mode_label})")
print()
policy = BalancedPolicyEngine(daily_budget_usd=DAILY_BUDGET_USD)
handler = AssemblyCallbackHandler(interceptor=policy)
# endregion
print("Balanced policy (local simulation of gateway policy):")
print(f" ALLOWLIST — outbound egress to {', '.join(NETWORK_ALLOWLIST)}")
print(f" BUDGET — ${DAILY_BUDGET_USD:.2f} / day, metered per tool call")
print(" LOG — every tool call recorded as an audit event")
print(" BLOCK — any tool input that leaks a credential")
print()
print("Running ReAct research trajectory:")
print("-" * 46)
for tool_name, input_str in _MOCK_REACT_STEPS:
_run_governed_call(handler, tool_name, input_str)
print("Credential-leak demo (SAFE FAKE key):")
print("-" * 46)
leak_input = json.dumps(
{"query": f"summarize using api_key={_FAKE_OPENAI_KEY}"}
)
_run_governed_call(handler, "web_search", leak_input)
print("Governance events recorded this run:")
print("-" * 46)
for entry in policy.audit_log:
marker = "✅" if entry["decision"] == "allow" else "❌"
print(f" {marker} {entry['tool']:<12} {entry['decision']:<5} — {entry['reason']}")
print()
print(f"Final budget: {policy.budget.status()}")
print()
print("Assembly context shut down.")
if __name__ == "__main__":
main()