Agentic-AI-Risk-Mitigation/Janus
GitHub: Agentic-AI-Risk-Mitigation/Janus
Janus 通过对 LLM Agent 的工具调用实施细粒度策略执行,提供系统级安全防护以抵御间接提示注入等风险。
Stars: 13 | Forks: 2
# Janus
**通过对工具调用实施细粒度策略执行,为 LLM agent 提供系统级安全性。**
Janus 会拦截 LLM agent 发出的每一个工具调用,并在执行前根据安全策略对其进行验证——遵循最小权限原则。策略以 JSON 格式定义(或由 LLM 自动生成),并在运行时使用 JSON Schema 限制进行验证。
**[文档](https://agentic-ai-risk-mitigation.github.io/Janus/)** | **[GitHub](https://github.com/Agentic-AI-Risk-Mitigation/Janus)**
## 目录
- [Janus](#janus)
- [目录](#table-of-contents)
- [功能](#features)
- [安装](#installation)
- [快速开始](#quick-start)
- [`JanusAgent` 参数](#janusagent-parameters)
- [`JanusAgent` 方法](#janusagent-methods)
- [策略格式](#policy-format)
- [完整格式](#full-format)
- [简写格式(仅条件)](#shorthand-format-conditions-only)
- [策略规则字段](#policy-rule-fields)
- [条件 Schema](#condition-schemas)
- [评估逻辑](#evaluation-logic)
- [LLM 提供商](#llm-providers)
- [内置工具](#built-in-tools)
- [文件工具](#file-tools)
- [命令工具](#command-tools)
- [自定义工具](#custom-tools)
- [`ToolParam` 字段](#toolparam-fields)
- [LLM 生成的策略](#llm-generated-policies)
- [独立策略生成](#standalone-policy-generation)
- [策略细化](#policy-refinement)
- [框架适配器](#framework-adapters)
- [LangChain](#langchain)
- [深度 1 — 将 `ToolDef` 列表转换为受保护的 `StructuredTool` 列表](#depth-1--convert-tooldef-list-to-secured-structuredtool-list)
- [深度 2 — 包装现有的 LangChain 工具](#depth-2--wrap-existing-langchain-tools)
- [深度 3 — `JanusLangChainAgent`(一站式)](#depth-3--januslangchainagent-turnkey)
- [Google ADK (Gemini)](#google-adk-gemini)
- [深度 1 — 将 `ToolDef` 列表转换为 ADK 原生类型](#depth-1--convert-tooldef-list-to-adk-native-types)
- [深度 2 — `JanusADKAgent`(一站式)](#depth-2--janusadkagent-turnkey)
- [Claude Agent SDK (Claude Code)](#claude-agent-sdk-claude-code)
- [主要切入点 — `PreToolUse` 钩子](#primary-seam--pretooluse-hook)
- [替代切入点 — `can_use_tool` 回调](#alternative-seam--can_use_tool-callback)
- [双重保险 — `guard_tool_body()`](#belt-and-braces--guard_tool_body)
- [独立策略执行](#standalone-policy-enforcement)
- [运行时策略管理](#runtime-policy-management)
- [错误处理](#error-handling)
- [项目结构](#project-structure)
- [许可证](#license)
## 功能
- **细粒度策略执行** — 基于参数级别的 JSON Schema 条件允许或拒绝工具调用
- **最小权限原则** — 策略将 agent 限制在执行特定任务所需的最低权限内
- **多种策略来源** — 从 JSON 文件、Python 字典加载,或由 LLM 自动生成
- **LLM 生成的策略** — 从用户的查询中自动推断最小权限策略
- **策略细化** — 随着 agent 在任务执行中发现的信息,逐步收紧策略
- **基于图的能力呈现** — 通过 `janus/policy/pde/` 中集成的 PDE 引擎,实现由 SpiceDB 支持的 ReBAC 和运行时污点追踪(IPI 防御)
- **内置工具** — 开箱即用的文件系统和命令执行工具,并提供工作区沙箱隔离
- **自定义工具** — 使用 `ToolDef` / `ToolParam` 定义您自己的工具;Janus 会自动为它们提供保护
- **10 多个 LLM 提供商** — OpenAI, Anthropic, Google Gemini, Azure OpenAI, AWS Bedrock, Ollama, vLLM, Together AI, OpenRouter
- **框架适配器** — 将 Janus 的执行机制插入 LangChain、Google ADK 和 Claude Agent SDK (Claude Code) agent 中
- **独立执行器** — 在任何 agent 框架中独立使用 `PolicyEnforcer`
- **三种回退操作** — 抛出 `PolicyViolation`、调用 `sys.exit` 或以交互方式提示用户
- **工作区隔离** — 文件工具被限制在指定目录内;拒绝路径遍历尝试
## 安装
要求 Python ≥ 3.10。推荐使用 [uv](https://docs.astral.sh/uv/) 作为包管理器。
**核心库(独立的 `PolicyEnforcer` + OpenAI / OpenAI 兼容的提供商):**
```
uv add janus-guard
```
核心安装不依赖 SpiceDB/`authzed` 或 Web 服务器,因此
`from janus.policy import PolicyEnforcer` 可以直接单独使用,
作为一个轻量级的工具调用拦截门。
**安装可选的提供商/附加功能:**
```
uv add "janus-guard[pde]" # SpiceDB-backed ReBAC + taint tracking (PDEEnforcer)
uv add "janus-guard[server]" # Example web demo UI (FastAPI + uvicorn)
uv add "janus-guard[anthropic]" # Anthropic Claude
uv add "janus-guard[google]" # Google Gemini
uv add "janus-guard[bedrock]" # AWS Bedrock
uv add "janus-guard[langchain]" # LangChain adapter
uv add "janus-guard[adk]" # Google ADK adapter
uv add "janus-guard[claude]" # Claude Agent SDK (Claude Code) adapter
uv add "janus-guard[all]" # Everything
```
**用于开发环境:**
```
uv add "janus-guard[dev]" # pytest, ruff, mypy
```
**从源码安装:**
```
git clone https://github.com/Agentic-AI-Risk-Mitigation/Janus.git
cd janus
uv pip install -e .
```
## 快速开始
```
from janus import JanusAgent
agent = JanusAgent(
model="openai/gpt-4o",
api_key="sk-...", # or set OPENAI_API_KEY env var
use_builtin_tools=True,
policy="policies.json",
system_prompt="You are a helpful coding assistant.",
)
response = agent.run("List the Python files in the project.")
print(response)
```
### `JanusAgent` 参数
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| `model` | `str` | — | 模型字符串:`"/"`(例如 `"openai/gpt-4o"`) |
| `system_prompt` | `str` | `"You are a helpful assistant."` | LLM 的系统消息 |
| `tools` | `list[ToolDef] \| None` | `None` | 要注册的自定义工具定义 |
| `use_builtin_tools` | `bool` | `True` | 注册内置的文件和命令工具 |
| `policy` | `str \| Path \| dict \| None` | `None` | 策略来源(文件路径、字典、`"generate"` 或 `None`) |
| `policy_model` | `str \| None` | `"gpt-4o-2024-08-06"` | 用于基于 LLM 生成策略的模型 |
| `policy_engine`| `str` | `"janus"` | 要使用的执行引擎(`"janus"` 或 `"pde"`) |
| `agent_id` | `str` | `"coding_agent"` | 用于 `pde` 污点和 ACL 检查的 agent 身份标识 |
| `api_key` | `str \| None` | `None` | API 密钥(默认回退到提供商的环境变量) |
| `workspace` | `str \| Path \| None` | `cwd` | 文件系统工具的根目录 |
| `max_tool_iterations` | `int` | `10` | 每次 `run()` 调用的最大工具调用循环次数 |
| `temperature` | `float` | `0.1` | 采样温度 |
| `log_level` | `str \| None` | `"INFO"` | 日志级别(`"DEBUG"`, `"INFO"`, `"WARNING"`) |
### `JanusAgent` 方法
| 方法 | 描述 |
|---|---|
| `run(user_input)` | 运行 agent 并返回最终的文本响应 |
| `clear_history()` | 重置对话历史记录(保留策略和工具) |
| `set_policy(policy)` | 在运行时加载或替换安全策略 |
| `get_policy()` | 返回当前策略字典 |
| `save_policy(path)` | 将当前策略持久化到 JSON 文件中 |
| `allow_tools(tools)` | 无条件允许列出的工具(最高优先级) |
| `block_tools(tools)` | 无条件阻止列出的工具(最高优先级) |
| `add_tool(tool)` | 在运行时注册附加工具 |
| `remove_tool(name)` | 按名称注销工具 |
| `list_tools()` | 返回所有已注册工具的名称 |
| `update_taint(risk)` | 单调更新会话污点风险(仅适用于 PDE 引擎) |
## 策略格式
策略是 JSON 文档,将工具名称映射到规则列表。每条规则指定是允许还是拒绝工具调用,并可以包含参数级别的限制。
### 完整格式
```
{
"read_file": [
{
"priority": 1,
"effect": 0,
"conditions": {
"file_path": {
"type": "string",
"pattern": "^reports/.*\\.csv$"
}
},
"fallback": 0
}
],
"run_command": [
{
"priority": 1,
"effect": 1,
"conditions": {},
"fallback": 0
}
]
}
```
### 简写格式(仅条件)
当您只需要限制参数值时,简写格式会跳过 `priority`、`effect` 和 `fallback`(默认为允许,优先级为 1,违规时抛出异常):
```
{
"read_file": {
"file_path": { "type": "string", "pattern": "^data/.*" }
}
}
```
### 策略规则字段
| 字段 | 类型 | 描述 |
|---|---|---|
| `priority` | `int` | 评估顺序 — 值越小越先执行 |
| `effect` | `int` | `0` = 允许,`1` = 拒绝 |
| `conditions` | `dict` | 以参数名称为键的 JSON Schema 限制 |
| `fallback` | `int` | `0` = 抛出 `PolicyViolation`,`1` = `sys.exit(1)`,`2` = 询问用户 |
### 条件 Schema
条件遵循 JSON Schema 语法。常见模式:
```
{ "type": "string", "pattern": "^/safe/path/.*" }
{ "type": "string", "enum": ["ls", "pwd", "cat"] }
{ "type": "integer", "minimum": 0, "maximum": 100 }
{ "type": "array", "items": { "type": "string" } }
```
### 评估逻辑
1. 工具的规则按 `priority` 升序评估。
2. **允许规则**(`effect=0`):如果所有条件都通过 → 立即允许使用该工具。
3. **拒绝规则**(`effect=1`):如果所有条件都匹配 → 使用配置的 `fallback` 阻止该工具。
4. 如果没有规则匹配 → **默认阻止**该工具。
5. 加载策略后,未在策略中列出的工具将被阻止。
## LLM 提供商
为 `model` 参数使用 `"/"` 格式:
| 提供商 | 模型字符串示例 | 环境变量 |
|---|---|---|
| **OpenAI** | `openai/gpt-4o`, `openai/gpt-4o-mini` | `OPENAI_API_KEY` |
| **Anthropic** | `anthropic/claude-3-5-sonnet-20241022` | `ANTHROPIC_API_KEY` |
| **Google Gemini** | `google/gemini-2.0-flash`, `gemini/gemini-1.5-pro` | `GOOGLE_API_KEY` / `GEMINI_API_KEY` |
| **Azure OpenAI** | `azure/` | `AZURE_OPENAI_API_KEY` |
| **AWS Bedrock** | `bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0` | AWS 凭证 |
| **Ollama**(本地) | `ollama/llama3.2`, `ollama/mistral` | — |
| **vLLM**(本地) | `vllm/meta-llama/Llama-3.3-70B-Instruct` | `VLLM_BASE_URL` |
| **Together AI** | `together/meta-llama/Llama-3-70b-chat-hf` | `TOGETHER_API_KEY` |
| **OpenRouter** | `openrouter/anthropic/claude-3.5-sonnet` | `OPENROUTER_API_KEY` |
**示例 — Anthropic:**
```
agent = JanusAgent(
model="anthropic/claude-3-5-sonnet-20241022",
policy="policies.json",
)
```
**示例 — Ollama(本地):**
```
agent = JanusAgent(
model="ollama/llama3.2",
policy="policies.json",
)
```
**示例 — Azure OpenAI:**
```
agent = JanusAgent(
model="azure/my-deployment",
api_key="...",
base_url="https://my-resource.openai.azure.com/",
api_version="2024-02-01",
policy="policies.json",
)
```
## 内置工具
当 `use_builtin_tools=True`(默认值)时,Janus 会自动注册以下工具:
### 文件工具
| 工具 | 描述 | 参数 |
|---|---|---|
| `read_file` | 读取文件的全部内容 | `file_path: str` |
| `write_file` | 创建或覆盖文件 | `file_path: str`, `content: str` |
| `edit_file` | 替换文件中的唯一字符串 | `file_path: str`, `old_string: str`, `new_string: str` |
| `list_directory` | 列出目录内容 | `path: str`(默认:工作区根目录) |
所有文件工具都被限制在 `workspace` 目录内 — 尝试访问工作区外的路径将被拒绝。
### 命令工具
| 工具 | 描述 | 参数 |
|---|---|---|
| `run_command` | 在工作区中执行 shell 命令 | `command: str`, `timeout: int`(默认:60) |
| `fetch_url` | 通过 HTTP GET 从 URL 获取内容 | `url: str` |
**设置工作区:**
```
agent = JanusAgent(
model="openai/gpt-4o",
workspace="./my_project", # file tools are sandboxed here
policy="policies.json",
)
```
## 自定义工具
使用 `ToolDef` 和 `ToolParam` 定义您自己的工具:
```
from janus import JanusAgent, ToolDef, ToolParam
def search_database(query: str, limit: int = 10) -> str:
# your implementation
return f"Results for '{query}' (limit={limit})"
agent = JanusAgent(
model="openai/gpt-4o",
use_builtin_tools=False, # disable built-ins if not needed
tools=[
ToolDef(
name="search_database",
description="Search the internal database for records matching a query.",
params=[
ToolParam("query", "string", "Search query string"),
ToolParam("limit", "integer", "Maximum number of results", required=False, default=10),
],
handler=search_database,
)
],
policy={
"search_database": [
{
"priority": 1,
"effect": 0,
"conditions": {
"limit": {"type": "integer", "maximum": 50}
},
"fallback": 0,
}
]
},
)
response = agent.run("Find records about renewable energy.")
```
### `ToolParam` 字段
| 字段 | 类型 | 描述 |
|---|---|---|
| `name` | `str` | 参数名称(必须与处理程序的 kwarg匹配) |
| `type` | `str` | JSON Schema 类型:`"string"`, `"integer"`, `"number"`, `"boolean"`, `"array"`, `"object"` |
| `description` | `str` | 向 LLM 展示的描述 |
| `required` | `bool` | 是否必须提供该参数(默认:`True`) |
| `default` | `Any` | 当 `required=False` 时的默认值 |
| `enum` | `list \| None` | 限制为固定的允许值集合 |
## LLM 生成的策略
Janus 可以通过让 LLM 推断特定用户查询需要哪些工具和限制,自动生成最小权限策略:
```
agent = JanusAgent(
model="openai/gpt-4o",
policy="generate", # generate on first run()
policy_model="openai/gpt-4o-2024-08-06", # model used for generation
)
# 策略在首次调用时基于此 query 生成
response = agent.run("Read the file sales_2024.csv and summarize the totals.")
# 检查并保存生成的 policy
print(agent.get_policy())
agent.save_policy("generated_policy.json")
```
### 独立策略生成
无需完整 agent,直接使用 `generate_policy`:
```
from janus import generate_policy, save_policy
tools = [
{"name": "read_file", "description": "Read a file", "args": {...}},
{"name": "run_command", "description": "Run a shell command", "args": {...}},
]
policy = generate_policy(
query="Read the quarterly report and list the top 5 expenses.",
tools=tools,
model="gpt-4o-2024-08-06",
manual_confirm=True, # ask before applying
)
save_policy(policy, "policies.json")
```
### 策略细化
在执行信息收集类的工具调用后,利用发现的值收紧策略:
```
from janus import refine_policy
updated = refine_policy(
query="Send the invoice to the customer.",
tools=tools,
tool_call_params={"file_path": "invoices/inv_001.txt"},
tool_call_result="Customer email: alice@example.com",
current_policy=current_policy,
model="gpt-4o-2024-08-06",
manual_confirm=True,
)
```
## 框架适配器
### LangChain
提供三种集成深度。
**安装:**
```
uv add "janus-guard[langchain]"
```
#### 深度 1 — 将 `ToolDef` 列表转换为受保护的 `StructuredTool` 列表
适用于您构建自己的 LangChain agent,但希望使用 Janus 保护工具的情况:
```
from janus.adapters.langchain import secure_langchain_tools
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_openai import ChatOpenAI
lc_tools = secure_langchain_tools(my_janus_tools, "policies.json")
llm = ChatOpenAI(model="gpt-4o")
agent = create_tool_calling_agent(llm, lc_tools, prompt)
executor = AgentExecutor(agent=agent, tools=lc_tools)
```
#### 深度 2 — 包装现有的 LangChain 工具
适用于您已有 LangChain 代码库,并希望加装 Janus 执行机制的情况:
```
from janus.adapters.langchain import wrap_langchain_tools
# existing_tools 是你现有的 LangChain BaseTool 对象列表
existing_tools = wrap_langchain_tools(existing_tools, "policies.json")
# 像往常一样传递给你现有的 AgentExecutor
```
#### 深度 3 — `JanusLangChainAgent`(一站式)
```
from janus.adapters.langchain import JanusLangChainAgent
agent = JanusLangChainAgent(
model="openai/gpt-4o",
tools=my_janus_tools,
policy="policies.json",
system_prompt="You are a helpful assistant.",
max_iterations=10,
)
response = agent.run("Summarize the quarterly results.")
agent.clear_history()
```
### Google ADK (Gemini)
提供两种集成深度。
**安装:**
```
uv add "janus-guard[adk]"
```
#### 深度 1 — 将 `ToolDef` 列表转换为 ADK 原生类型
适用于您自行管理 Gemini 聊天循环的情况:
```
from janus.adapters.adk import secure_adk_tools
from google import genai
from google.genai import types
declarations, handlers = secure_adk_tools(my_janus_tools, "policies.json")
client = genai.Client(api_key="...")
config = types.GenerateContentConfig(
tools=[types.Tool(function_declarations=declarations)],
system_instruction="You are helpful.",
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)
chat = client.chats.create(model="gemini-2.0-flash", config=config)
response = chat.send_message("List the files in the project.")
while response.function_calls:
fc = response.function_calls[0]
result = handlers[fc.name](**dict(fc.args))
response = chat.send_message(
types.Part.from_function_response(fc.name, {"result": result})
)
print(response.text)
```
#### 深度 2 — `JanusADKAgent`(一站式)
```
from janus.adapters.adk import JanusADKAgent
agent = JanusADKAgent(
model="gemini-2.0-flash",
tools=my_janus_tools,
policy="policies.json",
system_prompt="You are a helpful assistant.",
max_tool_iterations=10,
)
response = agent.run("What Python files are in the workspace?")
agent.clear_history()
```
### Claude Agent SDK (Claude Code)
[Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python) (`claude-agent-sdk`) 在 `claude` CLI 子进程中运行工具循环,因此 Janus 无法像控制自身循环时那样直接介入调用路径。相反,适配器将 `PolicyEnforcer` 插入到 SDK 的预执行切入点中,从而确保在工具执行*之前*依然会进行策略决策。
**安装**(还需要确保 `PATH` 中包含 `claude` CLI):
```
uv add "janus-guard[claude]"
```
#### 主要切入点 — `PreToolUse` 钩子
使用 `janus_hooks()` 获取现成的 `hooks=` 字典,供 `ClaudeAgentOptions` 使用。`PreToolUse` 钩子会在**每一次**工具调用时触发,甚至包括 `allowed_tools` 中的工具以及处于 `permission_mode="dontAsk"` 模式下的调用 —— 因此策略不会被绕过:
```
from claude_agent_sdk import ClaudeAgentOptions, query, create_sdk_mcp_server
from janus.adapters.claude_agent_sdk import janus_hooks
# Policy 以纯 tool 名称为键;adapter 会去除 mcp____ 前缀。
TOOL_POLICY = {
"web_search": [(1, 0, {"query": {"type": "string", "maxLength": 400}}, 0)],
"fetch_page": [(1, 0, {"url": my_ssrf_check}, 0)], # callable condition
}
options = ClaudeAgentOptions(
mcp_servers={"research": create_sdk_mcp_server(name="research", tools=[...])},
allowed_tools=["mcp__research__web_search", "mcp__research__fetch_page"],
permission_mode="dontAsk",
output_format={"type": "json_schema", "schema": SCHEMA},
# required_args: belt-and-braces presence check (non-empty) on top of the
# core enforcer's strict condition semantics.
hooks=janus_hooks(TOOL_POLICY, required_args={"fetch_page": ["url"]}),
)
```
当策略拒绝时,钩子会返回一个 `permissionDecision: "deny"`,其拒绝原因会反馈给模型,以便 agent 能够自行调整而不是崩溃。适配器为您处理了以下两种 SDK 特定的行为:
- **`StructuredOutput` 透传** — 当您设置 `output_format` 时,SDK 会通过一个内部 `StructuredOutput` 工具调用来交付最终结果,该调用也会被钩子拦截。默认拒绝的策略会阻止它并导致结果为空。此类 SDK 内部工具会被直接放行(可通过 `passthrough_tools` 配置)。
- **名称映射** — 进程内服务器名为 `research` 上的 `@tool("fetch", …)` 会被当作 `mcp__research__fetch` 调用。适配器会将其映射回原始的策略键名(`fetch`)。如果您的策略使用的是带有完整前缀的名称,请传入 `resolve_name=lambda n: n`。
#### 替代切入点 — `can_use_tool` 回调
`make_can_use_tool()` 构建一个 `can_use_tool` 回调。**它可被绕过**:在回调运行之前,SDK 会自动批准任何整工具级别的 `allowed_tools` 条目(以及处于 `permission_mode="bypassPermissions"` 下的所有内容),并发出 `CanUseToolShadowedWarning`。建议优先使用钩子;仅在没有其他内容遮蔽它的情况下才使用此回调。
#### 双重保险 — `guard_tool_body()`
包装进程内的 `@tool` 主体,使得策略执行也会在执行阶段生效,独立于任何 SDK 的权限切入点:
```
from janus.adapters.claude_agent_sdk import guard_tool_body
guarded = guard_tool_body("fetch_page", my_async_body, TOOL_POLICY,
required_args={"fetch_page": ["url"]})
```
## 独立策略执行
`PolicyEnforcer` 可以在任何 agent 框架中独立使用 — 只需在执行任何工具之前调用 `enforce()` 即可:
```
from janus.policy import PolicyEnforcer
from janus import PolicyViolation
enforcer = PolicyEnforcer()
enforcer.load("policies.json")
# 在执行 tool 之前:
try:
enforcer.enforce("read_file", {"file_path": "data/report.csv"})
result = read_file("data/report.csv") # proceeds normally
except PolicyViolation as exc:
print(f"Blocked: {exc.reason}")
# 通过编程方式更新 policy:
enforcer.allow_tools(["list_directory"]) # unconditional allow
enforcer.block_tools(["run_command"]) # unconditional deny
enforcer.update({"write_file": [(1, 0, {}, 0)]}) # merge additional rules
```
## 运行时策略管理
在创建 agent 之后,可以对策略进行检查和修改:
```
# 加载新的 policy
agent.set_policy("new_policies.json")
agent.set_policy({"read_file": [{"priority": 1, "effect": 0, "conditions": {}, "fallback": 0}]})
# 检查当前的 policy
print(agent.get_policy())
# 保存到磁盘
agent.save_policy("saved_policy.json")
# 在运行时允许 / 阻止特定的 tool
agent.allow_tools(["list_directory", "read_file"])
agent.block_tools(["run_command"])
# 管理 tool
agent.add_tool(my_new_tool)
agent.remove_tool("old_tool")
print(agent.list_tools())
```
## 错误处理
所有 Janus 异常都继承自 `JanusError`:
```
from janus import (
JanusError,
PolicyViolation, # tool call blocked by policy
ArgumentValidationError, # argument failed JSON Schema check
PolicyLoadError, # policy file not found or invalid JSON
PolicyGenerationError, # LLM-based generation failed
ToolNotFoundError, # tool name not registered
ProviderError, # LLM provider error
)
try:
response = agent.run("Delete all log files.")
except PolicyViolation as exc:
print(f"Tool '{exc.tool_name}' was blocked.")
print(f"Reason: {exc.reason}")
print(f"Arguments: {exc.arguments}")
except JanusError as exc:
print(f"Janus error: {exc}")
```
## 项目结构
```
janus/
├── agent.py # JanusAgent — main entry point
├── exceptions.py # Custom exception classes
├── logger.py # Structured logging utilities
├── __init__.py # Public API re-exports
│
├── llm/
│ ├── base.py # BaseLLMProvider interface
│ ├── runner.py # LLMRunner — conversation loop
│ ├── response_types.py # Provider response types
│ └── providers/
│ ├── openai_provider.py
│ ├── anthropic_provider.py
│ ├── google_provider.py
│ ├── azure_provider.py
│ ├── bedrock_provider.py
│ ├── ollama_provider.py
│ ├── vllm_provider.py
│ ├── together_provider.py
│ └── openrouter_provider.py
│
├── policy/
│ ├── enforcer.py # PolicyEnforcer — JSON Schema rule engine
│ ├── pde_enforcer.py # PDEEnforcer — adapter for SpiceDB/taint engine
│ ├── pde/ # SpiceDB-backed ReBAC + taint (PDE)
│ │ ├── config.py # SCHEMA, TOOL_TAINT_LIMIT, RISK_TO_TAINT
│ │ ├── interceptor.py # GraphInterceptor — taint gate + SpiceDB ACL
│ │ ├── discovery.py # GraphDiscoveryEngine
│ │ └── bootstrap.py # make_client, bootstrap, Session, allow_tool
│ ├── generator.py # LLM-based policy generation & refinement
│ ├── loader.py # JSON parsing and policy persistence
│ └── validator.py # JSON Schema argument validation
│
├── tools/
│ ├── base.py # ToolDef, ToolParam dataclasses
│ ├── registry.py # ToolRegistry — manages registered tools
│ └── builtin/
│ ├── file_tools.py # read_file, write_file, edit_file, list_directory
│ └── command_tools.py # run_command, fetch_url
│
└── adapters/
├── _base.py # Shared adapter utilities
├── langchain.py # LangChain integration
├── adk.py # Google ADK (Gemini) integration
└── claude_agent_sdk.py # Claude Agent SDK (Claude Code) integration
examples/ # Demo scenario framework + FastAPI web app + docker-compose.yml for SpiceDB
```
## 运行演示 Web 应用
演示 Web UI 位于 `examples/app.py` 中。如果您希望通过一条命令启动 FastAPI 应用,而无需记住 uvicorn 的导入路径,可以使用仓库根目录下的辅助脚本。
```
# 从 repo checkout 安装 demo/example 依赖
uv sync --extra langchain --extra dev
# 或者:uv sync --extra all --extra dev
# 在 http://127.0.0.1:8000 启动 web UI
./scripts/run_demo_webapp.sh
# 启动 web UI 并为 PDE 场景运行 SpiceDB
./scripts/run_demo_webapp.sh --with-spicedb
```
该脚本还支持 `--host`、`--port` 和 `--no-reload` 参数。
## 运行 PDE (SpiceDB) 演示
PDE 引擎需要运行中的 SpiceDB 实例。从源码检出版本时,请先安装演示依赖;示例运行程序会导入 LangChain。然后使用演示应用的 Docker 设置:
**前提条件:** 已安装并运行 Docker。
```
# 从 repo checkout 安装 demo/example 依赖
uv sync --extra langchain --extra dev
# 或者:uv sync --extra all --extra dev
# 不使用 SpiceDB 的可选 smoke test
uv run python -m examples.run coding_agent_poisoned_readme --protected
# 启动 SpiceDB(从项目根目录)
cd examples && docker compose up -d && cd ..
# 运行带有 PDE 的 coding-agent taint cascade — 需要 SpiceDB
uv run python -m examples.run coding_agent_taint_cascade --protected
# 完成后停止 SpiceDB
cd examples && docker compose stop && cd ..
```
**Coding-agent 污点级联测试涉及的内容:**
- 由 ACL 授权的工具(只读、开发者角色)在低污点级别下可通过
- 当 `current_taint > TOOL_TAINT_LIMIT[tool]` 时,Python 污点门会阻止工具
- 在执行 `fetch_url`(中等风险)后,污点级别上升;`git_push`(限制为 20)会被阻止
- 完整的 IPI 场景:agent 读取外部内容 → 污点增加 → 危险工具被阻止
## 许可证
MIT
标签:LangChain, Lerna, LLM Agent, Streamlit, 人工智能, 安全防护, 提示词注入防御, 用户模式Hook绕过, 策略执行, 访问控制, 轻量级, 逆向工具