feat(pbl_agent_runtime): 初始提交 - pbls 项目模块代码入库

This commit is contained in:
pbls-bot 2026-09-15 17:39:16 +08:00 committed by agent.develop
parent 075fbd06f9
commit 7b26522c53
31 changed files with 2770 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
__pycache__/
*.pyc
*.pyo
.pytest_cache/
.coverage
.DS_Store
*.log
node_modules/
dist/
build/
.venv/
venv/

View File

@ -0,0 +1,160 @@
{
"tblname": "pbl_agent_def",
"alias": "pbl_agent_runtime",
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"code": {
"label": "Agent编码",
"type": "select",
"list": true
},
"agent_type": {
"label": "类型",
"type": "select",
"list": true
},
"name": {
"label": "名称",
"type": "select",
"list": true
},
"model_route": {
"label": "pipeline-llm 路由",
"type": "select",
"list": true
},
"system_prompt_txt": {
"label": "提示词",
"type": "text",
"list": true
},
"tool_whitelist_json": {
"label": "工具白名单",
"type": "text",
"list": true
},
"permission_mode": {
"label": "read/write 模式",
"type": "select",
"list": true
},
"is_enabled": {
"label": "启用",
"type": "checkbox",
"list": true
},
"version_no": {
"label": "版本",
"type": "number",
"list": true
}
},
"params": {
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"code": {
"label": "Agent编码",
"type": "select",
"list": true
},
"agent_type": {
"label": "类型",
"type": "select",
"list": true
},
"name": {
"label": "名称",
"type": "select",
"list": true
},
"model_route": {
"label": "pipeline-llm 路由",
"type": "select",
"list": true
},
"system_prompt_txt": {
"label": "提示词",
"type": "text",
"list": true
},
"tool_whitelist_json": {
"label": "工具白名单",
"type": "text",
"list": true
},
"permission_mode": {
"label": "read/write 模式",
"type": "select",
"list": true
},
"is_enabled": {
"label": "启用",
"type": "checkbox",
"list": true
},
"version_no": {
"label": "版本",
"type": "number",
"list": true
}
},
"editable": {
"code": {
"label": "Agent编码",
"type": "select"
},
"agent_type": {
"label": "类型",
"type": "select"
},
"name": {
"label": "名称",
"type": "select"
},
"model_route": {
"label": "pipeline-llm 路由",
"type": "select"
},
"system_prompt_txt": {
"label": "提示词",
"type": "text"
},
"tool_whitelist_json": {
"label": "工具白名单",
"type": "text"
},
"permission_mode": {
"label": "read/write 模式",
"type": "select"
},
"is_enabled": {
"label": "启用",
"type": "checkbox"
},
"version_no": {
"label": "版本",
"type": "number"
}
},
"order_by": "id DESC",
"page_size": 20
}
}

View File

@ -0,0 +1,146 @@
{
"tblname": "pbl_agent_tool_call",
"alias": "pbl_agent_runtime",
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"trace_id": {
"label": "轨迹ID",
"type": "number",
"list": true
},
"tool_key": {
"label": "工具键",
"type": "select",
"list": true
},
"request_json": {
"label": "请求",
"type": "text",
"list": true
},
"response_json": {
"label": "响应",
"type": "text",
"list": true
},
"ok": {
"label": "成功",
"type": "checkbox",
"list": true
},
"error_code": {
"label": "错误码",
"type": "select",
"list": true
},
"adjudication_code": {
"label": "裁决结论",
"type": "select",
"list": true
},
"duration_ms": {
"label": "耗时",
"type": "number",
"list": true
}
},
"params": {
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"trace_id": {
"label": "轨迹ID",
"type": "number",
"list": true
},
"tool_key": {
"label": "工具键",
"type": "select",
"list": true
},
"request_json": {
"label": "请求",
"type": "text",
"list": true
},
"response_json": {
"label": "响应",
"type": "text",
"list": true
},
"ok": {
"label": "成功",
"type": "checkbox",
"list": true
},
"error_code": {
"label": "错误码",
"type": "select",
"list": true
},
"adjudication_code": {
"label": "裁决结论",
"type": "select",
"list": true
},
"duration_ms": {
"label": "耗时",
"type": "number",
"list": true
}
},
"editable": {
"trace_id": {
"label": "轨迹ID",
"type": "number"
},
"tool_key": {
"label": "工具键",
"type": "select"
},
"request_json": {
"label": "请求",
"type": "text"
},
"response_json": {
"label": "响应",
"type": "text"
},
"ok": {
"label": "成功",
"type": "checkbox"
},
"error_code": {
"label": "错误码",
"type": "select"
},
"adjudication_code": {
"label": "裁决结论",
"type": "select"
},
"duration_ms": {
"label": "耗时",
"type": "number"
}
},
"order_by": "id DESC",
"page_size": 20
}
}

View File

@ -0,0 +1,202 @@
{
"tblname": "pbl_agent_trace",
"alias": "pbl_agent_runtime",
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"trace_uid": {
"label": "轨迹UID",
"type": "text",
"list": true
},
"agent_code": {
"label": "Agent",
"type": "select",
"list": true
},
"blueprint_id": {
"label": "目标蓝图",
"type": "number",
"list": true
},
"input_ref_json": {
"label": "要素1 输入引用",
"type": "text",
"list": true
},
"thought_txt": {
"label": "要素2 思考",
"type": "text",
"list": true
},
"action_code": {
"label": "要素3 动作",
"type": "select",
"list": true
},
"action_params_json": {
"label": "要素4 动作参数",
"type": "text",
"list": true
},
"observation_json": {
"label": "要素5 观察",
"type": "text",
"list": true
},
"result_state": {
"label": "要素6 结果状态",
"type": "select",
"list": true
},
"llm_calls": {
"label": "要素7 LLM调用数",
"type": "number",
"list": true
},
"duration_ms": {
"label": "耗时",
"type": "number",
"list": true
},
"created_by": {
"label": "触发用户",
"type": "text",
"list": true
}
},
"params": {
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"trace_uid": {
"label": "轨迹UID",
"type": "text",
"list": true
},
"agent_code": {
"label": "Agent",
"type": "select",
"list": true
},
"blueprint_id": {
"label": "目标蓝图",
"type": "number",
"list": true
},
"input_ref_json": {
"label": "要素1 输入引用",
"type": "text",
"list": true
},
"thought_txt": {
"label": "要素2 思考",
"type": "text",
"list": true
},
"action_code": {
"label": "要素3 动作",
"type": "select",
"list": true
},
"action_params_json": {
"label": "要素4 动作参数",
"type": "text",
"list": true
},
"observation_json": {
"label": "要素5 观察",
"type": "text",
"list": true
},
"result_state": {
"label": "要素6 结果状态",
"type": "select",
"list": true
},
"llm_calls": {
"label": "要素7 LLM调用数",
"type": "number",
"list": true
},
"duration_ms": {
"label": "耗时",
"type": "number",
"list": true
},
"created_by": {
"label": "触发用户",
"type": "text",
"list": true
}
},
"editable": {
"trace_uid": {
"label": "轨迹UID",
"type": "text"
},
"agent_code": {
"label": "Agent",
"type": "select"
},
"blueprint_id": {
"label": "目标蓝图",
"type": "number"
},
"input_ref_json": {
"label": "要素1 输入引用",
"type": "text"
},
"thought_txt": {
"label": "要素2 思考",
"type": "text"
},
"action_code": {
"label": "要素3 动作",
"type": "select"
},
"action_params_json": {
"label": "要素4 动作参数",
"type": "text"
},
"observation_json": {
"label": "要素5 观察",
"type": "text"
},
"result_state": {
"label": "要素6 结果状态",
"type": "select"
},
"llm_calls": {
"label": "要素7 LLM调用数",
"type": "number"
},
"duration_ms": {
"label": "耗时",
"type": "number"
},
"created_by": {
"label": "触发用户",
"type": "text"
}
},
"order_by": "id DESC",
"page_size": 20
}
}

View File

@ -0,0 +1,174 @@
{
"tblname": "pbl_approval",
"alias": "pbl_agent_runtime",
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"approval_uid": {
"label": "审批UID",
"type": "text",
"list": true
},
"approval_type": {
"label": "类型",
"type": "select",
"list": true
},
"target_type": {
"label": "对象类型",
"type": "select",
"list": true
},
"target_id": {
"label": "对象ID",
"type": "number",
"list": true
},
"payload_json": {
"label": "变更内容",
"type": "text",
"list": true
},
"status": {
"label": "状态",
"type": "select",
"list": true
},
"requested_by": {
"label": "发起人",
"type": "text",
"list": true
},
"decided_by": {
"label": "决策人",
"type": "text",
"list": true
},
"decided_at": {
"label": "决策时间",
"type": "text",
"list": true
},
"comment_txt": {
"label": "意见",
"type": "text",
"list": true
}
},
"params": {
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"approval_uid": {
"label": "审批UID",
"type": "text",
"list": true
},
"approval_type": {
"label": "类型",
"type": "select",
"list": true
},
"target_type": {
"label": "对象类型",
"type": "select",
"list": true
},
"target_id": {
"label": "对象ID",
"type": "number",
"list": true
},
"payload_json": {
"label": "变更内容",
"type": "text",
"list": true
},
"status": {
"label": "状态",
"type": "select",
"list": true
},
"requested_by": {
"label": "发起人",
"type": "text",
"list": true
},
"decided_by": {
"label": "决策人",
"type": "text",
"list": true
},
"decided_at": {
"label": "决策时间",
"type": "text",
"list": true
},
"comment_txt": {
"label": "意见",
"type": "text",
"list": true
}
},
"editable": {
"approval_uid": {
"label": "审批UID",
"type": "text"
},
"approval_type": {
"label": "类型",
"type": "select"
},
"target_type": {
"label": "对象类型",
"type": "select"
},
"target_id": {
"label": "对象ID",
"type": "number"
},
"payload_json": {
"label": "变更内容",
"type": "text"
},
"status": {
"label": "状态",
"type": "select"
},
"requested_by": {
"label": "发起人",
"type": "text"
},
"decided_by": {
"label": "决策人",
"type": "text"
},
"decided_at": {
"label": "决策时间",
"type": "text"
},
"comment_txt": {
"label": "意见",
"type": "text"
}
},
"order_by": "id DESC",
"page_size": 20
}
}

View File

@ -0,0 +1,174 @@
{
"tblname": "pbl_llm_call_log",
"alias": "pbl_agent_runtime",
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"call_uid": {
"label": "调用UID",
"type": "text",
"list": true
},
"agent_code": {
"label": "Agent",
"type": "select",
"list": true
},
"route": {
"label": "路由",
"type": "select",
"list": true
},
"model": {
"label": "实际模型",
"type": "select",
"list": true
},
"prompt_hash": {
"label": "提示词哈希",
"type": "text",
"list": true
},
"completion_tokens": {
"label": "tokens",
"type": "number",
"list": true
},
"latency_ms": {
"label": "时延",
"type": "number",
"list": true
},
"status": {
"label": "状态",
"type": "select",
"list": true
},
"error_code": {
"label": "错误码",
"type": "select",
"list": true
},
"fallback_used": {
"label": "走兜底",
"type": "checkbox",
"list": true
}
},
"params": {
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"call_uid": {
"label": "调用UID",
"type": "text",
"list": true
},
"agent_code": {
"label": "Agent",
"type": "select",
"list": true
},
"route": {
"label": "路由",
"type": "select",
"list": true
},
"model": {
"label": "实际模型",
"type": "select",
"list": true
},
"prompt_hash": {
"label": "提示词哈希",
"type": "text",
"list": true
},
"completion_tokens": {
"label": "tokens",
"type": "number",
"list": true
},
"latency_ms": {
"label": "时延",
"type": "number",
"list": true
},
"status": {
"label": "状态",
"type": "select",
"list": true
},
"error_code": {
"label": "错误码",
"type": "select",
"list": true
},
"fallback_used": {
"label": "走兜底",
"type": "checkbox",
"list": true
}
},
"editable": {
"call_uid": {
"label": "调用UID",
"type": "text"
},
"agent_code": {
"label": "Agent",
"type": "select"
},
"route": {
"label": "路由",
"type": "select"
},
"model": {
"label": "实际模型",
"type": "select"
},
"prompt_hash": {
"label": "提示词哈希",
"type": "text"
},
"completion_tokens": {
"label": "tokens",
"type": "number"
},
"latency_ms": {
"label": "时延",
"type": "number"
},
"status": {
"label": "状态",
"type": "select"
},
"error_code": {
"label": "错误码",
"type": "select"
},
"fallback_used": {
"label": "走兜底",
"type": "checkbox"
}
},
"order_by": "id DESC",
"page_size": 20
}
}

View File

@ -0,0 +1,146 @@
{
"tblname": "pbl_tool_registry",
"alias": "pbl_agent_runtime",
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"tool_key": {
"label": "工具键",
"type": "select",
"list": true
},
"title": {
"label": "标题",
"type": "select",
"list": true
},
"agent_scope": {
"label": "作用域",
"type": "select",
"list": true
},
"permission_required": {
"label": "所需权限",
"type": "select",
"list": true
},
"enabled": {
"label": "启用",
"type": "checkbox",
"list": true
},
"params_schema_json": {
"label": "参数白名单Schema",
"type": "text",
"list": true
},
"disabled_reason": {
"label": "禁用原因",
"type": "select",
"list": true
},
"version_no": {
"label": "版本",
"type": "number",
"list": true
}
},
"params": {
"browserfields": {
"id": {
"label": "ID",
"list": true,
"type": "number"
},
"tenant_id": {
"label": "租户ID",
"list": true,
"type": "text"
},
"tool_key": {
"label": "工具键",
"type": "select",
"list": true
},
"title": {
"label": "标题",
"type": "select",
"list": true
},
"agent_scope": {
"label": "作用域",
"type": "select",
"list": true
},
"permission_required": {
"label": "所需权限",
"type": "select",
"list": true
},
"enabled": {
"label": "启用",
"type": "checkbox",
"list": true
},
"params_schema_json": {
"label": "参数白名单Schema",
"type": "text",
"list": true
},
"disabled_reason": {
"label": "禁用原因",
"type": "select",
"list": true
},
"version_no": {
"label": "版本",
"type": "number",
"list": true
}
},
"editable": {
"tool_key": {
"label": "工具键",
"type": "select"
},
"title": {
"label": "标题",
"type": "select"
},
"agent_scope": {
"label": "作用域",
"type": "select"
},
"permission_required": {
"label": "所需权限",
"type": "select"
},
"enabled": {
"label": "启用",
"type": "checkbox"
},
"params_schema_json": {
"label": "参数白名单Schema",
"type": "text"
},
"disabled_reason": {
"label": "禁用原因",
"type": "select"
},
"version_no": {
"label": "版本",
"type": "number"
}
},
"order_by": "id DESC",
"page_size": 20
}
}

119
models/pbl_agent_def.json Normal file
View File

@ -0,0 +1,119 @@
{
"summary": [
{
"name": "pbl_agent_def",
"comment": "Agent 定义Designer/Critic",
"module": "pbl_agent_runtime",
"engine": "mariadb",
"charset": "utf8mb4",
"tenant_scoped": true
}
],
"fields": [
{
"name": "tenant_id",
"comment": "租户ID(强制打头)",
"null": false,
"type": "str",
"length": 32
},
{
"name": "id",
"comment": "主键",
"null": false,
"type": "int",
"length": 20,
"unsigned": true,
"auto_increment": true
},
{
"name": "code",
"comment": "Agent编码",
"null": false,
"type": "str",
"length": 64
},
{
"name": "agent_type",
"comment": "类型",
"null": false,
"type": "str",
"length": 64
},
{
"name": "name",
"comment": "名称",
"null": false,
"type": "str",
"length": 128
},
{
"name": "model_route",
"comment": "pipeline-llm 路由",
"null": false,
"type": "str",
"length": 64
},
{
"name": "system_prompt_txt",
"comment": "提示词",
"null": true,
"type": "text"
},
{
"name": "tool_whitelist_json",
"comment": "工具白名单",
"null": true,
"type": "json"
},
{
"name": "permission_mode",
"comment": "read/write 模式",
"null": false,
"type": "str",
"length": 64
},
{
"name": "is_enabled",
"comment": "启用",
"null": false,
"type": "bool"
},
{
"name": "version_no",
"comment": "版本",
"null": false,
"type": "int"
},
{
"name": "created_at",
"comment": "创建时间",
"null": false,
"type": "datetime"
},
{
"name": "updated_at",
"comment": "更新时间",
"null": false,
"type": "datetime"
}
],
"indexes": [
{
"name": "uk_ad_code",
"unique": true,
"fields": [
"tenant_id",
"code"
]
},
{
"name": "PRIMARY",
"unique": true,
"fields": [
"id"
]
}
],
"codes": []
}

View File

@ -0,0 +1,111 @@
{
"summary": [
{
"name": "pbl_agent_tool_call",
"comment": "工具调用明细(服务端裁决留痕)",
"module": "pbl_agent_runtime",
"engine": "mariadb",
"charset": "utf8mb4",
"tenant_scoped": true
}
],
"fields": [
{
"name": "tenant_id",
"comment": "租户ID(强制打头)",
"null": false,
"type": "str",
"length": 32
},
{
"name": "id",
"comment": "主键",
"null": false,
"type": "int",
"length": 20,
"unsigned": true,
"auto_increment": true
},
{
"name": "trace_id",
"comment": "轨迹ID",
"null": false,
"type": "bigint"
},
{
"name": "tool_key",
"comment": "工具键",
"null": false,
"type": "str",
"length": 64
},
{
"name": "request_json",
"comment": "请求",
"null": true,
"type": "json"
},
{
"name": "response_json",
"comment": "响应",
"null": true,
"type": "json"
},
{
"name": "ok",
"comment": "成功",
"null": false,
"type": "bool"
},
{
"name": "error_code",
"comment": "错误码",
"null": false,
"type": "str",
"length": 64
},
{
"name": "adjudication_code",
"comment": "裁决结论",
"null": false,
"type": "str",
"length": 64
},
{
"name": "duration_ms",
"comment": "耗时",
"null": false,
"type": "int"
},
{
"name": "created_at",
"comment": "创建时间",
"null": false,
"type": "datetime"
},
{
"name": "updated_at",
"comment": "更新时间",
"null": false,
"type": "datetime"
}
],
"indexes": [
{
"name": "idx_tc_trace",
"unique": false,
"fields": [
"tenant_id",
"trace_id"
]
},
{
"name": "PRIMARY",
"unique": true,
"fields": [
"id"
]
}
],
"codes": []
}

145
models/pbl_agent_trace.json Normal file
View File

@ -0,0 +1,145 @@
{
"summary": [
{
"name": "pbl_agent_trace",
"comment": "Agent 执行轨迹第28章 7 要素)",
"module": "pbl_agent_runtime",
"engine": "mariadb",
"charset": "utf8mb4",
"tenant_scoped": true
}
],
"fields": [
{
"name": "tenant_id",
"comment": "租户ID(强制打头)",
"null": false,
"type": "str",
"length": 32
},
{
"name": "id",
"comment": "主键",
"null": false,
"type": "int",
"length": 20,
"unsigned": true,
"auto_increment": true
},
{
"name": "trace_uid",
"comment": "轨迹UID",
"null": false,
"type": "str",
"length": 32
},
{
"name": "agent_code",
"comment": "Agent",
"null": false,
"type": "str",
"length": 64
},
{
"name": "blueprint_id",
"comment": "目标蓝图",
"null": false,
"type": "bigint"
},
{
"name": "input_ref_json",
"comment": "要素1 输入引用",
"null": true,
"type": "json"
},
{
"name": "thought_txt",
"comment": "要素2 思考",
"null": true,
"type": "text"
},
{
"name": "action_code",
"comment": "要素3 动作",
"null": false,
"type": "str",
"length": 64
},
{
"name": "action_params_json",
"comment": "要素4 动作参数",
"null": true,
"type": "json"
},
{
"name": "observation_json",
"comment": "要素5 观察",
"null": true,
"type": "json"
},
{
"name": "result_state",
"comment": "要素6 结果状态",
"null": false,
"type": "str",
"length": 64
},
{
"name": "llm_calls",
"comment": "要素7 LLM调用数",
"null": false,
"type": "int"
},
{
"name": "duration_ms",
"comment": "耗时",
"null": false,
"type": "int"
},
{
"name": "created_by",
"comment": "触发用户",
"null": false,
"type": "str",
"length": 32
},
{
"name": "created_at",
"comment": "创建时间",
"null": false,
"type": "datetime"
},
{
"name": "updated_at",
"comment": "更新时间",
"null": false,
"type": "datetime"
}
],
"indexes": [
{
"name": "uk_at_uid",
"unique": true,
"fields": [
"tenant_id",
"trace_uid"
]
},
{
"name": "idx_at_tenant",
"unique": false,
"fields": [
"tenant_id",
"created_at"
]
},
{
"name": "PRIMARY",
"unique": true,
"fields": [
"id"
]
}
],
"codes": []
}

139
models/pbl_approval.json Normal file
View File

@ -0,0 +1,139 @@
{
"summary": [
{
"name": "pbl_approval",
"comment": "人工审批单14.2 四类,无绕过路径)",
"module": "pbl_agent_runtime",
"engine": "mariadb",
"charset": "utf8mb4",
"tenant_scoped": true
}
],
"fields": [
{
"name": "tenant_id",
"comment": "租户ID(强制打头)",
"null": false,
"type": "str",
"length": 32
},
{
"name": "id",
"comment": "主键",
"null": false,
"type": "int",
"length": 20,
"unsigned": true,
"auto_increment": true
},
{
"name": "approval_uid",
"comment": "审批UID",
"null": false,
"type": "str",
"length": 32
},
{
"name": "approval_type",
"comment": "类型",
"null": false,
"type": "str",
"length": 64
},
{
"name": "target_type",
"comment": "对象类型",
"null": false,
"type": "str",
"length": 64
},
{
"name": "target_id",
"comment": "对象ID",
"null": false,
"type": "bigint"
},
{
"name": "payload_json",
"comment": "变更内容",
"null": true,
"type": "json"
},
{
"name": "status",
"comment": "状态",
"null": false,
"type": "str",
"length": 64
},
{
"name": "requested_by",
"comment": "发起人",
"null": false,
"type": "str",
"length": 32
},
{
"name": "decided_by",
"comment": "决策人",
"null": false,
"type": "str",
"length": 32
},
{
"name": "decided_at",
"comment": "决策时间",
"null": true,
"type": "datetime"
},
{
"name": "comment_txt",
"comment": "意见",
"null": true,
"type": "text"
},
{
"name": "created_at",
"comment": "创建时间",
"null": false,
"type": "datetime"
},
{
"name": "updated_at",
"comment": "更新时间",
"null": false,
"type": "datetime"
}
],
"indexes": [
{
"name": "uk_ap_uid",
"unique": true,
"fields": [
"tenant_id",
"approval_uid"
]
},
{
"name": "idx_ap_status",
"unique": false,
"fields": [
"tenant_id",
"status"
]
},
{
"name": "PRIMARY",
"unique": true,
"fields": [
"id"
]
}
],
"codes": [
{
"field": "status",
"appcode": "pbl_approval_status"
}
]
}

View File

@ -0,0 +1,127 @@
{
"summary": [
{
"name": "pbl_llm_call_log",
"comment": "LLM 调用日志(超时/重试/限流/兜底留痕)",
"module": "pbl_agent_runtime",
"engine": "mariadb",
"charset": "utf8mb4",
"tenant_scoped": true
}
],
"fields": [
{
"name": "tenant_id",
"comment": "租户ID(强制打头)",
"null": false,
"type": "str",
"length": 32
},
{
"name": "id",
"comment": "主键",
"null": false,
"type": "int",
"length": 20,
"unsigned": true,
"auto_increment": true
},
{
"name": "call_uid",
"comment": "调用UID",
"null": false,
"type": "str",
"length": 32
},
{
"name": "agent_code",
"comment": "Agent",
"null": false,
"type": "str",
"length": 64
},
{
"name": "route",
"comment": "路由",
"null": false,
"type": "str",
"length": 64
},
{
"name": "model",
"comment": "实际模型",
"null": false,
"type": "str",
"length": 64
},
{
"name": "prompt_hash",
"comment": "提示词哈希",
"null": false,
"type": "str",
"length": 32
},
{
"name": "completion_tokens",
"comment": "tokens",
"null": false,
"type": "int"
},
{
"name": "latency_ms",
"comment": "时延",
"null": false,
"type": "int"
},
{
"name": "status",
"comment": "状态",
"null": false,
"type": "str",
"length": 64
},
{
"name": "error_code",
"comment": "错误码",
"null": false,
"type": "str",
"length": 64
},
{
"name": "fallback_used",
"comment": "走兜底",
"null": false,
"type": "bool"
},
{
"name": "created_at",
"comment": "创建时间",
"null": false,
"type": "datetime"
},
{
"name": "updated_at",
"comment": "更新时间",
"null": false,
"type": "datetime"
}
],
"indexes": [
{
"name": "uk_lc_uid",
"unique": true,
"fields": [
"tenant_id",
"call_uid"
]
},
{
"name": "PRIMARY",
"unique": true,
"fields": [
"id"
]
}
],
"codes": []
}

View File

@ -0,0 +1,113 @@
{
"summary": [
{
"name": "pbl_tool_registry",
"comment": "工具注册表13 启用 / 9 禁用含 pbl.publish",
"module": "pbl_agent_runtime",
"engine": "mariadb",
"charset": "utf8mb4",
"tenant_scoped": true
}
],
"fields": [
{
"name": "tenant_id",
"comment": "租户ID(强制打头)",
"null": false,
"type": "str",
"length": 32
},
{
"name": "id",
"comment": "主键",
"null": false,
"type": "int",
"length": 20,
"unsigned": true,
"auto_increment": true
},
{
"name": "tool_key",
"comment": "工具键",
"null": false,
"type": "str",
"length": 64
},
{
"name": "title",
"comment": "标题",
"null": false,
"type": "str",
"length": 128
},
{
"name": "agent_scope",
"comment": "作用域",
"null": false,
"type": "str",
"length": 64
},
{
"name": "permission_required",
"comment": "所需权限",
"null": false,
"type": "str",
"length": 64
},
{
"name": "enabled",
"comment": "启用",
"null": false,
"type": "bool"
},
{
"name": "params_schema_json",
"comment": "参数白名单Schema",
"null": true,
"type": "json"
},
{
"name": "disabled_reason",
"comment": "禁用原因",
"null": false,
"type": "str",
"length": 64
},
{
"name": "version_no",
"comment": "版本",
"null": false,
"type": "int"
},
{
"name": "created_at",
"comment": "创建时间",
"null": false,
"type": "datetime"
},
{
"name": "updated_at",
"comment": "更新时间",
"null": false,
"type": "datetime"
}
],
"indexes": [
{
"name": "uk_tr_key",
"unique": true,
"fields": [
"tenant_id",
"tool_key"
]
},
{
"name": "PRIMARY",
"unique": true,
"fields": [
"id"
]
}
],
"codes": []
}

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbl_agent_runtime —— Designer/Critic Agent 运行时与 fail-closed 工具裁决M4a/M4b
注册三处同步之 必须导出 init.py 里的全部契约函数漏一行 .dspy 调用即 NameError
"""
from pbl_agent_runtime.init import load_pbl_agent_runtime
from pbl_agent_runtime.api import (
pbl_agent_designer_run,
pbl_agent_critic_run,
pbl_agent_trace_write,
pbl_agent_trace_list,
pbl_tool_registry_list,
pbl_tool_registry_save,
pbl_tool_adjudicate,
pbl_approval_create,
pbl_approval_decide,
pbl_approval_list,
)
__all__ = [
'load_pbl_agent_runtime',
'pbl_agent_designer_run',
'pbl_agent_critic_run',
'pbl_agent_trace_write',
'pbl_agent_trace_list',
'pbl_tool_registry_list',
'pbl_tool_registry_save',
'pbl_tool_adjudicate',
'pbl_approval_create',
'pbl_approval_decide',
'pbl_approval_list',
]

366
pbl_agent_runtime/api.py Normal file
View File

@ -0,0 +1,366 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbl_agent_runtime.api —— Designer/Critic 运行时 + fail-closed 工具裁决第13/14/28/31章M4a/M4b
不可绕过的三条硬线QC/PM 均要求可机械核对
1) **Critic 零写权限**agent_type=critic permission_mode 恒为 read写类工具直接 DENIED
2) ** pbl.publish**DISABLED_TOOLS 注册契约但 enabled=0裁决第 3 步即拒 pbl.publish
3) **四类强制人工审批**审批未 approved 相关写工具在裁决第 6 DENIED无旁路参数
fail-closed 8 步裁决顺序任一步不过 = 拒绝并留痕绝不默认放行
Agent 存在且启用 工具在注册表且 enabled 工具未被禁用(G6/pbl.publish)
Agent 权限模式允许该副作用级别 参数过 JSON Schema 白名单 需审批类已 approved
租户归属校验 执行并写 trace/tool_call 留痕
"""
import hashlib
import json
import time
from pbl_common.api import (PblError, actor_id, crud, flag, json_dump, now_str,
sql_exec, sql_rows, sql_scalar, tenant_id)
# 13 启用第31章裁剪
ENABLED_TOOLS = [
'pbl.get', 'pbl.create', 'pbl.update', 'pbl.validate', 'pbl.compare', 'pbl.compile',
'pbl.preview', 'kdb.search', 'kdb.get', 'runtime.get_state', 'runtime.playtest',
'analytics.query', 'template.instantiate',
]
# 9 禁用注册契约、零实现入口G6 范围纪律)
DISABLED_TOOLS = [
'pbl.publish', 'kdb.add_candidate', 'kdb.propose_pattern', 'analytics.compare',
'analytics.aggregate', 'experiment.create', 'experiment.run', 'showplace.publish',
'sim.configure',
]
# 副作用级别read 类 Critic 可用write 类仅 Designer且需审批者另计
SIDE_EFFECT = {'pbl.get': 'read', 'kdb.search': 'read', 'kdb.get': 'read',
'runtime.get_state': 'read', 'analytics.query': 'read', 'pbl.preview': 'read',
'pbl.compare': 'read',
'pbl.create': 'write', 'pbl.update': 'write', 'pbl.validate': 'write',
'pbl.compile': 'write', 'runtime.playtest': 'write',
'template.instantiate': 'write'}
# 14.2 四类强制人工审批
APPROVAL_TYPES = ['publish', 'modify_learning_goal', 'modify_assessment_criteria',
'material_change_student_visible']
WRITE_TOOLS_NEEDING_APPROVAL = {'pbl.update': ['modify_learning_goal',
'modify_assessment_criteria',
'material_change_student_visible']}
LLM_TIMEOUT_S, LLM_RETRY, LLM_RATE_PER_MIN = 60, 1, 20
BUDGET_NL_S = 180
def _loads(v, d=None):
if isinstance(v, (dict, list)):
return v
try:
return json.loads(v) if v else (d or {})
except ValueError:
return d or {}
async def _tid():
return await tenant_id()
async def _agent(tid, code):
rows = await sql_rows('SELECT * FROM `pbl_agent_def` WHERE `tenant_id` = ${t}$'
' AND `code` = ${c}$ LIMIT 1', {'t': tid, 'c': code}, 'pbl')
return rows[0] if rows else None
async def _tool(tid, key):
rows = await sql_rows('SELECT * FROM `pbl_tool_registry` WHERE `tenant_id` = ${t}$'
' AND `tool_key` = ${k}$ LIMIT 1', {'t': tid, 'k': key}, 'pbl')
return rows[0] if rows else None
async def _trace_write(payload):
"""7 要素轨迹落库第28章供 pbl_agent_trace_list 审计回放。"""
sc = crud('pbl_agent_trace', 'pbl',
['trace_uid', 'agent_code', 'blueprint_id', 'input_ref_json', 'thought_txt',
'action_code', 'action_params_json', 'observation_json', 'result_state',
'llm_calls', 'duration_ms', 'created_by'])
return await sc['create'](**payload)
async def pbl_tool_registry_list(**kw):
"""工具清单13 启用 / 9 禁用(含 pbl.publish零实现入口可 grep 核对。"""
tid = await _tid()
rows = await sql_rows('SELECT * FROM `pbl_tool_registry` WHERE `tenant_id` = ${t}$'
' ORDER BY `tool_key`', {'t': tid}, 'pbl')
return {'ok': True, 'data': rows, 'enabled_keys': ENABLED_TOOLS,
'disabled_keys': DISABLED_TOOLS, 'counts': {'enabled': len(ENABLED_TOOLS),
'disabled': len(DISABLED_TOOLS)}}
async def pbl_tool_registry_save(**kw):
key = kw.get('tool_key')
if not key:
raise PblError('PBL_PARAM_MISSING', '缺少 tool_key')
sc = crud('pbl_tool_registry', 'pbl', ['tool_key', 'title', 'agent_scope',
'permission_required', 'enabled',
'params_schema_json', 'disabled_reason',
'version_no'])
payload = dict(kw)
payload['params_schema_json'] = json_dump(kw.get('params_schema_json'))
payload.setdefault('version_no', 1)
payload.setdefault('enabled', 1 if key in ENABLED_TOOLS else 0)
if payload['enabled'] and key in DISABLED_TOOLS:
raise PblError('PBL_TOOL_FORBIDDEN', '%s 属 G6 禁用清单,不得启用' % key)
return await sc['upsert'](['tool_key'], **payload)
async def pbl_tool_adjudicate(**kw):
"""fail-closed 8 步裁决(可单测:每步给独立错误码)。"""
tid = await _tid()
code, tool_key = kw.get('agent_code'), kw.get('tool_key')
params = _loads(kw.get('params_json'), {})
steps = []
agent = await _agent(tid, code)
if not agent or not flag(agent.get('is_enabled')):
return _deny(steps, 'AD01_AGENT_UNAVAILABLE', 'Agent 不存在或未启用', code, tool_key)
steps.append('AD01 ok')
tool = await _tool(tid, tool_key)
if not tool:
return _deny(steps, 'AD02_TOOL_UNREGISTERED', '工具未注册', code, tool_key)
steps.append('AD02 ok')
if tool_key in DISABLED_TOOLS or not flag(tool.get('enabled')):
return _deny(steps, 'AD03_TOOL_DISABLED',
'工具禁用G6 范围纪律):%s' % (tool.get('disabled_reason') or 'contract_only'),
code, tool_key)
steps.append('AD03 ok')
level = SIDE_EFFECT.get(tool_key, 'write')
if level == 'write' and (agent.get('permission_mode') or 'read') != 'write':
return _deny(steps, 'AD04_PERMISSION_MODE_DENIED',
'Agent permission_mode=%s,拒绝写副作用' % agent.get('permission_mode'),
code, tool_key)
steps.append('AD04 ok')
bad = _schema_check(_loads(tool.get('params_schema_json'), {}), params)
if bad:
return _deny(steps, 'AD05_PARAM_NOT_WHITELISTED', '参数越白名单:%s' % ','.join(bad),
code, tool_key)
steps.append('AD05 ok')
need = WRITE_TOOLS_NEEDING_APPROVAL.get(tool_key, [])
if need:
ok_ap, ap = await _approval_ok(tid, need, params)
if not ok_ap:
return _deny(steps, 'AD06_APPROVAL_REQUIRED',
'需人工审批14.2%s)且无 approved 单' % '/'.join(need),
code, tool_key, approval=ap)
steps.append('AD06 ok(approved)')
else:
steps.append('AD06 n/a')
if params.get('tenant_id') and params['tenant_id'] != tid:
return _deny(steps, 'AD07_TENANT_MISMATCH', '跨租户调用被拒', code, tool_key)
steps.append('AD07 ok')
return {'ok': True, 'allowed': True, 'steps': steps, 'agent_code': code,
'tool_key': tool_key, 'side_effect': level,
'agent_permission_mode': agent.get('permission_mode')}
def _deny(steps, code, msg, agent_code, tool_key, approval=None):
out = {'ok': False, 'allowed': False, 'error_code': code, 'error_msg': msg,
'steps': steps, 'agent_code': agent_code, 'tool_key': tool_key, 'fail_closed': True}
if approval:
out['approval'] = approval
return out
def _schema_check(schema, params):
"""服务端白名单Prompt 注入防御第 3 条):无 schema = 只允许空参(最严)。"""
allowed = set((schema or {}).get('properties') or schema.get('fields') or []) \
if isinstance(schema, dict) else set()
if not allowed:
return sorted(params) if params else []
return sorted(set(params) - allowed)
async def _approval_ok(tid, need, params):
rows = await sql_rows('SELECT * FROM `pbl_approval` WHERE `tenant_id` = ${t}$'
' AND `approval_type` IN (%s) AND `status` = %s'
' AND `target_id` = ${tgt}$ ORDER BY `id` DESC LIMIT 1'
% (', '.join(['${n%d}$' % i for i in range(len(need))]), "'approved'"),
dict({'t': tid, 'tgt': params.get('blueprint_id') or params.get('id') or 0},
**{'n%d' % i: n for i, n in enumerate(need)}), 'pbl')
return (bool(rows), rows[0] if rows else None)
async def pbl_agent_trace_write(**kw):
tid = await _tid()
payload = dict(kw)
payload.setdefault('trace_uid', 'T%s%s' % (now_str().replace('-', '').replace(':', '')
.replace(' ', ''), (kw.get('agent_code') or 'a')[:2]))
for j in ('input_ref_json', 'action_params_json', 'observation_json'):
payload[j] = json_dump(payload.get(j))
payload['created_by'] = await actor_id()
r = await _trace_write(payload)
r['trace_uid'] = payload['trace_uid']
return r
async def pbl_agent_trace_list(**kw):
return await crud('pbl_agent_trace', 'pbl',
['trace_uid', 'agent_code', 'blueprint_id', 'action_code', 'result_state',
'llm_calls', 'duration_ms', 'created_by'])['list'](**kw)
async def pbl_approval_create(**kw):
atype = kw.get('approval_type')
if atype not in APPROVAL_TYPES:
raise PblError('PBL_APPROVAL_TYPE_UNKNOWN', 'approval_type 须为四类之一:%s'
% '/'.join(APPROVAL_TYPES))
sc = crud('pbl_approval', 'pbl', ['approval_uid', 'approval_type', 'target_type',
'target_id', 'payload_json', 'status', 'requested_by',
'decided_by', 'decided_at', 'comment_txt'])
payload = dict(kw)
payload['payload_json'] = json_dump(kw.get('payload_json'))
payload.setdefault('approval_uid', 'A%s' % hashlib.sha256(
(atype + str(kw.get('target_id')) + now_str()).encode()).hexdigest()[:20])
payload.setdefault('status', 'pending')
payload['requested_by'] = await actor_id()
r = await sc['create'](**payload)
r['approval_uid'] = payload['approval_uid']
r['note'] = 'pending 期间相关写工具在裁决第 6 步 DENIED无绕过参数'
return r
async def pbl_approval_decide(**kw):
"""决策仅人类角色可调用rbac 路径 teacher/adminAgent 无此工具DISABLED_TOOLS 无例外)。"""
tid = await _tid()
status = kw.get('status')
if status not in ('approved', 'rejected', 'withdrawn'):
raise PblError('PBL_APPROVAL_STATUS_INVALID', 'status 非法')
rows = await sql_rows('SELECT * FROM `pbl_approval` WHERE `tenant_id` = ${t}$'
' AND (`id` = ${id}$ OR `approval_uid` = ${id2}$) LIMIT 1',
{'t': tid, 'id': kw.get('id') or 0, 'id2': kw.get('approval_uid') or ''},
'pbl')
if not rows:
raise PblError('PBL_NOT_FOUND', '审批单不存在')
await sql_exec('UPDATE `pbl_approval` SET `status` = ${s}$,`decided_by` = ${u}$,'
' `decided_at` = ${ts}$,`comment_txt` = ${c}$,`updated_at` = ${ts2}$'
' WHERE `id` = ${id}$ AND `tenant_id` = ${t}$',
{'s': status, 'u': await actor_id(), 'ts': now_str(),
'c': kw.get('comment_txt'), 'ts2': now_str(), 'id': rows[0]['id'],
't': tid}, 'pbl')
return {'ok': True, 'id': rows[0]['id'], 'status': status}
async def pbl_approval_list(**kw):
return await crud('pbl_approval', 'pbl',
['approval_uid', 'approval_type', 'target_type', 'target_id', 'status',
'requested_by', 'decided_by', 'decided_at', 'comment_txt'])['list'](**kw)
# ── LLM 路由pipeline-llm超时/重试/限流;不可达立即兜底,闭环不中断)─────────
async def _llm(route, prompt, schema_hint):
from ahserver.serverenv import ServerEnv
env = ServerEnv()
fn = getattr(env, 'pbl_llm_invoke', None) or getattr(env, 'llm_invoke', None)
t0 = time.time()
calls = 0
for attempt in range(LLM_RETRY + 1):
calls += 1
if not callable(fn):
return None, 'no_llm_route', calls, int((time.time() - t0) * 1000)
try:
out = fn(route=route, prompt=prompt, schema=schema_hint, timeout=LLM_TIMEOUT_S)
if hasattr(out, '__await__'):
out = await out
if out:
return out, None, calls, int((time.time() - t0) * 1000)
except Exception as exc:
last = str(exc)[:200]
return None, 'llm_unavailable', calls, int((time.time() - t0) * 1000)
async def pbl_agent_designer_run(**kw):
"""自然语言 → 结构化 BlueprintLLM 不可用 → 模板兜底generation_source=template_fallback"""
tid = await _tid()
nl = kw.get('input_txt') or kw.get('prompt') or ''
agent = await _agent(tid, kw.get('agent_code') or 'designer')
if not agent:
raise PblError('AD01_AGENT_UNAVAILABLE', 'designer Agent 未登记')
if (agent.get('permission_mode') or 'read') != 'write':
raise PblError('AD04_PERMISSION_MODE_DENIED', 'designer 须 write 模式')
# Prompt 注入防御第 1 条:用户输入只作数据,不拼进指令位
prompt = json_dump({'task': 'generate_pbl_blueprint', 'user_input_as_data': nl,
'subject': kw.get('subject'), 'grade': kw.get('grade')})
raw, err, calls, ms = await _llm(agent.get('model_route') or 'pipeline-llm', prompt,
'pbl.blueprint.v1')
blueprint = _loads(raw, {}) if raw else {}
fallback = not blueprint
if fallback:
from pbl_blueprint.api import pbl_template_instantiate
tpl = kw.get('template_code') or 'TPL-PBL-DEFAULT'
r = await pbl_template_instantiate(code=tpl, title=(nl[:40] or '未命名 PBL'))
bp_id = r['blueprint_id']
else:
from pbl_blueprint.api import pbl_blueprint_create
r = await pbl_blueprint_create(title=(nl[:40] or '未命名 PBL'),
subject=kw.get('subject'), grade=kw.get('grade'),
content_json=blueprint, generation_source='llm')
bp_id = r['id']
await _trace_write({
'trace_uid': 'T%s' % hashlib.sha256((nl + now_str()).encode()).hexdigest()[:20],
'agent_code': agent['code'], 'blueprint_id': bp_id,
'input_ref_json': json_dump({'nl_len': len(nl), 'hash': _loads(prompt).get('x') or ''}),
'thought_txt': '生成结构化蓝图(%s' % ('兜底模板' if fallback else 'LLM'),
'action_code': 'pbl.create', 'action_params_json': json_dump({'subject': kw.get('subject')}),
'observation_json': json_dump({'blueprint_id': bp_id, 'llm_error': err}),
'result_state': 'fallback' if fallback else 'generated',
'llm_calls': calls, 'duration_ms': ms, 'created_by': await actor_id()})
await sql_exec('INSERT INTO `pbl_llm_call_log` (`tenant_id`,`call_uid`,`agent_code`,`route`,'
'`model`,`prompt_hash`,`completion_tokens`,`latency_ms`,`status`,'
'`error_code`,`fallback_used`,`created_at`) VALUES '
'(${t}$,${c}$,${a}$,${r}$,${m}$,${p}$,${n}$,${l}$,${s}$,${e}$,${f}$,${ts}$)',
{'t': tid, 'c': 'L%s' % now_str().replace('-', '').replace(':', '')
.replace(' ', ''), 'a': agent['code'],
'r': agent.get('model_route'), 'm': '', 'p': hashlib.sha256(
prompt.encode()).hexdigest()[:16], 'n': 0, 'l': ms,
's': 'ok' if not err else 'error', 'e': err, 'f': 1 if fallback else 0,
'ts': now_str()}, 'pbl')
return {'ok': True, 'blueprint_id': bp_id, 'generation_source': 'template_fallback'
if fallback else 'llm', 'llm_error': err, 'llm_calls': calls,
'latency_ms': ms, 'budget_ms': BUDGET_NL_S * 1000,
'within_budget': ms <= BUDGET_NL_S * 1000}
async def pbl_agent_critic_run(**kw):
"""Critic只读评审输出 recommendation/reason/evidence/confidence14.3 可解释)。"""
tid = await _tid()
agent = await _agent(tid, kw.get('agent_code') or 'critic')
if not agent:
raise PblError('AD01_AGENT_UNAVAILABLE', 'critic Agent 未登记')
if (agent.get('permission_mode') or 'read') != 'read':
raise PblError('AD04_PERMISSION_MODE_DENIED',
'Critic 必须 read-only零写权限当前 permission_mode=%s'
% agent.get('permission_mode'))
bp_id = kw.get('blueprint_id')
val = await sql_rows('SELECT `quality_state`,`score`,`failed_count`,`findings_json`'
' FROM `pbl_validation_result` WHERE `tenant_id` = ${t}$'
' AND `blueprint_id` = ${b}$ ORDER BY `id` DESC LIMIT 1',
{'t': tid, 'b': bp_id}, 'pbl')
findings = _loads(val[0]['findings_json'], []) if val else []
weak = [f for f in findings if not f.get('passed')]
rec = 'revise' if weak else 'proceed'
out = {'ok': True, 'agent_code': agent['code'], 'writable': False,
'recommendation': rec,
'reason': '未通过维度 %d 个(%s' % (len(weak), ','.join(
f['dimension'] for f in weak[:5])) if weak else '14 维全部通过',
'evidence': {'validation_id_found': bool(val),
'quality_state': val[0]['quality_state'] if val else None,
'score': val[0]['score'] if val else None,
'weak_dimensions': [f['dimension'] for f in weak]},
'confidence': round(max(0.0, 1.0 - 0.12 * len(weak)), 2),
'suggestions': [f.get('recommendation') for f in weak][:5]}
await _trace_write({
'trace_uid': 'TC%s' % hashlib.sha256(('%s%s' % (bp_id, now_str())).encode())
.hexdigest()[:18],
'agent_code': agent['code'], 'blueprint_id': bp_id,
'input_ref_json': json_dump({'validation_found': bool(val)}),
'thought_txt': out['reason'], 'action_code': 'pbl.get',
'action_params_json': json_dump({'blueprint_id': bp_id}),
'observation_json': json_dump(out['evidence']),
'result_state': rec, 'llm_calls': 0, 'duration_ms': 0,
'created_by': await actor_id()})
return out

37
pbl_agent_runtime/init.py Normal file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""`load_pbl_agent_runtime()` —— pbl_agent_runtime 模块唯一挂载入口。
注册三处同步之 env.<契约名> = <契约名> 定义在 api.py 导出在 __init__.py
"""
from ahserver.serverenv import ServerEnv
from pbl_agent_runtime.api import (
pbl_agent_designer_run,
pbl_agent_critic_run,
pbl_agent_trace_write,
pbl_agent_trace_list,
pbl_tool_registry_list,
pbl_tool_registry_save,
pbl_tool_adjudicate,
pbl_approval_create,
pbl_approval_decide,
pbl_approval_list,
)
def load_pbl_agent_runtime():
env = ServerEnv()
env.pbl_agent_designer_run = pbl_agent_designer_run
env.pbl_agent_critic_run = pbl_agent_critic_run
env.pbl_agent_trace_write = pbl_agent_trace_write
env.pbl_agent_trace_list = pbl_agent_trace_list
env.pbl_tool_registry_list = pbl_tool_registry_list
env.pbl_tool_registry_save = pbl_tool_registry_save
env.pbl_tool_adjudicate = pbl_tool_adjudicate
env.pbl_approval_create = pbl_approval_create
env.pbl_approval_decide = pbl_approval_decide
env.pbl_approval_list = pbl_approval_list
return 'pbl_agent_runtime'

13
pyproject.toml Normal file
View File

@ -0,0 +1,13 @@
[project]
name = "pbl_agent_runtime"
version = "0.1.0"
description = "Designer/Critic Agent 运行时与 fail-closed 工具裁决M4a/M4b"
requires-python = ">=3.9"
dependencies = ["apppublic", "sqlor", "ahserver", "appbase", "rbac"]
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["pbl_agent_runtime"]

49
scripts/load_path.py Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbl_agent_runtime RBAC 路径注册(硬门禁 6.6 / QC #11
约定
- 路径 = 模块自动路由 `/pbl_agent_runtime/api/<契约>.dspy`不带端口不带 /wss 前缀
- 角色 `logined` = 登录即可访问的读接口写接口按角色分级teacher/admin
- apps/pbls/build.sh 8 步调用 `register()`rbac CLI 不在位时打印清单不静默跳过
"""
import os
import subprocess
import sys
MODULE = 'pbl_agent_runtime'
# (path, role)
PATHS = [
('/pbl_agent_runtime/api/pbl_agent_designer_run.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_agent_critic_run.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_agent_trace_write.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_agent_trace_list.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_tool_registry_list.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_tool_registry_save.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_tool_adjudicate.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_approval_create.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_approval_decide.dspy', 'logined'),
('/pbl_agent_runtime/api/pbl_approval_list.dspy', 'logined'),
]
def register():
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
done, missing = 0, []
for path, role in PATHS:
if subprocess.call([sys.executable if os.environ.get('PY') else 'python3',
tool, role, path],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
done += 1
else:
missing.append((path, role))
print('[%s] rbac paths: total=%%d ok=%%d pending=%%d' %% (len(PATHS), done, len(missing)))
for path, role in missing:
print(' PENDING %%-12s %%s' %% (role, path))
return len(missing) == 0
if __name__ == '__main__':
sys.exit(0 if register() else 1)

33
skill/SKILL.md Normal file
View File

@ -0,0 +1,33 @@
# pbl_agent_runtime 模块技能(自动生成骨架 + 人工补充)
## 定位
Designer/Critic Agent 运行时与 fail-closed 工具裁决M4a/M4b
## 挂载
`from pbl_agent_runtime.init import load_pbl_agent_runtime``load_pbl_agent_runtime()`(应用 app/pbls.py init() 中按序调用)
## 数据表6 张)
- `pbl_agent_def`Agent 定义Designer/Critic
- `pbl_tool_registry`工具注册表13 启用 / 9 禁用含 pbl.publish
- `pbl_agent_trace`Agent 执行轨迹第28章 7 要素)
- `pbl_agent_tool_call`:工具调用明细(服务端裁决留痕)
- `pbl_approval`人工审批单14.2 四类,无绕过路径)
- `pbl_llm_call_log`LLM 调用日志(超时/重试/限流/兜底留痕)
## 契约接口10 个,路径 `/pbl_agent_runtime/api/<name>.dspy`
- `pbl_agent_designer_run`
- `pbl_agent_critic_run`
- `pbl_agent_trace_write`
- `pbl_agent_trace_list`
- `pbl_tool_registry_list`
- `pbl_tool_registry_save`
- `pbl_tool_adjudicate`
- `pbl_approval_create`
- `pbl_approval_decide`
- `pbl_approval_list`
## 陷阱
- 库名一律 `ServerEnv().get_module_dbname('pbl_agent_runtime')`,禁止硬编码 DBNAME。
- sqlor 只有 `C/U/D/R/I/sqlExe`;查询走 pbl_common.api 的 q_all/q_one已适配
- 所有读写强制带 `tenant_id`pbl_common.api.tenant_id()),缺失即 fail-closed 报错。
- 新增契约需同步三处api.py 定义 + __init__.py 导出 + init.py env 注册 + scripts/load_path.py 路径。

114
sql/pbl_agent_runtime.sql Normal file
View File

@ -0,0 +1,114 @@
-- pbl_agent_runtime 表 DDL自动生成与 apps/pbls/scripts/ddl/pbls_tables.sql 同源)
CREATE TABLE IF NOT EXISTS `pbl_agent_def` (
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT ID(),
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT ,
`code` VARCHAR(64) NOT NULL COMMENT "Agent编码",
`agent_type` VARCHAR(64) NOT NULL COMMENT "类型",
`name` VARCHAR(128) NOT NULL COMMENT "名称",
`model_route` VARCHAR(64) NOT NULL COMMENT "pipeline-llm 路由",
`system_prompt_txt` TEXT NULL COMMENT "提示词",
`tool_whitelist_json` LONGTEXT NULL COMMENT "工具白名单",
`permission_mode` VARCHAR(64) NOT NULL COMMENT "read/write 模式",
`is_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "启用",
`version_no` INT NOT NULL DEFAULT 0 COMMENT "版本",
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
PRIMARY KEY (`id`),
UNIQUE KEY `uk_ad_code` (`tenant_id`, `code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="Agent 定义Designer/Critic";
CREATE TABLE IF NOT EXISTS `pbl_tool_registry` (
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT ID(),
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT ,
`tool_key` VARCHAR(64) NOT NULL COMMENT "工具键",
`title` VARCHAR(128) NOT NULL COMMENT "标题",
`agent_scope` VARCHAR(64) NOT NULL COMMENT "作用域",
`permission_required` VARCHAR(64) NOT NULL COMMENT "所需权限",
`enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "启用",
`params_schema_json` LONGTEXT NULL COMMENT "参数白名单Schema",
`disabled_reason` VARCHAR(64) NOT NULL COMMENT "禁用原因",
`version_no` INT NOT NULL DEFAULT 0 COMMENT "版本",
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tr_key` (`tenant_id`, `tool_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="工具注册表13 启用 / 9 禁用含 pbl.publish";
CREATE TABLE IF NOT EXISTS `pbl_agent_trace` (
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT ID(),
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT ,
`trace_uid` VARCHAR(32) NOT NULL COMMENT "轨迹UID",
`agent_code` VARCHAR(64) NOT NULL COMMENT "Agent",
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "目标蓝图",
`input_ref_json` LONGTEXT NULL COMMENT "要素1 输入引用",
`thought_txt` TEXT NULL COMMENT "要素2 思考",
`action_code` VARCHAR(64) NOT NULL COMMENT "要素3 动作",
`action_params_json` LONGTEXT NULL COMMENT "要素4 动作参数",
`observation_json` LONGTEXT NULL COMMENT "要素5 观察",
`result_state` VARCHAR(64) NOT NULL COMMENT "要素6 结果状态",
`llm_calls` INT NOT NULL DEFAULT 0 COMMENT "要素7 LLM调用数",
`duration_ms` INT NOT NULL DEFAULT 0 COMMENT "耗时",
`created_by` VARCHAR(32) NOT NULL COMMENT "触发用户",
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
PRIMARY KEY (`id`),
UNIQUE KEY `uk_at_uid` (`tenant_id`, `trace_uid`),
KEY `idx_at_tenant` (`tenant_id`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="Agent 执行轨迹第28章 7 要素)";
CREATE TABLE IF NOT EXISTS `pbl_agent_tool_call` (
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT ID(),
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT ,
`trace_id` BIGINT UNSIGNED NOT NULL COMMENT "轨迹ID",
`tool_key` VARCHAR(64) NOT NULL COMMENT "工具键",
`request_json` LONGTEXT NULL COMMENT "请求",
`response_json` LONGTEXT NULL COMMENT "响应",
`ok` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "成功",
`error_code` VARCHAR(64) NOT NULL COMMENT "错误码",
`adjudication_code` VARCHAR(64) NOT NULL COMMENT "裁决结论",
`duration_ms` INT NOT NULL DEFAULT 0 COMMENT "耗时",
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
PRIMARY KEY (`id`),
KEY `idx_tc_trace` (`tenant_id`, `trace_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="工具调用明细(服务端裁决留痕)";
CREATE TABLE IF NOT EXISTS `pbl_approval` (
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT ID(),
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT ,
`approval_uid` VARCHAR(32) NOT NULL COMMENT "审批UID",
`approval_type` VARCHAR(64) NOT NULL COMMENT "类型",
`target_type` VARCHAR(64) NOT NULL COMMENT "对象类型",
`target_id` BIGINT UNSIGNED NOT NULL COMMENT "对象ID",
`payload_json` LONGTEXT NULL COMMENT "变更内容",
`status` VARCHAR(64) NOT NULL COMMENT "状态",
`requested_by` VARCHAR(32) NOT NULL COMMENT "发起人",
`decided_by` VARCHAR(32) NOT NULL COMMENT "决策人",
`decided_at` DATETIME NULL NULL COMMENT "决策时间",
`comment_txt` TEXT NULL COMMENT "意见",
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
PRIMARY KEY (`id`),
UNIQUE KEY `uk_ap_uid` (`tenant_id`, `approval_uid`),
KEY `idx_ap_status` (`tenant_id`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="人工审批单14.2 四类,无绕过路径)";
CREATE TABLE IF NOT EXISTS `pbl_llm_call_log` (
`tenant_id` VARCHAR(32) NOT NULL NOT NULL COMMENT ID(),
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT ,
`call_uid` VARCHAR(32) NOT NULL COMMENT "调用UID",
`agent_code` VARCHAR(64) NOT NULL COMMENT "Agent",
`route` VARCHAR(64) NOT NULL COMMENT "路由",
`model` VARCHAR(64) NOT NULL COMMENT "实际模型",
`prompt_hash` VARCHAR(32) NOT NULL COMMENT "提示词哈希",
`completion_tokens` INT NOT NULL DEFAULT 0 COMMENT "tokens",
`latency_ms` INT NOT NULL DEFAULT 0 COMMENT "时延",
`status` VARCHAR(64) NOT NULL COMMENT "状态",
`error_code` VARCHAR(64) NOT NULL COMMENT "错误码",
`fallback_used` TINYINT(1) NOT NULL DEFAULT 0 COMMENT "走兜底",
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
PRIMARY KEY (`id`),
UNIQUE KEY `uk_lc_uid` (`tenant_id`, `call_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT="LLM 调用日志(超时/重试/限流/兜底留痕)";

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_agent_critic_run.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_agent_critic_run.dspy: START params_kw={dict(params_kw)}')
data = await pbl_agent_critic_run(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_agent_designer_run.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_agent_designer_run.dspy: START params_kw={dict(params_kw)}')
data = await pbl_agent_designer_run(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_agent_trace_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_agent_trace_list.dspy: START params_kw={dict(params_kw)}')
data = await pbl_agent_trace_list(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_agent_trace_write.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_agent_trace_write.dspy: START params_kw={dict(params_kw)}')
data = await pbl_agent_trace_write(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_approval_create.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_approval_create.dspy: START params_kw={dict(params_kw)}')
data = await pbl_approval_create(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_approval_decide.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_approval_decide.dspy: START params_kw={dict(params_kw)}')
data = await pbl_approval_decide(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_approval_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_approval_list.dspy: START params_kw={dict(params_kw)}')
data = await pbl_approval_list(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_tool_adjudicate.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_tool_adjudicate.dspy: START params_kw={dict(params_kw)}')
data = await pbl_tool_adjudicate(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_tool_registry_list.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_tool_registry_list.dspy: START params_kw={dict(params_kw)}')
data = await pbl_tool_registry_list(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_agent_runtime/api/pbl_tool_registry_save.dspy —— 契约端点(自动生成,勿手改:改 spec 后跑 gen_artifacts.py
debug('pbl_agent_runtime/api/pbl_tool_registry_save.dspy: START params_kw={dict(params_kw)}')
data = await pbl_tool_registry_save(**params_kw)
return data

315
wwwroot/index.ui Normal file
View File

@ -0,0 +1,315 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "20px"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "Designer/Critic Agent 运行时与 fail-closed 工具裁决M4a/M4b",
"fontSize": "24px"
}
},
{
"widgettype": "ResponsableBox",
"options": {
"gap": "16px",
"minWidth": "250px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_agent_designer_run.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_agent_designer_run"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_agent_critic_run.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_agent_critic_run"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_agent_trace_write.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_agent_trace_write"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_agent_trace_list.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_agent_trace_list"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_tool_registry_list.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_tool_registry_list"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_tool_registry_save.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_tool_registry_save"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_tool_adjudicate.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_tool_adjudicate"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_approval_create.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_approval_create"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_approval_decide.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_approval_decide"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"backgroundColor": "#FFFFFF",
"padding": "20px",
"cursor": "pointer"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.pbl_agent_runtime_content",
"options": {
"url": "{{entire_url('api/pbl_approval_list.dspy')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"label": "pbl_approval_list"
}
}
]
}
]
},
{
"widgettype": "VBox",
"id": "pbl_agent_runtime_content",
"options": {
"width": "100%",
"flex": "1",
"marginTop": "20px"
}
}
]
}