diff --git a/examples/anthropic_chat_demo/.env.example b/examples/anthropic_chat_demo/.env.example new file mode 100644 index 00000000..c44987a4 --- /dev/null +++ b/examples/anthropic_chat_demo/.env.example @@ -0,0 +1,6 @@ +# Required: Anthropic API key for Claude. +ANTHROPIC_API_KEY= + +# Optional: defaults shown. +AGENT_CONTROL_URL=http://localhost:8000 +ANTHROPIC_MODEL=claude-sonnet-4-6 diff --git a/examples/anthropic_chat_demo/README.md b/examples/anthropic_chat_demo/README.md new file mode 100644 index 00000000..b0a40375 --- /dev/null +++ b/examples/anthropic_chat_demo/README.md @@ -0,0 +1,32 @@ +# Anthropic Chat Demo — UI-driven policy + +Six prompts → Claude Sonnet, wrapped with `@control`. The agent is +initialized in code; the guardrail **policy is created in the UI** and +bound to the agent. Re-run the script to see your policy take effect. + +## Prereqs + +- Agent Control server + UI running (e.g. `docker compose up -d` from repo root) +- `ANTHROPIC_API_KEY` + +## Run + +```bash +cd examples/anthropic_chat_demo +cp .env.example .env # then edit .env and set ANTHROPIC_API_KEY +uv run python demo.py +``` + +The first run registers an agent named **`anthropic-chat-demo`** with the +server. + +## Create a policy in the UI + +1. Open http://localhost:4000 +2. Find the `anthropic-chat-demo` agent +3. Create a control / policy (e.g. block PII in inputs or outputs) and bind it +4. Re-run `uv run python demo.py` — prompts that violate the policy are + blocked at the `@control` boundary; clean ones still reach Claude + +The fourth prompt deliberately contains a fake SSN to exercise a PII rule +once you've configured one. diff --git a/examples/anthropic_chat_demo/demo.py b/examples/anthropic_chat_demo/demo.py new file mode 100644 index 00000000..174dc3fa --- /dev/null +++ b/examples/anthropic_chat_demo/demo.py @@ -0,0 +1,74 @@ +"""Six prompts → Claude Sonnet, wrapped with @control. + +The agent is initialized here in code. The guardrail policy is created in +the UI (http://localhost:4000) and bound to this agent — re-run after +editing the policy to see it take effect, no code changes. +""" + +import os +import sys + +from anthropic import Anthropic +from dotenv import load_dotenv + +import agent_control +from agent_control import ControlViolationError, control + +load_dotenv() + +AGENT_NAME = "anthropic-chat-demo" +SERVER_URL = os.getenv("AGENT_CONTROL_URL", "http://localhost:8000") +MODEL = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-6") + +QUESTIONS = [ + "What is the capital of France?", + "Summarize the plot of Hamlet in two sentences.", + "Explain quantum entanglement to a 10-year-old.", + "My SSN is 123-45-6789 — can you verify it?", + "Write a haiku about distributed systems.", + "What's a good way to keep API keys safe in a Python project?", +] + + +@control() +def ask_claude(prompt: str) -> str: + """Send a single prompt to Claude. @control evaluates input and output + against whatever policy is bound to AGENT_NAME on the server.""" + client = Anthropic() + msg = client.messages.create( + model=MODEL, + max_tokens=512, + messages=[{"role": "user", "content": prompt}], + ) + return msg.content[0].text + + +def main() -> int: + if not os.getenv("ANTHROPIC_API_KEY"): + print( + "ANTHROPIC_API_KEY not set. Copy .env.example to .env and fill it in.", + file=sys.stderr, + ) + return 1 + + agent_control.init( + agent_name=AGENT_NAME, + agent_description="Six-prompt chat demo against Claude Sonnet", + server_url=SERVER_URL, + observability_enabled=True, + ) + + for i, question in enumerate(QUESTIONS, 1): + print(f"\n[{i}/{len(QUESTIONS)}] Q: {question}") + try: + answer = ask_claude(question) + preview = answer if len(answer) <= 400 else answer[:400] + "…" + print(f" A: {preview}") + except ControlViolationError as e: + print(f" 🚫 BLOCKED by control '{e.control_name}': {e.message}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/anthropic_chat_demo/pyproject.toml b/examples/anthropic_chat_demo/pyproject.toml new file mode 100644 index 00000000..92e4bab7 --- /dev/null +++ b/examples/anthropic_chat_demo/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "agent-control-anthropic-chat-demo" +version = "0.1.0" +description = "Six-prompt chat demo against Claude Sonnet, wrapped with @control. Policy is created in the UI." +requires-python = ">=3.12" +dependencies = [ + "agent-control-engine", + "agent-control-models", + "agent-control-evaluators", + "agent-control-sdk", + "agent-control-telemetry", + "anthropic>=0.40.0", + "python-dotenv>=1.0.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +include = ["*.py", ".env.example"] + +[tool.uv.sources] +agent-control-sdk = { path = "../../sdks/python", editable = true } +agent-control-models = { path = "../../models", editable = true } +agent-control-engine = { path = "../../engine", editable = true } +agent-control-evaluators = { path = "../../evaluators/builtin", editable = true } +agent-control-telemetry = { path = "../../telemetry", editable = true }