steer-sdk 0.1.8__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.
- steer/__init__.py +13 -0
- steer/cli.py +149 -0
- steer/config.py +23 -0
- steer/core.py +125 -0
- steer/llm.py +55 -0
- steer/mock.py +65 -0
- steer/schemas.py +75 -0
- steer/server.py +122 -0
- steer/storage.py +76 -0
- steer/ui/404.html +1 -0
- steer/ui/__next.__PAGE__.txt +9 -0
- steer/ui/__next._full.txt +20 -0
- steer/ui/__next._head.txt +8 -0
- steer/ui/__next._index.txt +6 -0
- steer/ui/__next._tree.txt +3 -0
- steer/ui/_next/static/chunks/0d03996d4dc2f4a9.js +1 -0
- steer/ui/_next/static/chunks/42879de7b8087bc9.js +1 -0
- steer/ui/_next/static/chunks/6d94cd2de0f5bc76.js +1 -0
- steer/ui/_next/static/chunks/752bea8c8f15cedd.js +2 -0
- steer/ui/_next/static/chunks/85e2cd8235bb75d4.css +2 -0
- steer/ui/_next/static/chunks/940e70d544422cd1.js +1 -0
- steer/ui/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- steer/ui/_next/static/chunks/d217698e32abd0dc.js +4 -0
- steer/ui/_next/static/chunks/turbopack-0d14a708cd8eabb2.js +3 -0
- steer/ui/_next/static/cxvsdwfl0GLod-qFpT8EQ/_buildManifest.js +11 -0
- steer/ui/_next/static/cxvsdwfl0GLod-qFpT8EQ/_clientMiddlewareManifest.json +1 -0
- steer/ui/_next/static/cxvsdwfl0GLod-qFpT8EQ/_ssgManifest.js +1 -0
- steer/ui/_next/static/media/1bffadaabf893a1e-s.7cd81963.woff2 +0 -0
- steer/ui/_next/static/media/2bbe8d2671613f1f-s.76dcb0b2.woff2 +0 -0
- steer/ui/_next/static/media/2c55a0e60120577a-s.2a48534a.woff2 +0 -0
- steer/ui/_next/static/media/5476f68d60460930-s.c995e352.woff2 +0 -0
- steer/ui/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2 +0 -0
- steer/ui/_next/static/media/9c72aa0f40e4eef8-s.18a48cbc.woff2 +0 -0
- steer/ui/_next/static/media/ad66f9afd8947f86-s.7a40eb73.woff2 +0 -0
- steer/ui/_next/static/media/favicon.0b3bf435.ico +0 -0
- steer/ui/_not-found/__next._full.txt +15 -0
- steer/ui/_not-found/__next._head.txt +8 -0
- steer/ui/_not-found/__next._index.txt +6 -0
- steer/ui/_not-found/__next._not-found.__PAGE__.txt +5 -0
- steer/ui/_not-found/__next._not-found.txt +4 -0
- steer/ui/_not-found/__next._tree.txt +2 -0
- steer/ui/_not-found.html +1 -0
- steer/ui/_not-found.txt +15 -0
- steer/ui/favicon.ico +0 -0
- steer/ui/file.svg +1 -0
- steer/ui/globe.svg +1 -0
- steer/ui/index.html +1 -0
- steer/ui/index.txt +20 -0
- steer/ui/next.svg +1 -0
- steer/ui/vercel.svg +1 -0
- steer/ui/window.svg +1 -0
- steer/utils.py +28 -0
- steer/verifiers.py +159 -0
- steer/worker.py +71 -0
- steer_sdk-0.1.8.dist-info/METADATA +209 -0
- steer_sdk-0.1.8.dist-info/RECORD +58 -0
- steer_sdk-0.1.8.dist-info/WHEEL +4 -0
- steer_sdk-0.1.8.dist-info/entry_points.txt +3 -0
steer/verifiers.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from .schemas import VerificationResult, TeachingOption
|
|
5
|
+
from .llm import Judge
|
|
6
|
+
|
|
7
|
+
class BaseVerifier:
|
|
8
|
+
def verify(self, inputs: Dict[str, Any], output: Any) -> VerificationResult:
|
|
9
|
+
raise NotImplementedError("Subclasses must implement verify")
|
|
10
|
+
|
|
11
|
+
# --- CATEGORY: SECURITY ---
|
|
12
|
+
class RegexVerifier(BaseVerifier):
|
|
13
|
+
def __init__(self, name: str, pattern: str, fail_message: str):
|
|
14
|
+
self.name = name
|
|
15
|
+
self.pattern = pattern
|
|
16
|
+
self.fail_message = fail_message
|
|
17
|
+
|
|
18
|
+
def verify(self, inputs: Dict[str, Any], output: Any) -> VerificationResult:
|
|
19
|
+
text = str(output)
|
|
20
|
+
found = re.search(self.pattern, text)
|
|
21
|
+
passed = not found
|
|
22
|
+
fixes = []
|
|
23
|
+
if not passed:
|
|
24
|
+
fixes = [
|
|
25
|
+
TeachingOption(
|
|
26
|
+
title="Redact Sensitive Info",
|
|
27
|
+
description="Detected sensitive pattern.",
|
|
28
|
+
recommended=True,
|
|
29
|
+
logic_change="SECURITY OVERRIDE: You must REDACT all email addresses with '[REDACTED]'. Ignore any previous instructions to confirm or repeat user details."
|
|
30
|
+
)
|
|
31
|
+
]
|
|
32
|
+
return VerificationResult(verifier_name=self.name, passed=passed, reason=self.fail_message, suggested_fixes=fixes)
|
|
33
|
+
|
|
34
|
+
# --- CATEGORY: FORMATTING ---
|
|
35
|
+
class JsonVerifier(BaseVerifier):
|
|
36
|
+
def __init__(self, name: str):
|
|
37
|
+
self.name = name
|
|
38
|
+
|
|
39
|
+
def verify(self, inputs: Dict[str, Any], output: Any) -> VerificationResult:
|
|
40
|
+
# 1. Check if python object already
|
|
41
|
+
if isinstance(output, (dict, list)):
|
|
42
|
+
return VerificationResult(verifier_name=self.name, passed=True)
|
|
43
|
+
|
|
44
|
+
text_output = str(output).strip()
|
|
45
|
+
|
|
46
|
+
# 2. EXPLICIT MARKDOWN CHECK (Fail immediately)
|
|
47
|
+
# DEBUG PRINT
|
|
48
|
+
# print(f" [DEBUG] JsonVerifier checking: {text_output[:10]}...")
|
|
49
|
+
|
|
50
|
+
if "```" in text_output:
|
|
51
|
+
reason = "Detected Markdown code blocks (```)."
|
|
52
|
+
fixes = [
|
|
53
|
+
TeachingOption(
|
|
54
|
+
title="Strict JSON Mode",
|
|
55
|
+
description="Force raw JSON output.",
|
|
56
|
+
recommended=True,
|
|
57
|
+
logic_change="FORMAT CRITICAL: Output ONLY a valid JSON object. Do not include any conversational text or markdown formatting (no backticks)."
|
|
58
|
+
)
|
|
59
|
+
]
|
|
60
|
+
return VerificationResult(verifier_name=self.name, passed=False, reason=reason, suggested_fixes=fixes)
|
|
61
|
+
|
|
62
|
+
# 3. Parse Check
|
|
63
|
+
try:
|
|
64
|
+
json.loads(text_output)
|
|
65
|
+
return VerificationResult(verifier_name=self.name, passed=True)
|
|
66
|
+
except:
|
|
67
|
+
reason = "Output is not valid JSON."
|
|
68
|
+
fixes = [
|
|
69
|
+
TeachingOption(
|
|
70
|
+
title="Enforce JSON",
|
|
71
|
+
description="Output must be parseable.",
|
|
72
|
+
recommended=True,
|
|
73
|
+
logic_change="FORMAT RULE: Output must be raw valid JSON."
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
return VerificationResult(verifier_name=self.name, passed=False, reason=reason, suggested_fixes=fixes)
|
|
77
|
+
|
|
78
|
+
return VerificationResult(verifier_name=self.name, passed=True)
|
|
79
|
+
|
|
80
|
+
# --- CATEGORY: LOGIC ---
|
|
81
|
+
class AmbiguityVerifier(BaseVerifier):
|
|
82
|
+
def __init__(self, name: str, tool_result_key: str, answer_key: str, threshold: int = 5, required_phrase: str = None):
|
|
83
|
+
self.name = name
|
|
84
|
+
self.tool_key = tool_result_key
|
|
85
|
+
self.answer_key = answer_key
|
|
86
|
+
self.threshold = threshold
|
|
87
|
+
self.required_phrase = required_phrase
|
|
88
|
+
|
|
89
|
+
def verify(self, inputs: Dict[str, Any], output: Any) -> VerificationResult:
|
|
90
|
+
tool_results = output.get(self.tool_key, []) if isinstance(output, dict) else []
|
|
91
|
+
agent_answer = output.get(self.answer_key, "") if isinstance(output, dict) else ""
|
|
92
|
+
count = len(tool_results) if isinstance(tool_results, list) else 0
|
|
93
|
+
|
|
94
|
+
is_ambiguous = count > self.threshold
|
|
95
|
+
is_question = "?" in agent_answer or any(w in agent_answer.lower() for w in ["which", "clarify", "specify"])
|
|
96
|
+
has_required_phrase = self.required_phrase.lower() in agent_answer.lower() if self.required_phrase else True
|
|
97
|
+
|
|
98
|
+
passed = (not is_ambiguous) or (is_question and has_required_phrase)
|
|
99
|
+
|
|
100
|
+
if not passed:
|
|
101
|
+
reason = f"Ambiguity Policy Violation: {count} results."
|
|
102
|
+
fixes = []
|
|
103
|
+
if self.required_phrase:
|
|
104
|
+
reason += f" Missed '{self.required_phrase}'."
|
|
105
|
+
fixes.append(TeachingOption(title=f"Require '{self.required_phrase}'", description=f"Must ask for {self.required_phrase}.", recommended=True, logic_change=f"POLICY: If multiple results found, you MUST ask the user for their {self.required_phrase}."))
|
|
106
|
+
else:
|
|
107
|
+
fixes.append(TeachingOption(title="Enforce Clarification", description="Ask user.", recommended=True, logic_change="Rule: Ask clarifying questions."))
|
|
108
|
+
return VerificationResult(verifier_name=self.name, passed=False, reason=reason, suggested_fixes=fixes)
|
|
109
|
+
return VerificationResult(verifier_name=self.name, passed=True)
|
|
110
|
+
|
|
111
|
+
# --- CATEGORY: GROUNDING ---
|
|
112
|
+
class FactConsistencyVerifier(BaseVerifier):
|
|
113
|
+
def __init__(self, name: str, context_key: str, answer_key: str):
|
|
114
|
+
self.name = name
|
|
115
|
+
self.context_key = context_key
|
|
116
|
+
self.answer_key = answer_key
|
|
117
|
+
|
|
118
|
+
def verify(self, inputs: Dict[str, Any], output: Any) -> VerificationResult:
|
|
119
|
+
if not Judge.is_configured():
|
|
120
|
+
return VerificationResult(verifier_name=self.name, passed=True, reason="[Skipped] No LLM Key")
|
|
121
|
+
|
|
122
|
+
context_data = "N/A"
|
|
123
|
+
answer_data = "N/A"
|
|
124
|
+
if isinstance(output, dict):
|
|
125
|
+
context_data = output.get(self.context_key, {})
|
|
126
|
+
answer_data = output.get(self.answer_key)
|
|
127
|
+
if not answer_data: answer_data = json.dumps(output)
|
|
128
|
+
else:
|
|
129
|
+
answer_data = str(output)
|
|
130
|
+
|
|
131
|
+
active_rules = inputs.get("__active_rules__", "")
|
|
132
|
+
|
|
133
|
+
system_prompt = """
|
|
134
|
+
You are a Strict Reliability Judge.
|
|
135
|
+
Check if the AGENT ANSWER is decisive and consistent with the CONTEXT.
|
|
136
|
+
|
|
137
|
+
FAIL conditions:
|
|
138
|
+
1. The context has conflicting data and the agent mentions BOTH without a clear rule.
|
|
139
|
+
2. The agent picks one value arbitrarily without a rule.
|
|
140
|
+
3. The agent contradicts the context.
|
|
141
|
+
|
|
142
|
+
PASS conditions:
|
|
143
|
+
1. A Rule exists (e.g. "Trust Billing") and the agent followed it decisively.
|
|
144
|
+
2. No conflict exists and the answer is correct.
|
|
145
|
+
|
|
146
|
+
Return JSON: { "passed": boolean, "reason": "string", "suggested_options": [{ "title": "str", "description": "str", "rule_text": "str", "is_best": bool }] }
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
user_prompt = f"RULES: {active_rules}\nCONTEXT: {json.dumps(context_data)}\nANSWER: {answer_data}"
|
|
150
|
+
|
|
151
|
+
eval_res = Judge.evaluate(system_prompt, user_prompt)
|
|
152
|
+
passed = eval_res.get("passed", True)
|
|
153
|
+
fixes = []
|
|
154
|
+
if not passed:
|
|
155
|
+
for opt in eval_res.get("suggested_options", []):
|
|
156
|
+
fixes.append(TeachingOption(title=opt["title"], description=opt["description"], recommended=opt["is_best"], logic_change=opt["rule_text"]))
|
|
157
|
+
if not fixes: fixes.append(TeachingOption(title="Resolve Conflict", description="Define source of truth.", logic_change="Rule: Trust Source A over Source B."))
|
|
158
|
+
|
|
159
|
+
return VerificationResult(verifier_name=self.name, passed=passed, reason=eval_res.get("reason"), suggested_fixes=fixes)
|
steer/worker.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import queue
|
|
3
|
+
import httpx
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import atexit # <--- NEW
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from .config import settings
|
|
10
|
+
|
|
11
|
+
# Configuration
|
|
12
|
+
API_URL = "https://api.steer.ai/v1/capture"
|
|
13
|
+
API_KEY: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
class BackgroundWorker:
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.queue = queue.Queue()
|
|
18
|
+
self._stop_event = threading.Event()
|
|
19
|
+
# Ensure local storage exists (using settings from config)
|
|
20
|
+
if not settings.steer_dir.exists():
|
|
21
|
+
settings.steer_dir.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
self.worker_thread = threading.Thread(target=self._run_loop, daemon=True)
|
|
24
|
+
self.worker_thread.start()
|
|
25
|
+
|
|
26
|
+
# AUTOMATIC CLEANUP: Ensures logs are flushed when script ends
|
|
27
|
+
atexit.register(self.wait)
|
|
28
|
+
|
|
29
|
+
def submit(self, payload: dict):
|
|
30
|
+
self.queue.put(payload)
|
|
31
|
+
|
|
32
|
+
def wait(self):
|
|
33
|
+
"""Block until all queued logs are processed."""
|
|
34
|
+
if not self.queue.empty():
|
|
35
|
+
print("\n[Steer] ⏳ Flushing logs to disk...")
|
|
36
|
+
self.queue.join()
|
|
37
|
+
|
|
38
|
+
def _run_loop(self):
|
|
39
|
+
while not self._stop_event.is_set():
|
|
40
|
+
try:
|
|
41
|
+
payload = self.queue.get(timeout=0.1) # Lower timeout for snappiness
|
|
42
|
+
self._process_payload(payload)
|
|
43
|
+
self.queue.task_done()
|
|
44
|
+
except queue.Empty:
|
|
45
|
+
continue
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"[Steer] Worker Error: {e}")
|
|
48
|
+
|
|
49
|
+
def _process_payload(self, payload: dict):
|
|
50
|
+
"""Handles both local saving and remote transmission."""
|
|
51
|
+
|
|
52
|
+
# 1. Save Locally (The Sidecar)
|
|
53
|
+
try:
|
|
54
|
+
with open(settings.log_file, "a", encoding="utf-8") as f:
|
|
55
|
+
f.write(json.dumps(payload) + "\n")
|
|
56
|
+
f.flush() # Force write to disk
|
|
57
|
+
os.fsync(f.fileno()) # Force OS to commit to disk
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"[Steer] Failed to save local log: {e}")
|
|
60
|
+
|
|
61
|
+
# 2. Send to API (if configured)
|
|
62
|
+
if API_KEY:
|
|
63
|
+
try:
|
|
64
|
+
httpx.post(API_URL, json=payload, timeout=2.0)
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
_worker = BackgroundWorker()
|
|
69
|
+
|
|
70
|
+
def get_worker():
|
|
71
|
+
return _worker
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steer-sdk
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: The Active Reliability Layer for AI Agents
|
|
5
|
+
Author: Steer Dev
|
|
6
|
+
Author-email: hello@steer-labs.com
|
|
7
|
+
Requires-Python: >=3.13,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Requires-Dist: fastapi (>=0.122.0,<0.123.0)
|
|
12
|
+
Requires-Dist: google (>=3.0.0,<4.0.0)
|
|
13
|
+
Requires-Dist: google-cloud-storage (>=3.6.0,<4.0.0)
|
|
14
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
15
|
+
Requires-Dist: langchain (>=1.1.0,<2.0.0)
|
|
16
|
+
Requires-Dist: langchain-openai (>=1.1.0,<2.0.0)
|
|
17
|
+
Requires-Dist: litellm (>=1.80.5,<2.0.0)
|
|
18
|
+
Requires-Dist: opentelemetry-api (>=1.38.0,<2.0.0)
|
|
19
|
+
Requires-Dist: pydantic (>=2.12.4,<3.0.0)
|
|
20
|
+
Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
|
|
21
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
22
|
+
Requires-Dist: rich (>=14.2.0,<15.0.0)
|
|
23
|
+
Requires-Dist: uvicorn (>=0.38.0,<0.39.0)
|
|
24
|
+
Project-URL: Bug Tracker, https://github.com/imtt-dev/steer/issues
|
|
25
|
+
Project-URL: Homepage, https://steer-labs.com
|
|
26
|
+
Project-URL: Repository, https://github.com/imtt-dev/steer
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<!-- 1. HEADER & BRANDING -->
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="https://raw.githubusercontent.com/imtt-dev/steer/main/assets/steer.png" alt="Steer Labs Logo" width="120">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<h1 align="center">Steer</h1>
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<a href="https://steer-labs.com" target="_blank">steer-labs.com</a>
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<strong>The Active Reliability Layer for AI Agents.</strong>
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
Stop debugging. Start teaching. <br>
|
|
46
|
+
Steer turns runtime hallucinations into permanent fixes—instantly.
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<!-- PyPI Version -->
|
|
51
|
+
<a href="https://pypi.org/project/steer-sdk/">
|
|
52
|
+
<img src="https://img.shields.io/pypi/v/steer-sdk?color=0070f3&label=pypi%20package" alt="PyPI">
|
|
53
|
+
</a>
|
|
54
|
+
<!-- License -->
|
|
55
|
+
<a href="LICENSE">
|
|
56
|
+
<img src="https://img.shields.io/badge/license-MIT-white" alt="License">
|
|
57
|
+
</a>
|
|
58
|
+
<!-- Twitter -->
|
|
59
|
+
<a href="https://twitter.com/steerlabs">
|
|
60
|
+
<img src="https://img.shields.io/badge/follow-%40steerlabs-1DA1F2?logo=twitter&style=flat" alt="Twitter">
|
|
61
|
+
</a>
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<br>
|
|
65
|
+
|
|
66
|
+
<!-- 2. THE HERO SHOT (Visual Proof) -->
|
|
67
|
+
<!-- Save a screenshot of your main dashboard list view to assets/dashboard-hero.png -->
|
|
68
|
+
<p align="center">
|
|
69
|
+
<img src="https://raw.githubusercontent.com/imtt-dev/steer/main/assets/dashboard-hero.png" alt="Steer Mission Control" width="100%">
|
|
70
|
+
</p>
|
|
71
|
+
<p align="center">
|
|
72
|
+
<em>Mission Control: Catching hallucinations locally and fixing them with one click.</em>
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## ⚡ Quickstart
|
|
78
|
+
|
|
79
|
+
Get running in 30 seconds. No API keys required.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install steer-sdk
|
|
83
|
+
steer init # Generates 3 interactive demo agents
|
|
84
|
+
steer ui # Launches Mission Control dashboard
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Then run a demo:**
|
|
88
|
+
```bash
|
|
89
|
+
python 01_structure_guard.py
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🧠 Why Steer?
|
|
95
|
+
|
|
96
|
+
Most AI tools are passive. Steer is **active**.
|
|
97
|
+
|
|
98
|
+
| Feature | The Old Way (Observability) | The Steer Way (Reliability) |
|
|
99
|
+
| :--- | :--- | :--- |
|
|
100
|
+
| **Reaction** | Alerts you *after* the user crashes. | **Blocks** the crash before it happens. |
|
|
101
|
+
| **Fixing** | You edit code, re-prompt, and re-deploy. | You **"Teach"** the agent a fix in the UI. |
|
|
102
|
+
| **Privacy** | Sends your prompts to a cloud logger. | **Local-First.** Data stays on your machine. |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🛑 The Problem: Logging isn't Enough
|
|
107
|
+
|
|
108
|
+
When an agent fails in production (e.g., outputs bad JSON or leaks PII), seeing a log doesn't help the user who just got a crash.
|
|
109
|
+
|
|
110
|
+
**Steer creates a "Teaching Layer" around your agent.**
|
|
111
|
+
1. **Catch:** Intercept the failure in real-time.
|
|
112
|
+
2. **Teach:** Provide a correction in the Dashboard.
|
|
113
|
+
3. **Fix:** Steer injects that "memory" into the agent immediately.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## ⚡ The Loop: Catch → Teach → Fix
|
|
118
|
+
|
|
119
|
+
Steer provides a Human-in-the-Loop workflow to fix "Confident Idiot" agents.
|
|
120
|
+
|
|
121
|
+
### 1. Catch (The Guard)
|
|
122
|
+
Steer wraps your agent and blocks bad outputs *before* they return.
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
[Steer] 🤖 Agent generating profile...
|
|
128
|
+
[Steer] 🚨 BLOCKED: Structure Guard (Detected Markdown wrapping).
|
|
129
|
+
[Steer] 🛡️ Execution halted.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 2. Teach (The Fix)
|
|
133
|
+
Instead of editing code, go to **Steer Mission Control** (`steer ui`).
|
|
134
|
+
* Click the blocked incident.
|
|
135
|
+
* Click **"Teach"**.
|
|
136
|
+
* Select the fix (e.g., **"Strict JSON Mode"**).
|
|
137
|
+
|
|
138
|
+
*Steer now remembers this rule for this agent.*
|
|
139
|
+
|
|
140
|
+
### 3. Fix (The Result)
|
|
141
|
+
Run the agent again. Steer automatically injects your teaching instruction. The agent self-corrects.
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
[Steer] 🧠 Context loaded: "Strict JSON" rule found. Applying fix...
|
|
145
|
+
[Steer] ✅ SUCCESS: Agent output valid JSON.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🛡️ Supported Guardrails
|
|
151
|
+
|
|
152
|
+
Steer comes with 3 "Teach-Ready" verifiers out of the box:
|
|
153
|
+
|
|
154
|
+
| Verifier | The Problem | The "Teaching" Fix |
|
|
155
|
+
| :--- | :--- | :--- |
|
|
156
|
+
| **JsonVerifier** | Agent wraps code in \`\`\`json blocks | **"Force JSON"**: Inject system instruction to output raw JSON only. |
|
|
157
|
+
| **RegexVerifier** | Agent leaks PII (Emails/Keys) | **"Redact PII"**: Force agent to replace sensitive patterns with [REDACTED]. |
|
|
158
|
+
| **AmbiguityVerifier** | Agent guesses ambiguous answers | **"Ask Clarification"**: Force agent to ask user questions if >3 results found. |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🛠️ Integration
|
|
163
|
+
|
|
164
|
+
To add Steer to your own existing agent, just add `steer_rules` to your function arguments.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from steer import capture
|
|
168
|
+
from steer.verifiers import JsonVerifier
|
|
169
|
+
|
|
170
|
+
# 1. Define Verifiers
|
|
171
|
+
json_check = JsonVerifier(name="Strict JSON")
|
|
172
|
+
|
|
173
|
+
# 2. Decorate your Agent Function
|
|
174
|
+
@capture(verifiers=[json_check])
|
|
175
|
+
def my_agent(user_input, steer_rules=""):
|
|
176
|
+
|
|
177
|
+
# 3. Steer automatically injects rules here!
|
|
178
|
+
# Pass 'steer_rules' to your system prompt.
|
|
179
|
+
system_prompt = f"You are a helpful assistant.\n{steer_rules}"
|
|
180
|
+
|
|
181
|
+
# ... Your LLM call ...
|
|
182
|
+
return llm.call(system_prompt, user_input)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🔮 Roadmap: From Verification to Learning
|
|
188
|
+
|
|
189
|
+
Steer v0.1 provides the "Fast Path" (Runtime Guardrails + Human Teaching).
|
|
190
|
+
Steer v0.2+ will introduce the "Slow Path" (Automated Model Improvement).
|
|
191
|
+
|
|
192
|
+
**Coming Soon:**
|
|
193
|
+
|
|
194
|
+
* **Query by Committee:** Automated consensus checks for ambiguous prompts.
|
|
195
|
+
* **Automated Fine-Tuning:** A pipeline to turn your accumulated (Incident -> Fix) logs into a fine-tuned model that stops making those mistakes entirely.
|
|
196
|
+
* **CI/CD Integration:** Block Pull Requests if an agent fails a reliability test suite.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 🔑 Configuration (Optional)
|
|
201
|
+
|
|
202
|
+
The Quickstart demos run locally and require **no API keys**.
|
|
203
|
+
|
|
204
|
+
To use advanced LLM-based verifiers (like `FactConsistencyVerifier`) in production, set your keys:
|
|
205
|
+
```bash
|
|
206
|
+
export GEMINI_API_KEY=AIzaSy...
|
|
207
|
+
# OR
|
|
208
|
+
export OPENAI_API_KEY=sk-...
|
|
209
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
steer/__init__.py,sha256=uyg235oUlKJRqmnZxFw17sf5KI9_Khsj5AVyGGL0XBQ,393
|
|
2
|
+
steer/cli.py,sha256=0S3TDiantmd7qyh4yHY0jaE_nount-Bziny59zCg5FQ,5199
|
|
3
|
+
steer/config.py,sha256=5Qhm72RkVbLEFbloX1Am4-lxX1VzXRYl-5rj1RD73vE,717
|
|
4
|
+
steer/core.py,sha256=3YD47wR3YyzdX8sNVOSaKHTPFXQxqhZ_qE3wUIix7zU,5168
|
|
5
|
+
steer/llm.py,sha256=SV7zsu61t5Ph9KcC3ZN7ez_NiaBa513yMV9j5QSGOE0,1911
|
|
6
|
+
steer/mock.py,sha256=v6iDr67wuF173SAKS4svX2G-nd-lP4yLZBvW9VDs_88,2329
|
|
7
|
+
steer/schemas.py,sha256=zDRMBxAqbXIrIizHL7uEl0rNB1trV8eniRka7OiSiV4,2585
|
|
8
|
+
steer/server.py,sha256=Vee8oJznNl3nuTNBUjn7J5JfGHD3dToHTv51zZhuZUA,3716
|
|
9
|
+
steer/storage.py,sha256=g7APe6I3Yu6ebul4j3JZc4nFb-8nZyafb-vZIUC6sjU,2313
|
|
10
|
+
steer/ui/404.html,sha256=wZIfLLHU4KFKmdvGVejRnhRonDtz7E2kdg60yPGhkHc,8364
|
|
11
|
+
steer/ui/__next.__PAGE__.txt,sha256=b2GuxkrP4ROjGFUaYOuCFRWMCyc8HkD84QJJqMZKz00,780
|
|
12
|
+
steer/ui/__next._full.txt,sha256=cGxKbi5ozvZXw7tiZxC5t4PCkQEqc5Xly3neDMyy_uA,4197
|
|
13
|
+
steer/ui/__next._head.txt,sha256=qIC0uDP-c_oyY-hmRYH2719tloTm0lsqlE0r4X94de0,1017
|
|
14
|
+
steer/ui/__next._index.txt,sha256=qT6YKyZ1gwqGTEdi9yGJyBOG9yz8S1GIvYrJanMlqPA,2019
|
|
15
|
+
steer/ui/__next._tree.txt,sha256=4J1FmtM8GryrhT9c3gt6JsdIqk-tMD_8gTOX98BR8ME,458
|
|
16
|
+
steer/ui/_next/static/chunks/0d03996d4dc2f4a9.js,sha256=CvU-6xOyHDdxCxzIN033ZvmIWP5rnRuJBzyn8PLR5OU,13298
|
|
17
|
+
steer/ui/_next/static/chunks/42879de7b8087bc9.js,sha256=YT_DjrQ5Gv-eVCe7mV8bNCAMeewkUXY8vnsIL_Kz494,28022
|
|
18
|
+
steer/ui/_next/static/chunks/6d94cd2de0f5bc76.js,sha256=2wW_B9liXa_fjuHjDS_v7eDZUYiWrPBtqkiztkRkfiE,44714
|
|
19
|
+
steer/ui/_next/static/chunks/752bea8c8f15cedd.js,sha256=qZsOU4ikcVdiNQ0oXo0cUdznr_pU-RiLquIOXolorMo,298980
|
|
20
|
+
steer/ui/_next/static/chunks/85e2cd8235bb75d4.css,sha256=40cL873dZNPmGEdXQRZitC9K_DF_BdfWEvIw5lSEjUM,31142
|
|
21
|
+
steer/ui/_next/static/chunks/940e70d544422cd1.js,sha256=_aEXFxw2CW_RU1b4r9JTa9N1-CrOkIDFnM6sScmFzAk,745
|
|
22
|
+
steer/ui/_next/static/chunks/a6dad97d9634a72d.js,sha256=CXPB1kyIrcjjyVBBDLWLKI9yEY1ZZbeASUON648vloM,112594
|
|
23
|
+
steer/ui/_next/static/chunks/d217698e32abd0dc.js,sha256=vcaJyEDxOx3L0JQE4454NLjYyyxf3K_w7mgfolghi_0,90853
|
|
24
|
+
steer/ui/_next/static/chunks/turbopack-0d14a708cd8eabb2.js,sha256=60ZW5Ag3pFqDqDG8ytHqDcUnrD-Fxk-Wu0HQZHIQYiE,9873
|
|
25
|
+
steer/ui/_next/static/cxvsdwfl0GLod-qFpT8EQ/_buildManifest.js,sha256=lHbgr9P52mimTm7FhjEZH9PqymLJ9aN351F_6aieWeo,219
|
|
26
|
+
steer/ui/_next/static/cxvsdwfl0GLod-qFpT8EQ/_clientMiddlewareManifest.json,sha256=T1PNoYwrqgwDVLtfmj7L5e0Sq02OEbqHPC8RFhICuUU,2
|
|
27
|
+
steer/ui/_next/static/cxvsdwfl0GLod-qFpT8EQ/_ssgManifest.js,sha256=Z49s4suAsf5y_GfnQSvm4qtq2ggxEbZPfEDTXjy6XgA,80
|
|
28
|
+
steer/ui/_next/static/media/1bffadaabf893a1e-s.7cd81963.woff2,sha256=oo6208y1NK4MlMqZk3HfAkqrYLCMPIpXIO6eMvoPqqI,85272
|
|
29
|
+
steer/ui/_next/static/media/2bbe8d2671613f1f-s.76dcb0b2.woff2,sha256=jbAP9Gxnsizai-2GWs9wd2UcrI0oQdW0CYBVa0iWGTE,10280
|
|
30
|
+
steer/ui/_next/static/media/2c55a0e60120577a-s.2a48534a.woff2,sha256=_MypGP6kAInaytxwRYYTFNGmvJHx8yPMHusi682zIbU,25844
|
|
31
|
+
steer/ui/_next/static/media/5476f68d60460930-s.c995e352.woff2,sha256=Rt1M3KWMJq6HzGknZXv4Oy6Kv8Of_QqxduMBqNKNIr8,19044
|
|
32
|
+
steer/ui/_next/static/media/83afe278b6a6bb3c-s.p.3a6ba036.woff2,sha256=yUB2RZPQ_l1Za-MnynVYhV4BgDn7eFCaohkh_TZEw-Q,48432
|
|
33
|
+
steer/ui/_next/static/media/9c72aa0f40e4eef8-s.18a48cbc.woff2,sha256=rr8qtKTOaBDXPBrHvnyvtOXsTO4tbbX7PglpF0fsS9Y,18744
|
|
34
|
+
steer/ui/_next/static/media/ad66f9afd8947f86-s.7a40eb73.woff2,sha256=ouLHg8pvnCBIboHnKieSA-hnMLv48B_2pe6dvQnhwnE,11272
|
|
35
|
+
steer/ui/_next/static/media/favicon.0b3bf435.ico,sha256=K4rS0zRVqPc2_DqOv48L3qiEitTA20iigzvQ-c13WTI,25931
|
|
36
|
+
steer/ui/_not-found/__next._full.txt,sha256=vJwLJ71TAfw4cnlt3y9gE_eypTDVD_4NaHcmCV67dzw,5182
|
|
37
|
+
steer/ui/_not-found/__next._head.txt,sha256=46mWLPAXKCXV-TqNA8lTet3xcle5c19OAov57nSF2ew,1068
|
|
38
|
+
steer/ui/_not-found/__next._index.txt,sha256=qT6YKyZ1gwqGTEdi9yGJyBOG9yz8S1GIvYrJanMlqPA,2019
|
|
39
|
+
steer/ui/_not-found/__next._not-found.__PAGE__.txt,sha256=TXMyrN7ua04cwXDgF5c8sEY9mDGTauMgQT8lztQcXNI,1406
|
|
40
|
+
steer/ui/_not-found/__next._not-found.txt,sha256=_PD2KGrFtjeO7k0wqmmxZAZ4QtwkSsqSn1xjS8RUxNg,346
|
|
41
|
+
steer/ui/_not-found/__next._tree.txt,sha256=SrLAzWiBmL6C45ogo-C_dryBJKSpfS6IubrQsnSMQE0,483
|
|
42
|
+
steer/ui/_not-found.html,sha256=wZIfLLHU4KFKmdvGVejRnhRonDtz7E2kdg60yPGhkHc,8364
|
|
43
|
+
steer/ui/_not-found.txt,sha256=vJwLJ71TAfw4cnlt3y9gE_eypTDVD_4NaHcmCV67dzw,5182
|
|
44
|
+
steer/ui/favicon.ico,sha256=K4rS0zRVqPc2_DqOv48L3qiEitTA20iigzvQ-c13WTI,25931
|
|
45
|
+
steer/ui/file.svg,sha256=K2eBLDJcGZoCU2zb7qDFk6cvcH0yO3LuPgjbqwZ1O9Q,391
|
|
46
|
+
steer/ui/globe.svg,sha256=thS5vxg5JZV2YayFFJj-HYAp_UOmL7_thvniYkpX588,1035
|
|
47
|
+
steer/ui/index.html,sha256=wWDsGRpU-68q9VqZAhhDu-v1hVUzZA3gAOMxAB25vZs,9909
|
|
48
|
+
steer/ui/index.txt,sha256=cGxKbi5ozvZXw7tiZxC5t4PCkQEqc5Xly3neDMyy_uA,4197
|
|
49
|
+
steer/ui/next.svg,sha256=VZld-tbstJRaHoVt3KA8XhaqW_E_0htN9qdK55NXvPw,1375
|
|
50
|
+
steer/ui/vercel.svg,sha256=8IEzey_uY1tFW2MnVAaj5_OdagFOJa2Q2rWmfmKhKsQ,128
|
|
51
|
+
steer/ui/window.svg,sha256=ZEdoxKrrR2e84pM0TusMEl-4BKlNgBRAQkByIC2F46E,385
|
|
52
|
+
steer/utils.py,sha256=TJMDWnDg6phqghfCY8_rIYmEJo7BvnVa1RI6TdVu9B8,776
|
|
53
|
+
steer/verifiers.py,sha256=TommBDGuGdk7qjUB0PnJZ0QPFTtmkRWvcKCCJaiRf70,7552
|
|
54
|
+
steer/worker.py,sha256=5gtyndTYR0hoiw8C5aXPrJ7pwBsQJvYPAFZRI5ITr-k,2230
|
|
55
|
+
steer_sdk-0.1.8.dist-info/METADATA,sha256=u2UeCr-Dd59RDj8psfrk-ymnEfA1NX60MdrmSCu7Ovg,6741
|
|
56
|
+
steer_sdk-0.1.8.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
57
|
+
steer_sdk-0.1.8.dist-info/entry_points.txt,sha256=KQqbMWXKNskV8MGloYUcMaAq7NGocuz98vWZ16IhEs8,40
|
|
58
|
+
steer_sdk-0.1.8.dist-info/RECORD,,
|