
Law
- 1 installs
- 6 repo stars
- Updated April 19, 2026
- ximilalaxiang/law
Helps with ai & agent building tasks.
About
law is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
- law
- AI & Agent Building
- AI-coding skill
Law by the numbers
- 1 all-time installs (skills.sh)
- Ranked #14,102 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ximilalaxiang/law --skill lawAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 6 |
| Last updated | April 19, 2026 |
| Repository | ximilalaxiang/law ↗ |
What it does
Helps with ai & agent building tasks.
Files
Law — Agent Operations Manual
免责声明: 本技能辅助法律工作流程,不构成正式法律意见。所有分析结果应由持证律师审核后方可依赖。重大事项请咨询执业律师。
Quality Bar
输出必须达到执业律师初稿水平:法条引用精确到条款项、案例引用包含案号、风险判断有依据支撑。不得出现无来源的法律结论。
Security
- API 凭证必须来自环境变量
LEGAL_APP_ID和LEGAL_SECRET。 - 绝不硬编码、日志记录或在输出中展示 API 密钥。
- 若环境变量未设置,告知用户并停止。
Dependencies
| Dependency | Required | Install |
|---|---|---|
| Python ≥ 3.9 | Yes | — |
requests | Yes | pip install -r scripts/requirements.txt |
python-docx | Yes (for template extraction) | 同上 |
pandoc OR markdown_to_docx_converter | For DOCX export | See pandoc.org |
| PowerShell ≥ 5.1 | For .ps1 scripts (Windows) | Built-in on Windows |
---
Environment Branching
| Environment | Capabilities | Adjustments |
|---|---|---|
| Claude Code / Amp | 可执行脚本、读写文件、运行 Python | 完整工作流,直接调用 scripts/ 中的脚本 |
| Claude.ai (Artifact mode) | 无文件系统、无脚本执行 | 跳过 Stage 2 API 调用,基于内置知识分析并明示"未经接口检索验证";文书以 Artifact Markdown 交付,跳过 DOCX 导出 |
| API / Headless | 无用户交互、无文件系统 | 一次性输出完整分析,不使用渐进展开;返回纯 Markdown |
---
Reading Guide
按用户意图按需加载,不要一次性全部读取:
| User wants… | Load these references |
|---|---|
| Quick daily consultation | references/workflow-and-intent.md → references/output-standard.md |
| Statute/regulation lookup | + references/law-query-standard.md |
| Similar case analysis | + references/case-analysis-standard.md |
| Lawsuit / win-rate / dispute | + references/judicial-reasoning-standard.md + references/evidence-gap-diagnostic-standard.md |
| Compare similar cases | + references/case-difference-explainer-standard.md |
| Evidence sufficiency check | + references/evidence-gap-diagnostic-standard.md + references/evidence-burden-matrix.md |
| Contract review | + references/document-processing-standard.md |
| Draft a legal document | + references/document-processing-standard.md → check assets/templates/ |
| Export to Word | + references/word-export-skill-integration.md |
脚本视为黑盒。先用 --help 查看用法,不要阅读脚本源码(除非调试失败)。
---
Workflow Overview
┌──────────────────────────────────────────────────────────────────┐
│ LAW SKILL WORKFLOW │
├──────────────────────────────────────────────────────────────────┤
│ Stage 0: PREFLIGHT CHECK │
│ ✓ 检查环境变量 LEGAL_APP_ID / LEGAL_SECRET │
│ ✓ 运行 health_check.py 验证文件完整性 │
│ ✓ 失败则引导用户配置 │
├──────────────────────────────────────────────────────────────────┤
│ Stage 1: CLASSIFY INTENT │
│ ✓ 判断管辖范围(仅限中国大陆法律) │
│ ✓ 补充关键事实(主动提问) │
│ ✓ 分级:light / deep │
│ ✓ 分类:law / case / both / document │
├──────────────────────────────────────────────────────────────────┤
│ Stage 2: RETRIEVE │
│ ✓ 调用法条/案例 API │
│ ✓ 重试 + 降级链 │
├──────────────────────────────────────────────────────────────────┤
│ Stage 3: COMPOSE RESPONSE │
│ ✓ Mode A (light) 或 Mode B (deep) │
│ ✓ 风险分级:🟢 低 / 🟡 中 / 🔴 高 │
│ ✓ 渐进展开 → 无缝升级 │
├──────────────────────────────────────────────────────────────────┤
│ Stage 4: DOCUMENT GENERATION (if applicable) │
│ ✓ 起草法律文书 + 导出 DOCX │
└──────────────────────────────────────────────────────────────────┘---
Stage 0 — Preflight Check
Goal: 确认运行环境就绪。 Exit condition: health_check.py 返回 {"ok": true} 且环境变量已设置。 Trigger: 仅在首次调用时执行,后续对话复用结果。
1. 检查环境变量 LEGAL_APP_ID 和 LEGAL_SECRET 是否已设置。 2. 运行 python scripts/health_check.py。 3. 若缺失环境变量 → 告知用户:
需要设置LEGAL_APP_ID和LEGAL_SECRET环境变量才能使用法律检索功能。请联系管理员获取 API 凭证。
4. 若缺失文件 → 告知用户哪些文件缺失,并建议重新安装 skill。 5. 在 Claude.ai 环境下跳过此步骤(无脚本执行能力)。
Stage 1 — Classify Intent
Goal: 判断用户需求、确认管辖范围、补充关键事实。 Exit condition: 内部生成结构化 intent JSON(格式见 references/workflow-and-intent.md),且关键事实无重大缺失。
1. 读取 references/workflow-and-intent.md(如未加载)。
2. 管辖范围判断:
- 若用户问题明确涉及港澳台法律、涉外法律(跨境合同/外国法适用),告知:
本技能仅覆盖中国大陆法律体系。港澳台及涉外法律问题建议咨询专业涉外律师。
- 涉及中国大陆法与涉外因素交叉时(如外企在大陆的劳动纠纷),按大陆法处理,但标注涉外风险。
3. 主动补充关键事实(Context Gathering): 当用户提供的信息不足以进行准确分析时,主动提出编号问题。根据纠纷类型选择提问:
| 纠纷类型 | 应收集的关键事实 |
|---|---|
| 通用 | ① 事情发生的时间线 ② 涉及金额 ③ 所在城市/省份(影响管辖和地方标准) |
| 劳动争议 | ④ 是否签劳动合同 ⑤ 在职时长 ⑥ 工资发放方式(银行/现金/微信) |
| 合同纠纷 | ④ 合同签署方式(书面/口头/电子) ⑤ 已履行部分 ⑥ 违约方及违约内容 |
| 侵权纠纷 | ④ 损害后果(人身/财产/精神) ⑤ 是否报警/就医 ⑥ 有无第三方在场 |
| 房屋租赁 | ④ 是否签租赁合同 ⑤ 押金金额及支付凭证 ⑥ 房屋现状 |
格式: 列出 3-5 个编号问题,用户可用简短回答(如 "1: 去年3月, 2: 5万, 3: 北京")。 若用户明确拒绝补充(如"你先分析"): 直接分析,标注 [事实待补充]。
4. 分级:
- Light (Mode A) — 默认。日常咨询、快速建议。
- Deep (Mode B) — 用户提及起诉、律师函、胜诉率、裁判倾向、证据是否充分等。
5. 分类意图: law | case | both | document
6. 在内部生成 intent JSON(不展示给用户)。
用户自主权: 若用户明确拒绝结构化分析(如"直接告诉我就行"),切换为自由对话模式——仍执行检索硬约束,但输出不强制使用模板结构。
If unclear: 默认 light + both。先给轻量回答,再提供渐进展开入口。
Stage 2 — Retrieve
Goal: 在得出任何法律结论之前获取权威数据。 Exit condition: 至少一个 API 返回有效结果,或双接口失败均已报告给用户。
| Intent | APIs to Call |
|---|---|
law | python scripts/query_law_api.py --query "…" --fetch-detail -o /tmp/law.json |
case | python scripts/query_case_api.py --query "…" -o /tmp/case.json |
both | 法规(带--fetch-detail -o)+ 案例(-o)并行调用 |
document | 纯起草跳过;需要法律依据时按 both 调用 |
重要:必须使用 `-o` 参数输出到文件! Windows 下 Python subprocess 管道传输会破坏 UTF-8 中文编码。使用 -o output.json 直接写文件可完全避免此问题,读取文件内容即可。
法规检索说明: queryListLaw 接口仅返回元数据,必须使用 --fetch-detail 参数触发两步式检索(列表→逐条调 lawInfo 详情接口合并完整正文),才能获取法条具体内容。
Hard constraints:
- ❌ 绝不在未成功检索时给出实质性法律结论
- ❌ 绝不将 web search 作为主要检索渠道
- ✅ 一个接口失败时使用另一个的结果,标注失败原因,提供重试选项
Retry & fallback chain: 1. 首次失败 → 自动重试一次(相同参数)。 2. 仍失败 → 换用精简关键词重试一次。 3. 仍失败 → 降级为单接口(仅法条或仅案例),标注降级原因。 4. 双接口全部失败 → 告知用户"检索接口暂时不可用",征得用户明确同意后方可使用 web search 作为降级补充。
Stage 3 — Compose Response
Goal: 按适当深度交付回答,包含风险分级。 Exit condition: 输出同时满足以下全部条件:
- 包含至少 1 条法条引用(含条款号)或 1 条案例引用(含案号)
- 包含检索状态行(✅/❌ + 条数)
- 包含风险分级标记(🟢/🟡/🔴)
- 末尾包含风险声明
- 通过
python scripts/validate_output.py ./output.md验证(仅在生成文件时执行)
风险分级系统
对每个风险点或建议事项标注分级:
| 等级 | 含义 | 建议行动 |
|---|---|---|
| 🟢 低风险 | 法律关系清晰,胜率高,操作简单 | 用户可自行处理,参考建议步骤即可 |
| 🟡 中风险 | 存在争议空间、证据可能不足、或涉及地方裁判差异 | 建议律师复核关键环节后再行动 |
| 🔴 高风险 | 可能败诉、涉及重大财产/人身权益、法律关系复杂 | 必须委托律师处理,不可自行操作 |
Mode A (Light) — 输出模板
## 结论
一句话回答问题。
## 你现在可以怎么做
1. 行动建议1 [🟢/🟡/🔴]
2. 行动建议2 [🟢/🟡/🔴]
3. 行动建议3(可选)
## 注意风险
- 🟡 风险提醒1
- 🔴 风险提醒2(可选)
## 需要的话可展开
- 📖 查看法律依据
- 📋 查看类似案例
- 🔍 检查我还缺哪些证据
---
检索状态:queryListLaw [✅/❌ + 条数] | queryListCase [✅/❌ + 条数]
> ⚠️ 风险提示:以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。- 模板文件:
assets/templates/light_consult_response.md - 末尾提供渐进展开选项(📖 法律依据 / 📋 类似案例 / 🔍 证据检查)。
- 用户展开时 → 无缝升级到 Mode B,复用已有检索结果,不重复调用 API。
Mode B (Deep) — 输出模板
# [案件类型] 分析报告
## 一、任务类型识别
- 意图:[case/law/both/document]
- 复杂度:deep
- 案由:[识别的案由]
- 整体风险评级:[🟢/🟡/🔴]
## 二、处理结果
[包含适用的深度模块:裁判思维模拟器 / 证据缺口诊断器 / 类案差异解释器]
## 三、检索结果清单(原始条目)
[法条和/或类案原始条目]
## 四、需补充信息(如有)
[缺失的关键信息,标注 [待补充]]
## 五、下一步建议
- 24小时内:🔴 [紧急行动]
- 3天内:🟡 [准备工作]
- 7天内:🟢 [中期行动]
## 六、检索执行信息
- queryListLaw:[状态 + 条数]
- queryListCase:[状态 + 条数]
- 检索词:[实际使用的检索关键词]
> ⚠️ 风险提示:以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。- 详细输出规范见
references/output-standard.md。 - 按条件激活深度模块:
| Condition | Modules |
|---|---|
| 诉讼 / 胜诉率 / 争议 | 裁判思维模拟器 + 证据缺口诊断器 |
| 返回类案结果 | + 类案差异解释器 |
| "证据够不够" | 证据缺口诊断器(可单独使用) |
| 合同审查 | 合同审查(→ Stage 4) |
- 仅加载所需的深度模块参考文件(见 Reading Guide)。
多轮对话状态管理: 渐进展开或追问时,复用已有检索结果和 intent JSON,不重复调用 API。仅在用户提出新事实或新问题时重新检索。
Stage 4 — Document Generation & Export (if applicable)
Goal: 生成完整法律文书并导出 DOCX。 触发条件: Stage 3 产出需交付文件(起诉状、律师函、合同审查报告等)。 Exit condition: 文书文件已导出(.docx 或 .md 兜底),路径已告知用户。
1. 读取 references/document-processing-standard.md,按其规则起草文书。 2. 查找模板:assets/templates/official/ → assets/templates/official/samr/ → assets/templates/。 3. 标记所有缺失信息为 [待补充]。 4. 读取 references/word-export-skill-integration.md,按其流程导出 .docx。 5. 输出导出路径与状态,失败时提供原始 Markdown 作为兜底。
---
Error Handling
脚本失败时的统一处理流程:
| 场景 | 处理方式 |
|---|---|
| 脚本返回非零退出码 | 读取 stderr 输出,向用户报告错误原因 |
脚本返回 {"ok": false, "error": "..."} | 提取 error 字段内容展示给用户 |
| 脚本超时无响应(>30s) | 终止并告知用户"接口响应超时",按 Retry 链处理 |
| JSON 解析失败 | 展示原始输出前 200 字符,提示用户可能是接口异常 |
| 环境变量缺失 | 不尝试调用,直接提示配置步骤 |
通用原则: 不吞没错误,不猜测结果。失败时如实告知用户并提供可操作的下一步。
---
Prohibited
| ❌ Never | ✅ Instead |
|---|---|
| 编造法条、案号或裁判结果 | 仅引用检索返回的结果并标注来源 |
| 写"可直接套用判决结果" | 写"可援引"并说明边界条件 |
| 写"已证明事实" | 证据仅佐证时写"可补强" |
| 写"确定判决" | 写"裁判倾向预测" |
| 跳过检索直接从记忆给出法律结论 | 始终先检索 |
| 将 web search 作为主检索渠道 | 使用 API 脚本;web search 仅为用户同意的降级补充 |
| 暴露 API 凭证 | 凭证仅从环境变量获取 |
| 对港澳台/涉外法律问题直接给出结论 | 告知用户本技能仅覆盖大陆法律,建议咨询涉外律师 |
---
Directory Index
| Directory | Contents | Index |
|---|---|---|
references/ | 规则标准与参考文档 | references/README.md |
assets/templates/ | 可复用文书模板 | assets/README.md |
scripts/ | API 脚本、导出工具、实用程序 | scripts/README.md |
Assets
templates/ 目录存放可复用模板:
light_consult_response.md(日常咨询模式)civil_complaint.mdlawyer_letter.mdcontract_review_report.mdcase_analysis_report.mdlaw_query_report.mdjudicial_reasoning_simulator.mdcase_difference_explainer.mdevidence_gap_diagnostic.md
templates/official/ 存放网上收集并校验过的官方示范 .docx 模板:
- 离婚纠纷起诉状(官方示范)
- 买卖合同纠纷起诉状(官方示范)
- 劳动争议纠纷起诉状(官方示范)
- 部分案件起诉状答辩状示范文本(67类官方汇编)
类案检索与裁判趋势分析
一、结论概览(总)
- 一句话结论:{{one_line_conclusion}}
- 裁判趋势:{{trend}}
- 预计胜诉率:{{win_rate_range}}
- 关键成败因素:{{key_factors}}
二、类案拆解(分)
类案1
- 基本信息:{{case_1_info}}
- 裁判要旨:{{case_1_rule}}
- 对本案启示:{{case_1_hint}}
类案2
- 基本信息:{{case_2_info}}
- 裁判要旨:{{case_2_rule}}
- 对本案启示:{{case_2_hint}}
三、检索结果清单(原始条目)
| 序号 | 案号 | 法院 | 裁判日期 | 案由 | 核心裁判要旨 | 关联度说明 |
|---|---|---|---|---|---|---|
| 1 | {{case_no_1}} | {{court_1}} | {{date_1}} | {{cause_1}} | {{holding_1}} | {{relevance_1}} |
| 2 | {{case_no_2}} | {{court_2}} | {{date_2}} | {{cause_2}} | {{holding_2}} | {{relevance_2}} |
四、本案策略建议(总)
- 维权路径:{{path}}
- 证据补强清单:{{evidence_todo}}
- 反方可能抗辩点:{{counter_args}}
- 应对要点:{{responses}}
五、下一步行动清单
- 24小时内:{{todo_24h}}
- 3天内:{{todo_3d}}
- 7天内:{{todo_7d}}
六、检索执行信息
- 主检索功能:{{primary_retrieval}}
- 双接口执行状态:{{retrieval_status}}
- fallback检索功能:{{fallback}}
- 说明:{{retrieval_note}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
类案差异解释器报告
一、本案结构化标签
- 法律关系类型:{{case_relation_type}}
- 争议焦点:{{case_focuses}}
- 关键条款:{{case_key_clauses}}
- 履行状态:{{case_performance_status}}
- 证据完整度:{{case_evidence_completeness}}
- 损失证明强度:{{case_damage_proof_strength}}
二、类案对比表
| 对比维度 | 本案 | 类案A | 类案B |
|---|---|---|---|
| 法律关系类型 | {{case_relation_type}} | {{a_relation_type}} | {{b_relation_type}} |
| 争议焦点 | {{case_focuses}} | {{a_focuses}} | {{b_focuses}} |
| 违约金约定 | {{case_liquidated_damages}} | {{a_liquidated_damages}} | {{b_liquidated_damages}} |
| 履行状态 | {{case_performance_status}} | {{a_performance_status}} | {{b_performance_status}} |
| 证据完整度 | {{case_evidence_completeness}} | {{a_evidence_completeness}} | {{b_evidence_completeness}} |
| 损失证据强度 | {{case_damage_proof_strength}} | {{a_damage_proof_strength}} | {{b_damage_proof_strength}} |
三、相似点与关键差异
类案A
- 相似点:{{a_similarities}}
- 关键差异:{{a_differences}}
- 差异影响等级:{{a_impact_level}}
- 可援引程度:{{a_citability}}
- 可援引部分:{{a_can_cite}}
- 不宜援引部分:{{a_cannot_cite}}
类案B
- 相似点:{{b_similarities}}
- 关键差异:{{b_differences}}
- 差异影响等级:{{b_impact_level}}
- 可援引程度:{{b_citability}}
- 可援引部分:{{b_can_cite}}
- 不宜援引部分:{{b_cannot_cite}}
四、结论
- 综合可援引策略:{{overall_citation_strategy}}
- 律师实务提示:{{practice_tips}}
五、检索执行信息
- 主检索功能:{{primary_retrieval}}
- 双接口执行状态:{{retrieval_status}}
- fallback检索功能:{{fallback}}
- 说明:{{retrieval_note}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
民事起诉状(模板)
一、当事人信息
- 原告:{{plaintiff}}
- 被告:{{defendant}}
- 第三人(如有):{{third_party_optional}}
二、诉讼请求
1. {{claim_1}} 2. {{claim_2}} 3. {{claim_3_optional}}
三、事实与理由
{{facts_and_reasons}}
四、证据清单
1. {{evidence_1}} 2. {{evidence_2}} 3. {{evidence_3_optional}}
五、管辖法院
{{court_name}}
六、待补充信息(如有)
- {{missing_info_1_optional}}
- {{missing_info_2_optional}}
此致 {{court_name}}
具状人:{{plaintiff_or_agent}} 日期:{{date}}
合同审查报告
一、一句话结论
{{one_line_conclusion}}
二、风险条款清单
风险点1
- 条款摘录:{{clause_1}}
- 风险等级:{{level_1}}
- 风险说明:{{risk_1}}
- 修改建议:{{fix_1}}
- 可替换条款:{{replacement_1}}
风险点2
- 条款摘录:{{clause_2}}
- 风险等级:{{level_2}}
- 风险说明:{{risk_2}}
- 修改建议:{{fix_2}}
- 可替换条款:{{replacement_2}}
三、需补充信息(如有)
- {{missing_info_1_optional}}
- {{missing_info_2_optional}}
四、下一步行动清单
- 24小时内:{{todo_24h}}
- 3天内:{{todo_3d}}
- 7天内:{{todo_7d}}
五、检索执行信息(如有)
- 主检索功能:{{primary_retrieval_optional}}
- 双接口执行状态:{{retrieval_status_optional}}
- fallback检索功能:{{fallback_optional}}
- 说明:{{retrieval_note_optional}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
证据缺口诊断报告
一、案件与案由识别
- 案由:{{case_type}}
- 争议核心:{{core_dispute}}
二、当前已有证据
| 证据名称 | 对应待证事实 | 证明力评级 |
|---|---|---|
| {{existing_evidence_1}} | {{fact_link_1}} | {{strength_1}} |
| {{existing_evidence_2}} | {{fact_link_2}} | {{strength_2}} |
三、关键待证事实
1. {{fact_to_prove_1}} 2. {{fact_to_prove_2}} 3. {{fact_to_prove_3_optional}}
四、缺失证据与风险诊断
| 待证事实 | 缺失证据 | 风险等级 | 补强优先级 |
|---|---|---|---|
| {{fact_to_prove_1}} | {{missing_evidence_1}} | {{risk_level_1}} | {{priority_1}} |
| {{fact_to_prove_2}} | {{missing_evidence_2}} | {{risk_level_2}} | {{priority_2}} |
五、下一步取证建议
- P0(立即):{{action_p0}}
- P1(3天内):{{action_p1}}
- P2(7天内):{{action_p2}}
六、败诉风险触发点
- {{loss_trigger_1}}
- {{loss_trigger_2}}
七、检索执行信息
- 主检索功能:{{primary_retrieval}}
- 双接口执行状态:{{retrieval_status}}
- fallback检索功能:{{fallback}}
- 说明:{{retrieval_note}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
裁判思维模拟器报告
一、案件事实(左侧)
{{facts_summary}}
二、争议焦点与裁判倾向(右侧)
| 争议焦点 | 支持原告概率 | 支持被告概率 | 关键决定因素 |
|---|---|---|---|
| {{focus_1}} | {{plaintiff_prob_1}} | {{defendant_prob_1}} | {{key_factor_1}} |
| {{focus_2}} | {{plaintiff_prob_2}} | {{defendant_prob_2}} | {{key_factor_2}} |
| {{focus_3_optional}} | {{plaintiff_prob_3_optional}} | {{defendant_prob_3_optional}} | {{key_factor_3_optional}} |
三、焦点逐项推理
争议焦点1:{{focus_1}}
- 原告论点:{{plaintiff_argument_1}}
- 原告支持证据:{{plaintiff_evidence_1}}
- 原告可能被反驳点:{{plaintiff_rebuttal_risk_1}}
- 原告薄弱点:{{plaintiff_weakness_1}}
- 被告论点:{{defendant_argument_1}}
- 被告支持证据:{{defendant_evidence_1}}
- 被告可能被反驳点:{{defendant_rebuttal_risk_1}}
- 被告薄弱点:{{defendant_weakness_1}}
- 法官可能采纳路径:{{judge_path_1}}
争议焦点2:{{focus_2}}
- 原告论点:{{plaintiff_argument_2}}
- 原告支持证据:{{plaintiff_evidence_2}}
- 原告可能被反驳点:{{plaintiff_rebuttal_risk_2}}
- 原告薄弱点:{{plaintiff_weakness_2}}
- 被告论点:{{defendant_argument_2}}
- 被告支持证据:{{defendant_evidence_2}}
- 被告可能被反驳点:{{defendant_rebuttal_risk_2}}
- 被告薄弱点:{{defendant_weakness_2}}
- 法官可能采纳路径:{{judge_path_2}}
四、类案比较与规则适用
- 适用规则:{{applied_rules}}
- 类案比较结论:{{case_comparison}}
五、裁判倾向预测图
flowchart TD
A[案件事实] --> B[争议焦点拆解]
B --> C1[焦点1: {{focus_1}}]
B --> C2[焦点2: {{focus_2}}]
C1 --> D1[倾向: 原告 {{plaintiff_prob_1}} / 被告 {{defendant_prob_1}}]
C2 --> D2[倾向: 原告 {{plaintiff_prob_2}} / 被告 {{defendant_prob_2}}]
D1 --> E[综合裁判倾向: {{final_tendency}}]
D2 --> E六、下一步行动清单
- 24小时内:{{todo_24h}}
- 3天内:{{todo_3d}}
- 7天内:{{todo_7d}}
七、检索执行信息
- 主检索功能:{{primary_retrieval}}
- 双接口执行状态:{{retrieval_status}}
- fallback检索功能:{{fallback}}
- 说明:{{retrieval_note}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
法条检索与适用指引
一、一句话结论
{{one_line_conclusion}}
二、检索结果(按效力位阶)
法律
- 条文名称与条号:{{law_title_1}}
- 条文原文:{{law_text_1}}
行政法规
- 条文名称与条号:{{reg_title_1}}
- 条文原文:{{reg_text_1}}
地方法规
- 条文名称与条号:{{local_title_1}}
- 条文原文:{{local_text_1}}
三、检索结果清单(原始条目)
| 序号 | 法规名称 | 条号 | 发布机关 | 效力层级 | 条文原文 |
|---|---|---|---|---|---|
| 1 | {{law_name_1}} | {{article_no_1}} | {{issuer_1}} | {{level_1}} | {{article_text_1}} |
| 2 | {{law_name_2}} | {{article_no_2}} | {{issuer_2}} | {{level_2}} | {{article_text_2}} |
四、结合本案的落地解读
- 适用前提:{{preconditions}}
- 支持点:{{supports}}
- 不利点:{{risks}}
- 证据与程序建议:{{evidence_and_procedure}}
五、下一步行动清单
- 24小时内:{{todo_24h}}
- 3天内:{{todo_3d}}
- 7天内:{{todo_7d}}
六、检索执行信息
- 主检索功能:{{primary_retrieval}}
- 双接口执行状态:{{retrieval_status}}
- fallback检索功能:{{fallback}}
- 说明:{{retrieval_note}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
律师函(模板)
致:{{recipient}}
一、委托说明
{{entrustment}}
二、事实陈述
{{facts}}
三、法律依据
{{legal_basis}}
四、正式要求
1. {{demand_1}} 2. {{demand_2}} 3. {{demand_3_optional}}
五、期限与后果
请于 {{deadline}} 前完成上述事项,否则我方将依法采取进一步法律措施。
六、待补充信息(如有)
- {{missing_info_1_optional}}
- {{missing_info_2_optional}}
律师:{{lawyer_name}} 律师事务所:{{law_firm}} 日期:{{date}}
日常咨询快答(轻量模式)
结论
{{one_line_conclusion}}
你现在可以怎么做
1. {{action_1}} 2. {{action_2}} 3. {{action_3_optional}}
注意风险
- {{risk_1}}
- {{risk_2_optional}}
需要的话可展开
- 查看法律依据:{{law_basis_hint}}
- 查看类似案例:{{case_hint}}
- 检查我还缺哪些证据:{{evidence_gap_hint}}
检索执行信息(简版)
- queryListLaw:{{law_status}}
- queryListCase:{{case_status}}
- 说明:{{retrieval_note}}
风险提示
以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。
Official Templates
该目录保存已导入并可复用的官方 .docx 模板。
使用规则:
- 生成文书时优先匹配本目录模板。
- 命中后先执行
scripts/extract_docx_text.py读取模板正文,再进行字段映射填充。 - 若读取失败或无匹配模板,再回退到
assets/templates/通用模板。
SAMR Official Template Cache
该目录用于缓存从 SAMR(国家市场监督管理总局合同示范文本库)按需下载的官方模板。
生成方式
- 先执行
scripts/import_contract_library.py生成标准化模板索引: references/samr_contract_templates.json- 再执行
scripts/fetch_samr_template.py下载具体模板: - 示例:
python scripts/fetch_samr_template.py --keyword 商品房买卖合同 --type word
使用规则
- 本目录内模板优先级高于通用
assets/templates/模板。 - 命中模板后,先通过
scripts/extract_docx_text.py提取正文,再做字段映射填充。 - 任何“生成文件”流程仍需最终调用
scripts/export_to_docx.ps1导出目标.docx。
Law
一个 AI Agent Skill,为日常法律咨询与诉讼分析提供专业级智能辅助。基于真实法律 API 检索,覆盖中国大陆法律体系。
⚠️ 免责声明: 本技能辅助法律工作流程,不构成正式法律意见。所有分析结果应由持证律师审核后方可依赖。
---
✨ 功能亮点
四阶段智能工作流
┌──────────────────────────────────────────────────────────────┐
│ USER: "房东不退押金怎么办?" │
├──────────────────────────────────────────────────────────────┤
│ Stage 1 CLASSIFY ─ 意图识别 + 关键事实补全 │
│ → 租赁纠纷 / light / both │
│ │
│ Stage 2 RETRIEVE ─ 法条 API + 案例 API 并行检索 │
│ → 《民法典》第714条 + 3条类案 │
│ │
│ Stage 3 COMPOSE ─ 风险分级 + 结构化输出 │
│ → 🟢 低风险:可自行处理 │
│ → 🟡 中风险:建议律师复核 │
│ → 🔴 高风险:必须委托律师 │
│ │
│ Stage 4 DOCUMENT ─ 法律文书生成 + DOCX 导出 │
│ → 起诉状.docx ✅ │
└──────────────────────────────────────────────────────────────┘双模式自适应响应
| 模式 | 适用场景 | 输出内容 |
|---|---|---|
| Light (轻量) | 日常咨询、快速建议 | 一句话结论 + 行动建议 + 渐进展开入口 |
| Deep (深度) | 诉讼分析、胜诉率评估 | 裁判思维模拟 + 证据缺口诊断 + 类案差异分析 |
轻量回答末尾可一键展开为深度分析,复用已有检索结果,不重复调用 API。
---
📦 项目结构
Law/
├── SKILL.md # Agent 操作手册(AI 读取入口)
├── README.md # 本文件
├── references/ # 规则标准与参考文档(14 份)
│ ├── workflow-and-intent.md # 意图识别与复杂度分级
│ ├── judicial-reasoning-standard.md # 裁判思维模拟器标准
│ ├── evidence-gap-diagnostic-standard.md # 证据缺口诊断标准
│ ├── evidence-burden-matrix.md # 案由举证要件矩阵
│ ├── case-analysis-standard.md # 类案检索与分析标准
│ ├── document-processing-standard.md # 合同审查与文书生成标准
│ └── ...
├── assets/
│ └── templates/ # 可复用文书模板
│ ├── civil_complaint.md # 民事起诉状
│ ├── lawyer_letter.md # 律师函
│ ├── contract_review_report.md # 合同审查报告
│ ├── evidence_gap_diagnostic.md # 证据缺口诊断
│ └── official/ # 官方示范模板(.docx)
│ ├── 离婚纠纷起诉状_官方示范.docx
│ ├── 劳动争议纠纷起诉状_官方示范.docx
│ ├── 买卖合同纠纷起诉状_官方示范.docx
│ ├── 部分案件起诉状答辩状示范文本_67类_官方汇编.docx
│ └── samr/ # 市监局合同示范文本
├── scripts/ # API 脚本与工具
│ ├── query_law_api.py # 法条检索
│ ├── query_case_api.py # 案例检索
│ ├── validate_output.py # 输出结构校验
│ ├── export_to_docx.ps1 # Markdown → Word 导出
│ ├── health_check.py # 环境完整性检查
│ ├── fetch_samr_template.py # 市监局模板按需下载
│ └── ...
└── .git/---
🚀 安装
前置依赖
| 依赖 | 必需 | 安装方式 |
|---|---|---|
| Python ≥ 3.9 | ✅ | winget install Python.Python.3.12 |
requests | ✅ | pip install -r scripts/requirements.txt |
python-docx | ✅ | 同上 |
| pandoc | 可选(DOCX 导出) | pandoc.org |
安装到 Claude Code / Amp
方法 1:直接克隆到全局 skills 目录
git clone https://github.com/XimilalaXiang/Law.git ~/.claude/skills/law方法 2:手动复制
将整个 Law/ 目录复制到 ~/.claude/skills/law/ 或 ~/.agents/skills/law/。
配置 API 凭证
# 设置环境变量(必须)
export LEGAL_APP_ID="your_app_id"
export LEGAL_SECRET="your_secret"Windows PowerShell:
$env:LEGAL_APP_ID = "your_app_id"
$env:LEGAL_SECRET = "your_secret"验证安装
python scripts/health_check.py返回 {"ok": true} 即安装成功。
---
💬 使用示例
安装后在 Claude Code / Amp 中直接对话即可自动触发:
帮我看看这个合同有没有问题房东不退押金,我能告他吗?上班路上出了车祸算工伤吗?帮我写一份民事起诉状这种情况胜诉率有多大?需要准备哪些证据?进阶用法
# 法条检索
python scripts/query_law_api.py --query "深圳市房地产相关的法律规定有哪些?"
# 案例检索
python scripts/query_case_api.py --query "上班途中车祸工伤案例"
# 输出校验
python scripts/validate_output.py ./result.md
# Word 导出
powershell -ExecutionPolicy Bypass -File scripts/export_to_docx.ps1 -InputMarkdown .\result.md -OutputDocx .\result.docx
# 市监局合同模板下载
python scripts/fetch_samr_template.py --keyword 商品房买卖合同 --type word---
🔧 核心能力
法律检索引擎
| 接口 | 脚本 | 数据来源 |
|---|---|---|
法条检索 queryListLaw | query_law_api.py | 法律法规数据库 |
案例检索 queryListCase | query_case_api.py | 裁判文书数据库 |
检索硬约束: 绝不在未成功检索时给出实质性法律结论。API 失败时自动重试 → 精简关键词重试 → 单接口降级 → 征得用户同意后 web search 补充。
深度分析模块
| 模块 | 触发条件 | 说明 |
|---|---|---|
| 裁判思维模拟器 | 用户提及诉讼/胜诉率 | 模拟法官视角分析争议焦点与裁判倾向 |
| 证据缺口诊断器 | 用户问"证据够不够" | 诊断缺失证据、评估风险、给出补强建议 |
| 类案差异解释器 | 检索返回类案结果时 | 对比本案与类案的关键差异及影响 |
| 合同审查 | 用户要求审查合同 | 条款风险扫描 + 修改建议 |
文书生成
内置 9 种文书模板 + 67 类官方示范文本:
| 模板 | 文件 |
|---|---|
| 日常咨询回复 | light_consult_response.md |
| 民事起诉状 | civil_complaint.md |
| 律师函 | lawyer_letter.md |
| 合同审查报告 | contract_review_report.md |
| 案例分析报告 | case_analysis_report.md |
| 法条查询报告 | law_query_report.md |
| 裁判思维模拟报告 | judicial_reasoning_simulator.md |
| 类案差异分析 | case_difference_explainer.md |
| 证据缺口诊断 | evidence_gap_diagnostic.md |
所有文书支持导出为 .docx 格式。
风险分级系统
每个分析结论和行动建议均标注风险等级:
| 等级 | 含义 | 建议行动 |
|---|---|---|
| 🟢 低风险 | 法律关系清晰,胜率高 | 可自行处理 |
| 🟡 中风险 | 存在争议空间或证据不足 | 建议律师复核 |
| 🔴 高风险 | 可能败诉或涉及重大权益 | 必须委托律师 |
---
🌍 适用范围
- ✅ 中国大陆法律体系(民法、劳动法、合同法、侵权法等)
- ❌ 不覆盖港澳台法律
- ❌ 不覆盖涉外/跨境法律(涉及大陆法部分除外)
- ❌ 不覆盖非中国法律(US、EU、日本等)
---
🤝 贡献
1. Fork 本仓库 2. 在 references/ 中添加或完善规则标准 3. 在 assets/templates/ 中补充文书模板 4. 运行 python scripts/health_check.py 确认文件完整性 5. 提交 PR
---
📄 License
MIT License
Adaptive Response Standard (Dual-Mode)
Product concept
- 名称:分层响应式法律 agent(问题复杂度自适应法律辅助)
- 目标:轻问题快答,重问题深析,避免一上来冗长输出。
Mode switch policy
- 默认 light。
- 满足深度触发条件后切到 deep。
- 支持“先 light 后 deep”的渐进展开。
Deep trigger checklist
任一命中即 deep:
- 用户提到“起诉/律师函/起诉状/胜诉率”
- 用户要求争议焦点分析或裁判倾向
- 用户要求证据缺口诊断
- 用户要求类案差异可援引分析
Light output constraints
- 总长度短、可执行、低负担
- 建议条目优先 2~4 条
- 默认不展开深度推理细节
UI hint text (for demo)
- 查看法律依据
- 查看类似案例
- 检查我还缺哪些证据
Case Analysis Standard
API
- URL:
https://openapi.delilegal.com/api/qa/v3/search/queryListCase - Method:
POST - Stream mode: 非流式
- Auth: Header 鉴权(
appid+secret),凭据来自环境变量LEGAL_APP_ID/LEGAL_SECRET - Headers:
Content-Type: application/jsonappid: ${LEGAL_APP_ID}secret: ${LEGAL_SECRET}
Request body rules
- 默认参数:
pageNo=1,pageSize=5,sortField=correlation,sortOrder=desc sortField:correlation或timesortOrder:asc或desccondition.caseYearStart/condition.caseYearEnd: 可选裁判年份区间condition.courtLevelArr:"0": 最高法院"1": 高级法院"2": 中级法院"3": 基层人民法院condition.keywordArr与condition.longText通常二选一condition.judgementTypeArr:"30": 判决书"31": 裁定书"32": 调解书"33": 决定书"34": 通知书"99": 其他
Analysis format
使用“总-分-总”结构: 1. 总:裁判趋势、胜诉率区间、关键影响因素 2. 分:逐条类案拆解(裁判要旨、与本案关联点) 3. 总:维权路径、证据补强、应对建议
Win-rate rule
- 仅给区间,不给绝对值
- 示例:
45%-60% - 必须说明影响因素
Case Difference Explainer Standard
Goal
不是只给“相似类案”,而是解释“为什么该类案可援引/不可直接套用”。
Required steps
1. 抽取本案结构化事实标签。 2. 抽取类案结构化事实标签。 3. 输出相似点与关键差异。 4. 评估差异对裁判结论的影响程度(高/中/低)。 5. 输出可援引程度(高/中/低)与援引边界。
Recommended tag dimensions
- 法律关系类型(买卖、租赁、劳动、侵权等)
- 争议焦点类型(违约责任、损失范围、关系成立等)
- 关键合同条款(违约金、解除条件、付款节点等)
- 履行状态(未履行/部分履行/完全履行)
- 证据完整度(高/中/低)
- 损失证明强度(高/中/低)
- 程序性行为(催告、通知、协商记录)
Output requirements
每个类案必须给出:
- 相似点(至少2项)
- 关键差异(至少2项)
- 差异影响等级(高/中/低)
- 可援引程度(高/中/低)
- 可援引部分 / 不宜援引部分
Prohibited
- 不得仅写“相似度高/低”而不解释原因。
- 不得把“可援引”误写成“可直接套用判决结果”。
Document Processing Standard
Contract review
必须识别以下风险类型:
- 霸王条款
- 权利义务不对等条款
- 免责或责任过度限制条款
- 违约责任明显失衡条款
- 自动续约、单方变更、争议解决不公平条款
每个风险点必须包含:
- 条款摘录
- 风险等级(高/中/低)
- 风险说明
- 修改建议
- 可替换条款(可直接粘贴)
Drafting
若起草《民事起诉状》,至少包含:
- 原告信息(缺失则标注“待补充”)
- 被告信息(缺失则标注“待补充”)
- 诉讼请求
- 事实与理由
- 证据清单
Missing-data behavior
- 信息不足时继续输出初稿,不中断
- 额外附“待补充信息清单”
Template priority
查找模板的优先顺序: 1. assets/templates/official/ — 官方法院 .docx 模板 2. assets/templates/official/samr/ — SAMR 合同模板(可通过 fetch_samr_template.py 按需下载) 3. assets/templates/ — 通用 Markdown 模板
Pre-export checklist
交付前逐项检查:
- [ ] 当事人名称前后一致
- [ ] 金额、日期数字一致
- [ ] 管辖法院名称完整
- [ ] 附件/证据清单完整
- [ ]
[待补充]标记已标注所有缺失信息
Auto DOCX rule
- 只要本流程生成了任何交付文件(报告、起诉状、律师函等),必须自动导出
.docx。 - 回复中必须包含导出路径与导出状态。
DOCX Export Standard
Conversion tool
- Tool name:
markdown_to_docx_converter
Markdown requirements for clean conversion
- 仅使用
#/##/###标题层级 - 表格保持简洁(避免复杂合并单元格)
- 列表尽量单层
- 证据清单使用平铺 bullet
Before export checklist
- 当事人名称一致
- 金额、日期一致
- 管辖法院名称完整
- 附件清单完整
Evidence Burden Matrix (Demo)
用于“证据缺口诊断器”的案由举证要件清单。可持续扩展。
民间借贷纠纷
- 待证事实:借贷合意存在
- 关键证据:借条/欠条、聊天记录、录音
- 待证事实:款项实际交付
- 关键证据:银行流水原件、转账凭证、收条
- 待证事实:催收与逾期状态
- 关键证据:催款记录、律师函送达凭证、通话记录
买卖合同纠纷
- 待证事实:合同关系成立
- 关键证据:合同文本、订单、对账单
- 待证事实:履行与违约事实
- 关键证据:发货单/签收单、验收单、往来函件
- 待证事实:损失范围及金额
- 关键证据:发票、损失明细、审计或评估材料
劳动争议
- 待证事实:劳动关系成立
- 关键证据:劳动合同、考勤记录、工资流水、工牌工服
- 待证事实:解除/辞退事实与程序
- 关键证据:解除通知、谈话记录、会议纪要
- 待证事实:加班/补偿计算基础
- 关键证据:排班表、打卡记录、工资条
房屋租赁合同纠纷
- 待证事实:租赁关系与条款
- 关键证据:租赁合同、补充协议、付款约定
- 待证事实:租金/押金支付与拖欠
- 关键证据:转账记录、收据、催缴通知
- 待证事实:房屋交付/损坏情况
- 关键证据:交接清单、照片视频、维修票据
Evidence Gap Diagnostic Standard
Goal
不仅给法律结论,还要指出“还缺什么证据、缺口会造成什么败诉风险、如何补强”。
Required output blocks
1. 当前已有证据 2. 关键待证事实 3. 缺失证据 4. 证据强弱评级 5. 下一步取证建议 6. 败诉风险触发点(哪些缺口会显著提高败诉风险)
Required workflow
1. 根据案由匹配举证要件模板(参考 evidence-burden-matrix.md) 2. 将用户现有材料映射到“已覆盖要件” 3. 输出未覆盖要件及对应缺失证据 4. 对每个缺口给风险等级(高/中/低)和补强优先级(P0/P1/P2)
Rating rule
- 证据强弱评级:强 / 中 / 弱
- 风险等级:高 / 中 / 低
- 仅当关键待证事实已有直接证据链,才可判“强”
Prohibited
- 不得只列证据名称而不说明对应待证事实。
- 不得把“可补强”写成“已证明”。
Judicial Reasoning Simulator Standard
Goal
在“法条 + 类案”基础上,模拟法官的司法推理过程,而非仅罗列检索结果。
Required reasoning chain
必须按以下顺序输出: 1. 事实抽取 2. 争议焦点拆解 3. 适用规则(法条) 4. 原告/被告双方主张 5. 类案比较 6. 裁判倾向 7. 风险提示
Focus-level requirements
每个争议焦点必须包含:
- 焦点描述
- 支持原告概率(区间)
- 支持被告概率(区间)
- 关键决定因素
- 原告论点(支持证据 / 反驳点 / 薄弱点)
- 被告论点(支持证据 / 反驳点 / 薄弱点)
- 法官可能采纳路径
Probability rule
- 仅输出概率区间,不输出绝对确定值。
- 原告与被告概率区间应可解释,不允许拍脑袋数字。
- 概率判断必须绑定“决定因素 + 类案比较”。
Visualization rule
必须提供“裁判倾向预测图”,二选一:
- Mermaid 流程图
- 结构化对比表(焦点 x 双方概率 x 决定因素)
Prohibited
- 不得把“倾向预测”写成“确定判决”。
- 不得省略双方论点中的薄弱点分析。
Law Query Standard
API
- 列表接口:
https://openapi.delilegal.com/api/qa/v3/search/queryListLaw - Method:
POST - 返回法规元数据(名称、效力等级、发布机关等),content 字段为空
- 详情接口:
https://openapi.delilegal.com/api/qa/v3/search/lawInfo?lawId={law_id}&merge=true - Method:
GET - 参数:
lawId来自列表接口返回的 id 字段;merge=true表示合并内容不作拆分 - 返回完整法规正文:
body.lawDetailContent字段 - Stream mode: 非流式
- Auth: Header 鉴权(
appid+secret),凭据来自环境变量LEGAL_APP_ID/LEGAL_SECRET - Headers:
Content-Type: application/jsonappid: ${LEGAL_APP_ID}secret: ${LEGAL_SECRET}
检索流程 (两步式)
Step 1: queryListLaw → 获取法规列表(含 id, title, levelName 等)
↓ 遍历每条记录的 id
Step 2: lawInfo(lawId) → 获取每条法规的完整正文内容
↓ 合并输出
Result: 包含元数据 + 完整正文的增强结果数组脚本调用方式
# 模式1: 仅列表(快速)
python scripts/query_law_api.py --query "民法典租赁合同"
# 模式2: 列表+详情(推荐,获取完整法条内容)
python scripts/query_law_api.py --query "民法典租赁合同" --fetch-detail
python scripts/query_law_api.py --query "劳动法" --page-size 10 --fetch-detail --max-details 5Request body rules
- 默认参数:
pageNo=1,pageSize=5,sortField=correlation,sortOrder=desc condition.keywords: 必填数组(关键词或语义问题)condition.fieldName:title: 关键词检索semantic: 语义检索- 默认:
title
Interpretation standard
- 法条原文必须准确,不得改写原文内容
- 按效力位阶排序展示:
- 法律
- 行政法规
- 地方法规(含地方性法规、地方政府规章)
- 每条法条后必须给出场景化解读:
- 适用前提
- 支持点
- 不利点
- 举证建议
官方模板来源(已导入)
以下模板已下载并存放在 assets/templates/official/,用于优先复用:
1. 离婚纠纷起诉状(官方示范)
- 来源页面:https://www.sdcourt.gov.cn/jnanszqfy/389659/389660/389662/19798584/index.html
- 直链:https://www.sdcourt.gov.cn/jnanszqfy/389659/389660/389662/19798584/2024042816414995261.docx
2. 买卖合同纠纷起诉状(官方示范)
- 来源页面:https://www.sdcourt.gov.cn/jnanszqfy/389659/389660/389662/19798584/index.html
- 直链:https://www.sdcourt.gov.cn/jnanszqfy/389659/389660/389662/19798584/2024042816445948149.docx
3. 劳动争议纠纷起诉状(官方示范)
- 来源页面:https://www.sdcourt.gov.cn/jnanszqfy/389659/389660/389662/19798584/index.html
- 直链:https://www.sdcourt.gov.cn/jnanszqfy/389659/389660/389662/19798584/2024042816464583814.docx
4. 部分案件起诉状答辩状示范文本(67类官方汇编)
- 来源页面:https://www.ftcourt.gov.cn/ssfw/sszn/content/post_1577086.html
- 直链:https://www.ftcourt.gov.cn/attachment/0/92/92645/1577086.docx
- 上位通知(最高法):https://www.court.gov.cn/fabu/xiangqing/468671.html
使用说明
- 文书生成时,优先匹配
assets/templates/official/中对应案由模板。 - 命中官方模板后,必须先执行
scripts/extract_docx_text.py提取正文,再进行字段填充。 - 若无对应官方模板,再回退
assets/templates/通用模板。 - 输出时应标注“模板来源:官方示范/通用模板”。
Output Standard
Mode A: 日常咨询模式(light)
Structure
## 结论
一句话回答问题。
## 你现在可以怎么做
1. 行动建议1
2. 行动建议2
3. 行动建议3(可选)
## 注意风险
- 风险提醒1
- 风险提醒2(可选)
## 需要的话可展开
- 📖 查看法律依据
- 📋 查看类似案例
- 🔍 检查我还缺哪些证据
---
检索状态:queryListLaw [✅/❌] | queryListCase [✅/❌]
> ⚠️ 风险提示:以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。✅ Filled Example (Good)
## 结论
公司未签劳动合同超过一个月的,你可以主张最多11个月的双倍工资差额。
## 你现在可以怎么做
1. 整理工资流水、考勤记录、工牌照片等证明劳动关系的材料
2. 先向公司HR书面催告签订合同(微信/邮件留痕)
3. 如公司拒绝,向当地劳动仲裁委申请仲裁(免费,不需要律师)
## 注意风险
- 双倍工资的仲裁时效为1年,从应签合同之日起算,超期部分不予支持
- 如果是你本人拒绝签合同,公司有书面通知证据的,可能无法主张
## 需要的话可展开
- 📖 查看法律依据
- 📋 查看类似案例
- 🔍 检查我还缺哪些证据
---
检索状态:queryListLaw ✅成功(4条法条) | queryListCase ✅成功(5条类案)
> ⚠️ 风险提示:以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。❌ Bad Example (Avoid)
根据《劳动合同法》第八十二条的规定,用人单位自用工之日起超过一个月不满一年
未与劳动者订立书面劳动合同的,应当向劳动者每月支付二倍的工资。根据最高人民
法院的司法解释……(以下省略3000字)→ 轻量模式不应堆砌法条原文。结论在前,可展开在后。
---
Mode B: 办案分析模式(deep)
Structure
# [案件类型] 分析报告
## 一、任务类型识别
- 意图:[case/law/both/document]
- 复杂度:deep
- 案由:[识别的案由]
## 二、处理结果
[包含适用的深度模块:裁判思维模拟器 / 证据缺口诊断器 / 类案差异解释器]
## 三、检索结果清单(原始条目)
[法条和/或类案原始条目]
## 四、需补充信息(如有)
[缺失的关键信息]
## 五、下一步建议
- 24小时内:[紧急行动]
- 3天内:[准备工作]
- 7天内:[中期行动]
## 六、检索执行信息
- queryListLaw:[状态 + 条数]
- queryListCase:[状态 + 条数]
- 检索词:[实际使用的检索关键词]
> ⚠️ 风险提示:以上内容仅供信息参考,不构成正式法律意见。重大事项请咨询持证律师。Deep mode required blocks
| Condition | Required Blocks |
|---|---|
| 争议案件(诉讼/仲裁) | 裁判思维模拟器 + 证据缺口诊断器 |
| 涉及类案引用 | + 类案差异解释器 |
| 纯证据问题 | 证据缺口诊断器(可单独使用) |
Common constraints
- 默认双接口检索并标注执行状态(✅/❌ + 条数 + 失败原因)。
- 失败/降级必须明示原因和降级说明。
- 禁止把"裁判倾向预测"写成"确定判决"。
- 禁止把"可援引"写成"可直接套用判决结果"。
- 禁止把"可补强"写成"已证明"。
References Index
adaptive-response-standard.md: 双模式自适应响应标准(light/deep)workflow-and-intent.md: 工作流、意图识别与复杂度分级case-analysis-standard.md: 类案检索参数、分析结构、胜诉率表达标准case-difference-explainer-standard.md: 类案差异解释器标准(可援引边界与差异影响)evidence-gap-diagnostic-standard.md: 证据缺口诊断标准(缺失证据、风险等级、补强优先级)evidence-burden-matrix.md: 案由举证要件矩阵(借贷/买卖/劳动/租赁示例)law-query-standard.md: 法条检索参数、效力位阶排序、场景化解读标准document-processing-standard.md: 合同审查与法律文书生成标准output-standard.md: 分层输出结构与质量门槛judicial-reasoning-standard.md: 裁判思维模拟器标准(争议焦点/双方论证/裁判倾向)docx-export-standard.md: Markdown 转 Word 的格式要求word-export-skill-integration.md: 单技能 Word 导出集成规范official-template-sources.md: 已导入官方模板的来源与使用规则samr_contract_templates.json: 外部导入的 SAMR 合同模板标准化索引(含按需下载 URL)
Word Export Integration (Single-Skill Mode)
Goal
由 legal-copilot 单独完成 Word 导出,不依赖其他技能。
Runtime flow
1. 若命中官方模板,先调用 scripts/extract_docx_text.py 读取模板正文用于字段映射。 2. 生成结构化 Markdown 成果。 3. 只要发生"生成文件"行为,自动调用导出。
Export commands
Windows:
powershell -ExecutionPolicy Bypass -File scripts/export_to_docx.ps1 -InputMarkdown .\output.md -OutputDocx .\output.docxmacOS/Linux (fallback):
pandoc output.md -o output.docx4. export_to_docx.ps1 内部优先级:markdown_to_docx_converter → pandoc。 5. 成功后返回 .docx 输出路径。
Result handling
| Result | Action |
|---|---|
| ✅ Success | 显示:✅ 已导出:[path] 并注明模板来源 |
| ❌ Converter missing | 显示错误,提供原始 Markdown 兜底 |
| ❌ Other failure | 显示错误信息并提供原始 Markdown |
Dependency policy
- 不要求安装外部 Word 技能。
- 不要求执行 skill 搜索/安装步骤。
- 仅依赖当前 Skill 内置脚本 + 本机转换命令。
Output requirement
- 返回
.docx绝对路径 - 返回导出执行状态(成功/失败)
- 返回模板来源(官方/通用)与官方模板读取状态
- 失败时返回原因与可执行修复动作
- 附"提交前校验清单":当事人、金额、日期、附件一致性
Workflow And Intent
Intent labels
case: 类案检索与裁判分析law: 法条检索与适用解读both: 法条与类案联合检索document: 法律文书生成
Complexity tiers
light(日常咨询模式):快速可执行建议为主。deep(办案分析模式):诉讼/维权深度分析为主。
Routing rules
1. 用户仅做日常咨询(如押金、工资、报警、邻里纠纷),优先 light。 2. 用户提到“起诉/律师函/胜诉率/争议焦点/证据是否充分/类案可援引”,切到 deep。 3. 未明确要求深度分析时,先 light 后提供“渐进展开入口”。
Hard constraints
- 法律问题默认双接口:
queryListLaw + queryListCase。 - 轻量模式允许简化展示,但不得省略检索执行信息(可折叠为简版)。
- 深度模式必须输出:裁判思维模拟器 + 证据缺口诊断器;涉及类案时加类案差异解释器。
Required intent output
{
"intent": "case|law|both|document",
"complexity": "light|deep",
"reason": "不超过两句话",
"next_action": "light_response|parallel_query_both|document_processing",
"extracted_query": "核心检索词或核心问题",
"confidence": 0.0
}#!/usr/bin/env python3
"""Build queryListCase payload from simple CLI args."""
from __future__ import annotations
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--keywords", nargs="*", default=[])
parser.add_argument("--long-text", default="")
parser.add_argument("--sort-field", default="correlation", choices=["correlation", "time"])
parser.add_argument("--sort-order", default="desc", choices=["asc", "desc"])
parser.add_argument("--page-no", type=int, default=1)
parser.add_argument("--page-size", type=int, default=5)
parser.add_argument("--court-level", nargs="*", default=[])
parser.add_argument("--judgement-type", nargs="*", default=[])
parser.add_argument("--case-year-start", default="")
parser.add_argument("--case-year-end", default="")
args = parser.parse_args()
condition: dict[str, object] = {}
if args.long_text.strip():
condition["longText"] = args.long_text.strip()
elif args.keywords:
condition["keywordArr"] = args.keywords
if args.court_level:
condition["courtLevelArr"] = args.court_level
if args.judgement_type:
condition["judgementTypeArr"] = args.judgement_type
if args.case_year_start:
condition["caseYearStart"] = args.case_year_start
if args.case_year_end:
condition["caseYearEnd"] = args.case_year_end
payload = {
"pageNo": args.page_no,
"pageSize": args.page_size,
"sortField": args.sort_field,
"sortOrder": args.sort_order,
"condition": condition,
}
print(json.dumps(payload, ensure_ascii=False))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Build queryListLaw payload from simple CLI args."""
from __future__ import annotations
import argparse
import json
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--keywords", nargs="+", required=True)
parser.add_argument("--field-name", default="title", choices=["title", "semantic"])
parser.add_argument("--sort-field", default="correlation")
parser.add_argument("--sort-order", default="desc", choices=["asc", "desc"])
parser.add_argument("--page-no", type=int, default=1)
parser.add_argument("--page-size", type=int, default=5)
args = parser.parse_args()
payload = {
"pageNo": args.page_no,
"pageSize": args.page_size,
"sortField": args.sort_field,
"sortOrder": args.sort_order,
"condition": {
"keywords": args.keywords,
"fieldName": args.field_name,
},
}
print(json.dumps(payload, ensure_ascii=False))
if __name__ == "__main__":
main()
param(
[Parameter(Mandatory = $true)]
[string]$InputMarkdown,
[Parameter(Mandatory = $true)]
[string]$OutputDocx
)
if (!(Test-Path $InputMarkdown)) {
Write-Error "Input markdown not found: $InputMarkdown"
exit 1
}
$converter = Get-Command markdown_to_docx_converter -ErrorAction SilentlyContinue
if ($converter) {
& markdown_to_docx_converter --input "$InputMarkdown" --output "$OutputDocx"
if ($LASTEXITCODE -eq 0) {
Write-Output "OK: $OutputDocx"
exit 0
}
}
$pandoc = Get-Command pandoc -ErrorAction SilentlyContinue
if ($pandoc) {
& pandoc "$InputMarkdown" -o "$OutputDocx"
if ($LASTEXITCODE -eq 0) {
Write-Output "OK: $OutputDocx"
exit 0
}
}
Write-Error "No available converter. Install markdown_to_docx_converter or pandoc."
exit 2
#!/usr/bin/env python3
"""Extract readable plain text from a .docx template without external deps."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import xml.etree.ElementTree as ET
import zipfile
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
NS = {"w": W_NS}
def _paragraph_to_text(p: ET.Element) -> str:
chunks: list[str] = []
for node in p.iter():
tag = node.tag
if tag == f"{{{W_NS}}}t":
chunks.append(node.text or "")
elif tag == f"{{{W_NS}}}tab":
chunks.append("\t")
elif tag in {f"{{{W_NS}}}br", f"{{{W_NS}}}cr"}:
chunks.append("\n")
return "".join(chunks).strip()
def extract_docx_text(docx_path: Path) -> str:
with zipfile.ZipFile(docx_path, "r") as zf:
with zf.open("word/document.xml") as f:
root = ET.parse(f).getroot()
lines: list[str] = []
for p in root.findall(".//w:p", NS):
txt = _paragraph_to_text(p)
if txt:
lines.append(txt)
return "\n".join(lines)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True, help="Input .docx path")
parser.add_argument("--output", help="Output .txt path (optional)")
parser.add_argument("--max-lines", type=int, default=0, help="Trim output to first N lines")
args = parser.parse_args()
input_path = Path(args.input)
if not input_path.exists():
print(json.dumps({"ok": False, "error": f"File not found: {input_path}"}, ensure_ascii=False))
raise SystemExit(1)
if input_path.suffix.lower() != ".docx":
print(json.dumps({"ok": False, "error": "Input must be a .docx file"}, ensure_ascii=False))
raise SystemExit(1)
try:
text = extract_docx_text(input_path)
except KeyError:
print(json.dumps({"ok": False, "error": "Invalid .docx: missing word/document.xml"}, ensure_ascii=False))
raise SystemExit(1)
except Exception as exc:
print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
raise SystemExit(1)
lines = text.splitlines()
if args.max_lines and args.max_lines > 0:
lines = lines[: args.max_lines]
final_text = "\n".join(lines)
if args.output:
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(final_text, encoding="utf-8")
print(json.dumps({"ok": True, "output": str(out_path), "line_count": len(lines)}, ensure_ascii=False))
else:
print(final_text)
if __name__ == "__main__":
main()
"""Search and download SAMR contract templates for legal-copilot."""
from __future__ import annotations
import argparse
import json
import re
import urllib.request
from pathlib import Path
def slugify(name: str) -> str:
safe = re.sub(r"[\\/:*?\"<>|]+", "_", name).strip()
safe = re.sub(r"\s+", "_", safe)
return safe or "template"
def load_catalog(catalog_path: Path) -> dict:
if not catalog_path.exists():
raise SystemExit(
f"Catalog not found: {catalog_path}. Run scripts/import_contract_library.py first."
)
return json.loads(catalog_path.read_text(encoding="utf-8"))
def find_candidates(items: list[dict], keyword: str, limit: int = 10) -> list[dict]:
keyword_norm = keyword.strip().lower()
if not keyword_norm:
return items[:limit]
scored: list[tuple[int, dict]] = []
for item in items:
title = str(item.get("title", ""))
title_norm = title.lower()
score = 0
if keyword_norm in title_norm:
score += 100
for token in keyword_norm.split():
if token and token in title_norm:
score += 10
if score > 0:
scored.append((score, item))
scored.sort(key=lambda x: x[0], reverse=True)
return [item for _, item in scored[:limit]]
def download_file(url: str, output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
request = urllib.request.Request(
url,
headers={
"User-Agent": "Mozilla/5.0",
"Accept": "*/*",
},
)
with urllib.request.urlopen(request, timeout=60) as response:
output_path.write_bytes(response.read())
def main() -> None:
parser = argparse.ArgumentParser(description="Fetch SAMR template by id or keyword")
skill_root = Path(__file__).resolve().parents[1]
parser.add_argument(
"--catalog",
default=str(skill_root / "references" / "samr_contract_templates.json"),
help="Normalized SAMR catalog path",
)
parser.add_argument("--id", help="Template id")
parser.add_argument("--keyword", help="Keyword search in title")
parser.add_argument(
"--type",
choices=["word", "pdf"],
default="word",
help="Download file type",
)
parser.add_argument(
"--out-dir",
default=str(skill_root / "assets" / "templates" / "official" / "samr"),
help="Output directory",
)
args = parser.parse_args()
catalog = load_catalog(Path(args.catalog))
items = catalog.get("items", [])
if not isinstance(items, list) or not items:
raise SystemExit("Catalog is empty.")
selected: dict | None = None
if args.id:
selected = next((item for item in items if item.get("id") == args.id), None)
if selected is None:
raise SystemExit(f"Template id not found: {args.id}")
elif args.keyword:
candidates = find_candidates(items, args.keyword, limit=5)
if not candidates:
raise SystemExit(f"No template matched keyword: {args.keyword}")
selected = candidates[0]
else:
raise SystemExit("Provide --id or --keyword")
template_id = str(selected.get("id"))
title = str(selected.get("title", template_id))
file_type = args.type
download_key = "download_word_url" if file_type == "word" else "download_pdf_url"
download_url = str(selected.get(download_key, "")).strip()
if not download_url:
raise SystemExit(f"Missing {download_key} for template: {template_id}")
extension = ".docx" if file_type == "word" else ".pdf"
filename = f"{slugify(title)}_{template_id}{extension}"
output_path = Path(args.out_dir) / filename
download_file(download_url, output_path)
result = {
"ok": True,
"id": template_id,
"title": title,
"type": file_type,
"saved_path": str(output_path),
"view_url": selected.get("view_url"),
"download_url": download_url,
}
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
param(
[string]$Query = "docx"
)
$skillPath = Join-Path $env:USERPROFILE ".agents\skills\docx\SKILL.md"
if (Test-Path $skillPath) {
Write-Output "INSTALLED"
Write-Output $skillPath
exit 0
}
Write-Output "NOT_INSTALLED"
Write-Output "Searching marketplace..."
npx skills find $Query
#!/usr/bin/env python3
"""Basic integrity checks for legal-copilot skill files."""
from __future__ import annotations
import json
from pathlib import Path
REQUIRED_FILES = [
"SKILL.md",
"scripts/query_law_api.py",
"scripts/query_case_api.py",
"scripts/extract_docx_text.py",
"scripts/export_to_docx.ps1",
"scripts/import_contract_library.py",
"scripts/fetch_samr_template.py",
"scripts/validate_output.py",
"scripts/release_skill.ps1",
"references/workflow-and-intent.md",
"references/output-standard.md",
"references/adaptive-response-standard.md",
"references/case-difference-explainer-standard.md",
"references/evidence-gap-diagnostic-standard.md",
"references/evidence-burden-matrix.md",
"references/samr_contract_templates.json",
"assets/templates/light_consult_response.md",
"assets/templates/civil_complaint.md",
"assets/templates/lawyer_letter.md",
"assets/templates/judicial_reasoning_simulator.md",
"assets/templates/case_difference_explainer.md",
"assets/templates/evidence_gap_diagnostic.md",
"assets/templates/official/samr/README.md",
]
def main() -> None:
root = Path(__file__).resolve().parents[1]
missing: list[str] = []
for rel in REQUIRED_FILES:
if not (root / rel).exists():
missing.append(rel)
official_dir = root / "assets" / "templates" / "official"
official_docx_count = len(list(official_dir.glob("*.docx"))) if official_dir.exists() else 0
result = {
"ok": not missing,
"root": str(root),
"missing_files": missing,
"official_template_docx_count": official_docx_count,
}
print(json.dumps(result, ensure_ascii=False, indent=2))
if missing:
raise SystemExit(1)
if __name__ == "__main__":
main()
"""Import and normalize external contract template catalog into legal-copilot."""
from __future__ import annotations
import argparse
import html
import json
from pathlib import Path
def looks_mojibake(text: str) -> bool:
markers = ("涓", "鍚", "鎴", "甯", "绗", "妯", "鏍")
return any(marker in text for marker in markers)
def repair_mojibake(text: str) -> str:
if not text:
return text
try:
repaired = text.encode("gbk", errors="ignore").decode("utf-8", errors="ignore")
return repaired if repaired.strip() else text
except Exception:
return text
def normalize_title(raw_title: str) -> str:
title = html.unescape(raw_title or "").strip()
if looks_mojibake(title):
fixed = repair_mojibake(title).strip()
if fixed:
return fixed
return title
def build_download_url(template_id: str, file_type: int) -> str:
return f"https://htsfwb.samr.gov.cn/api/File/DownTemplate?id={template_id}&type={file_type}"
def main() -> None:
parser = argparse.ArgumentParser(description="Import external contract template library")
skill_root = Path(__file__).resolve().parents[1]
parser.add_argument(
"--input",
required=True,
help="Input catalog JSON path (e.g., path/to/all_templates.json)",
)
parser.add_argument(
"--output",
default=str(skill_root / "references" / "samr_contract_templates.json"),
help="Output normalized JSON path",
)
args = parser.parse_args()
input_path = Path(args.input)
output_path = Path(args.output)
if not input_path.exists():
raise SystemExit(f"Input not found: {input_path}")
payload = json.loads(input_path.read_text(encoding="utf-8"))
templates = payload.get("templates", [])
normalized_items: list[dict[str, str]] = []
for item in templates:
template_id = str(item.get("id", "")).strip()
if not template_id:
continue
title = normalize_title(str(item.get("title", "")))
source = str(item.get("source", "")).strip() or "SAMR"
view_url = str(item.get("url", "")).strip() or f"https://htsfwb.samr.gov.cn/View?id={template_id}"
normalized_items.append(
{
"id": template_id,
"title": title,
"source": source,
"view_url": view_url,
"download_word_url": build_download_url(template_id, 1),
"download_pdf_url": build_download_url(template_id, 2),
}
)
normalized_items.sort(key=lambda x: x["title"])
result = {
"source": "SAMR contract templates",
"total": len(normalized_items),
"items": normalized_items,
}
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(str(output_path))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Pack legal-copilot skill into a clean portable zip (POSIX paths)."""
from __future__ import annotations
import argparse
from pathlib import Path
import zipfile
EXCLUDED_DIR_NAMES = {".git", "__pycache__", ".pytest_cache", ".mypy_cache"}
EXCLUDED_FILE_NAMES = {".DS_Store"}
EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".tmp", ".log"}
EXCLUDED_NAME_PREFIXES = {"tmp_"}
def _should_include(path: Path) -> bool:
rel_parts = set(path.parts)
if rel_parts & EXCLUDED_DIR_NAMES:
return False
if path.name in EXCLUDED_FILE_NAMES:
return False
if any(path.name.startswith(prefix) for prefix in EXCLUDED_NAME_PREFIXES):
return False
if path.suffix.lower() in EXCLUDED_SUFFIXES:
return False
return True
def build_zip(skill_dir: Path, out_zip: Path) -> None:
if not skill_dir.exists() or not skill_dir.is_dir():
raise SystemExit(f"skill_dir not found: {skill_dir}")
if not (skill_dir / "SKILL.md").exists():
raise SystemExit(f"SKILL.md not found under: {skill_dir}")
out_zip.parent.mkdir(parents=True, exist_ok=True)
if out_zip.exists():
out_zip.unlink()
with zipfile.ZipFile(out_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for p in skill_dir.rglob("*"):
if not p.is_file():
continue
rel = p.relative_to(skill_dir)
if not _should_include(rel):
continue
zf.write(p, arcname=rel.as_posix())
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--skill-dir", required=True, help="Path to legal-copilot root")
parser.add_argument("--out", required=True, help="Output zip path")
args = parser.parse_args()
build_zip(Path(args.skill_dir), Path(args.out))
print(args.out)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Call DeliLegal queryListCase API directly (no MCP dependency)."""
from __future__ import annotations
import argparse
import json
import os
import urllib.error
import urllib.request
API_URL = "https://openapi.delilegal.com/api/qa/v3/search/queryListCase"
APP_ID = os.getenv("LEGAL_APP_ID", "")
SECRET = os.getenv("LEGAL_SECRET", "")
if not APP_ID or not SECRET:
print(json.dumps({"ok": False, "error": "Missing env vars LEGAL_APP_ID and/or LEGAL_SECRET"}, ensure_ascii=False))
raise SystemExit(1)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--payload", help="JSON payload string")
parser.add_argument("--payload-file", help="Path to JSON payload file")
parser.add_argument("--query", help="Natural language query for case retrieval")
parser.add_argument("--page-no", type=int, default=1)
parser.add_argument("--page-size", type=int, default=5)
parser.add_argument("--sort-field", default="correlation", choices=["correlation", "time"])
parser.add_argument("--sort-order", default="desc", choices=["asc", "desc"])
parser.add_argument("--output", "-o", help="Write result to file instead of stdout (avoids encoding issues)")
args = parser.parse_args()
selected_count = sum(bool(x) for x in [args.payload, args.payload_file, args.query])
if selected_count != 1:
print(
json.dumps(
{"ok": False, "error": "Provide exactly one of --payload, --payload-file, or --query"},
ensure_ascii=False,
)
)
raise SystemExit(1)
if args.query:
payload = {
"pageNo": args.page_no,
"pageSize": args.page_size,
"sortField": args.sort_field,
"sortOrder": args.sort_order,
"condition": {
"longText": args.query,
},
}
payload_text = json.dumps(payload, ensure_ascii=False)
elif args.payload_file:
with open(args.payload_file, "r", encoding="utf-8-sig") as f:
payload_text = f.read()
else:
payload_text = args.payload or ""
data = payload_text.encode("utf-8")
req = urllib.request.Request(
API_URL,
data=data,
method="POST",
headers={
"Content-Type": "application/json",
"appid": APP_ID,
"secret": SECRET,
},
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read()
# Auto-detect encoding: try UTF-8 first (standard), fallback to GBK/GB2312 (common for CN legal APIs)
body = _decode_bytes(raw)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(body)
else:
print(body)
except urllib.error.HTTPError as exc:
result = json.dumps({"ok": False, "status": exc.code, "error": exc.reason}, ensure_ascii=False)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(result)
else:
print(result)
raise SystemExit(1)
except Exception as exc:
result = json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(result)
else:
print(result)
raise SystemExit(1)
def _decode_bytes(raw: bytes) -> str:
"""Decode API response with automatic encoding detection."""
# Try UTF-8 first (standard JSON encoding)
for enc in ["utf-8", "gbk", "gb2312", "gb18030"]:
try:
text = raw.decode(enc)
# Validate: decoded text must be valid JSON with no replacement chars
parsed = json.loads(text)
return json.dumps(parsed, ensure_ascii=False, indent=None)
except (UnicodeDecodeError, ValueError):
continue
# Last resort: utf-8 with error replacement
return raw.decode("utf-8", errors="replace")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Call DeliLegal Law API — 支持两种模式:
Mode 1: 列表查询 (queryListLaw) — 仅返回法规元数据
Mode 2: 详情增强 (--fetch-detail) — 先查列表,再逐条调用 lawInfo 获取完整内容
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import urllib.error
import urllib.request
# Fix Windows console encoding for Chinese characters
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
# API Endpoints
LIST_URL = "https://openapi.delilegal.com/api/qa/v3/search/queryListLaw"
DETAIL_URL_TEMPLATE = "https://openapi.delilegal.com/api/qa/v3/search/lawInfo?lawId={law_id}&merge=true"
APP_ID = os.getenv("LEGAL_APP_ID", "")
SECRET = os.getenv("LEGAL_SECRET", "")
def get_headers() -> dict:
"""Build auth headers from environment variables."""
if not APP_ID or not SECRET:
return {}
return {
"Content-Type": "application/json",
"appid": APP_ID,
"secret": SECRET,
}
def check_env() -> bool:
"""Verify environment variables are set."""
if not APP_ID or not SECRET:
print(json.dumps(
{"ok": False, "error": "Missing env vars LEGAL_APP_ID and/or LEGAL_SECRET"},
ensure_ascii=False,
))
return False
return True
def query_law_list(payload_text: str, timeout: int = 30) -> dict:
"""
Step 1: Call queryListLaw API to get law metadata list.
Args:
payload_text: JSON-encoded request body string
Returns:
Parsed JSON response dict
"""
data = payload_text.encode("utf-8")
req = urllib.request.Request(
LIST_URL,
data=data,
method="POST",
headers=get_headers(),
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
body = resp.read().decode("utf-8", errors="ignore")
return json.loads(body)
except urllib.error.HTTPError as exc:
error_body = ""
try:
error_body = exc.read().decode("utf-8", errors="ignore") if exc.fp else ""
except Exception:
pass
return {"ok": False, "status": exc.code, "error": exc.reason, "detail": error_body}
except Exception as exc:
return {"ok": False, "error": str(exc)}
def fetch_law_detail(law_id: str, timeout: int = 30) -> dict:
"""
Step 2: Call lawInfo API to get full law content by ID.
Args:
law_id: The law ID from queryListLaw response
Returns:
Parsed JSON response containing lawDetailContent field
"""
url = DETAIL_URL_TEMPLATE.format(law_id=law_id)
req = urllib.request.Request(url, method="GET", headers=get_headers())
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
body = resp.read().decode("utf-8", errors="ignore")
return json.loads(body)
except urllib.error.HTTPError as exc:
return {"ok": False, "status": exc.code, "error": exc.reason, "lawId": law_id}
except Exception as exc:
return {"ok": False, "error": str(exc), "lawId": law_id}
def enrich_with_details(list_result: dict, delay: float = 0.15, max_details: int = 20) -> dict:
"""
Enrich law list results with full content from lawInfo API.
Iterates over each law item in the queryListLaw result,
calls lawInfo for each one, and merges the detail content back.
Args:
list_result: The parsed JSON from queryListLaw
delay: Delay between API calls (rate limiting)
max_details: Maximum number of details to fetch (safety limit)
Returns:
Enhanced result with 'body.data' items containing 'lawDetailContent'
"""
if list_result.get("ok") is False:
return list_result
# Extract data array from nested structure: body -> data
body = list_result.get("body", {})
laws = body.get("data", [])
if not laws:
return list_result
total = len(laws)
fetch_count = min(total, max_details)
enriched_items = []
success_count = 0
fail_count = 0
for i, law in enumerate(laws[:max_details]):
law_id = law.get("id", "")
if not law_id:
enriched_items.append({**law, "_detail_error": "No ID found"})
continue
# Rate limiting between requests
if i > 0:
time.sleep(delay)
# Fetch detail
detail = fetch_law_detail(law_id)
# Check if successful
if detail.get("success") and detail.get("code") == 0:
detail_body = detail.get("body", {})
# Merge original metadata + full content
merged_item = {
**law,
"lawDetailContent": detail_body.get("lawDetailContent", ""),
"items": detail_body.get("items"),
"_detail_fetched": True,
}
success_count += 1
else:
merged_item = {
**law,
"lawDetailContent": "",
"_detail_fetched": False,
"_detail_error": detail.get("error", "Unknown error"),
}
fail_count += 1
enriched_items.append(merged_item)
# Build enhanced response
enhanced_result = {
**list_result,
"body": {
**body,
"data": enriched_items,
"_enriched": True,
"_detail_stats": {
"total_in_list": total,
"fetched": fetch_count,
"success": success_count,
"failed": fail_count,
"skipped": max(0, total - max_details),
},
},
}
return enhanced_result
def main() -> None:
parser = argparse.ArgumentParser(
description="DeliLegal 法规检索 API (支持列表+详情双模式)",
epilog="示例:\n"
" python query_law_api.py --query \"民法典租赁合同\"\n"
" python query_law_api.py --query \"劳动法\" --page-size 3 --fetch-detail\n"
" python query_law_api.py --payload-file input.json --fetch-detail",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--payload", help="JSON payload string")
parser.add_argument("--payload-file", help="Path to JSON payload file")
parser.add_argument("--query", help="Natural language query for law retrieval")
parser.add_argument("--page-no", type=int, default=1, help="Page number (default: 1)")
parser.add_argument("--page-size", type=int, default=5, help="Results per page (default: 5)")
parser.add_argument("--sort-field", default="correlation", choices=["correlation", "activeDate"])
parser.add_argument("--sort-order", default="desc", choices=["asc", "desc"])
parser.add_argument("--field-name", default="semantic", choices=["title", "semantic"],
help="'title' for keyword search, 'semantic' for semantic search (default: semantic)")
parser.add_argument("--fetch-detail", action="store_true",
help="Enable: after getting list, call lawInfo API for each law to get full content")
parser.add_argument("--detail-delay", type=float, default=0.15,
help="Delay in seconds between detail API calls (default: 0.15)")
parser.add_argument("--max-details", type=int, default=20,
help="Maximum number of detail calls (safety limit, default: 20)")
parser.add_argument("--output", "-o",
help="Write result to file instead of stdout (avoids Windows encoding issues)")
args = parser.parse_args()
# Validate env
if not check_env():
raise SystemExit(1)
# Validate exactly one input source
selected_count = sum(bool(x) for x in [args.payload, args.payload_file, args.query])
if selected_count != 1:
print(json.dumps(
{"ok": False, "error": "Provide exactly one of --payload, --payload-file, or --query"},
ensure_ascii=False,
))
raise SystemExit(1)
# Build payload
if args.query:
payload = {
"pageNo": args.page_no,
"pageSize": args.page_size,
"sortField": args.sort_field,
"sortOrder": args.sort_order,
"condition": {
"keywords": [args.query],
"fieldName": args.field_name,
},
}
payload_text = json.dumps(payload, ensure_ascii=False)
elif args.payload_file:
with open(args.payload_file, "r", encoding="utf-8-sig") as f:
payload_text = f.read()
else:
payload_text = args.payload or ""
# Step 1: Query law list
print(f"{'='*60}", file=sys.stderr)
print(f"[Step 1] 查询法规列表...", file=sys.stderr)
print(f"{'='*60}", file=sys.stderr)
list_result = query_law_list(payload_text)
# Check for immediate failure
if list_result.get("ok") is False:
print(json.dumps(list_result, ensure_ascii=False))
raise SystemExit(1)
# If no detail needed, output list result directly
if not args.fetch_detail:
_output_result(list_result, args.output)
print(file=sys.stderr)
print(f"[Done] 返回 {len(list_result.get('body', {}).get('data', []))} 条法规元数据", file=sys.stderr)
return
# Step 2: Fetch details for each law
total_laws = len(list_result.get("body", {}).get("data", []))
fetch_limit = min(total_laws, args.max_details)
print(file=sys.stderr)
print(f"{'='*60}", file=sys.stderr)
print(f"[Step 2] 获取法规详情内容 ({fetch_limit}/{total_laws}条)...", file=sys.stderr)
print(f"{'='*60}", file=sys.stderr)
enriched_result = enrich_with_details(
list_result,
delay=args.detail_delay,
max_details=args.max_details,
)
stats = enriched_result.get("body", {}).get("_detail_stats", {})
print(file=sys.stderr)
print(f"{'='*60}", file=sys.stderr)
print(f"[Done] 详情获取完成:", file=sys.stderr)
print(f" 成功: {stats.get('success', 0)} | 失败: {stats.get('failed', 0)} | 跳过: {stats.get('skipped', 0)}", file=sys.stderr)
print(f"{'='*60}", file=sys.stderr)
# Output final result (compact JSON)
_output_result(enriched_result, args.output)
def _output_result(result: dict, output_path: str = None) -> None:
"""Output result to stdout or file (file avoids Windows encoding issues)."""
text = json.dumps(result, ensure_ascii=False, indent=None)
if output_path:
with open(output_path, "w", encoding="utf-8") as f:
f.write(text)
else:
print(text)
if __name__ == "__main__":
main()
Scripts
文件说明
build_case_payload.py: 生成queryListCase请求体build_law_payload.py: 生成queryListLaw请求体query_case_api.py: 直连queryListCase接口(支持--query/--payload/--payload-file)query_law_api.py: 直连法规检索接口,支持两步式详情增强模式- 模式1(列表): 仅返回法规元数据 —
--query "民法典" - 模式2(列表+详情): 获取完整法条正文(推荐)—
--query "民法典" --fetch-detail - 额外参数:
--max-details N(限制数量),--detail-delay S(限流间隔) validate_output.py: 校验 Markdown 输出结构export_to_docx.ps1: 将 Markdown 转为.docx(优先markdown_to_docx_converter,回退pandoc)find_docx_skill.ps1: 调试脚本(非主流程依赖)pack_skill_zip.py: 生成可导入 zip(自动过滤__pycache__/.pyc/.tmp/.log)extract_docx_text.py: 读取官方.docx模板正文并输出纯文本import_contract_library.py: 导入并规范化外部合同模板目录(例如桌面/合同模板库/all_templates.json)fetch_samr_template.py: 通过模板id或关键词从 SAMR 按需下载模板(Word/PDF)health_check.py: 一键检查 Skill 关键文件与官方模板完整性release_skill.ps1: 一键健康检查 + 打包 + 导入发布
快速示例
# 法规检索(推荐:带详情,获取完整法条内容)
python scripts/query_law_api.py --query "深圳市房地产相关的法律规定有哪些?" --fetch-detail
python scripts/query_law_api.py --query "劳动法" --page-size 10 --fetch-detail --max-details 5
# 案例检索(直接返回完整内容)
python scripts/query_case_api.py --query "上班途中车祸工伤案例"
python scripts/build_law_payload.py --keywords 深圳 房地产 法规 --field-name semantic
python scripts/build_case_payload.py --keywords 上班途中 车祸 工伤
python scripts/validate_output.py ./result.md
powershell -ExecutionPolicy Bypass -File scripts/export_to_docx.ps1 -InputMarkdown .\\result.md -OutputDocx .\\result.docx
python scripts/pack_skill_zip.py --skill-dir . --out ../legal-copilot-portable.zip
python scripts/extract_docx_text.py --input ../assets/templates/official/离婚纠纷起诉状_官方示范.docx --output ./tmp_official_template.txt --max-lines 300
python scripts/import_contract_library.py
python scripts/fetch_samr_template.py --keyword 商品房买卖合同 --type word
python scripts/health_check.py
powershell -ExecutionPolicy Bypass -File scripts/release_skill.ps1约束
query_*_api.py三种入参模式必须三选一,不能同时传。- 建议优先使用
--query,减少手工拼装 JSON 出错率。
param(
[string]$BackendUrl = "http://localhost:18000",
[string]$UserId = "default",
[string]$SkillName = "legal-copilot"
)
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$skillDir = Split-Path -Parent $scriptDir
$zipPath = Join-Path (Split-Path -Parent $skillDir) "legal-copilot-portable.zip"
Write-Host "[1/4] Running health check..."
python (Join-Path $scriptDir "health_check.py") | Out-Null
Write-Host "[2/4] Packing skill..."
python (Join-Path $scriptDir "pack_skill_zip.py") --skill-dir $skillDir --out $zipPath | Out-Null
Write-Host "[3/4] Discovering import candidates..."
$discoverRaw = curl.exe -sS -X POST "$BackendUrl/api/v1/skills/import/discover" -H "X-User-Id: $UserId" -F "file=@$zipPath"
$discover = $discoverRaw | ConvertFrom-Json
if (-not $discover.data.archive_key) {
throw "Discover failed: $discoverRaw"
}
$commitBody = @{
archive_key = $discover.data.archive_key
selections = @(@{ relative_path = "."; name_override = $SkillName })
} | ConvertTo-Json -Depth 10
$commit = Invoke-RestMethod -Method Post -Uri "$BackendUrl/api/v1/skills/import/commit" -Headers @{
"X-User-Id" = $UserId
"Content-Type" = "application/json; charset=utf-8"
} -Body $commitBody
$jobId = $commit.data.job_id
if (-not $jobId) {
throw "Commit failed: missing job id"
}
Write-Host "[4/4] Waiting import job: $jobId"
$deadline = (Get-Date).AddSeconds(60)
$job = $null
while ((Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 700
$job = Invoke-RestMethod -Method Get -Uri "$BackendUrl/api/v1/skills/import/jobs/$jobId" -Headers @{ "X-User-Id" = $UserId }
if ($job.data.status -in @("success", "failed")) { break }
}
if (-not $job) { throw "Import job polling failed." }
$item = $job.data.result.items[0]
[pscustomobject]@{
job_id = $jobId
status = $job.data.status
skill_id = $item.skill_id
skill_name = $item.skill_name
overwritten = $item.overwritten
} | ConvertTo-Json -Depth 8
requests>=2.28
python-docx>=0.8.11
#!/usr/bin/env python3
"""Lightweight markdown output validator for legal-copilot."""
from __future__ import annotations
import argparse
from pathlib import Path
COMMON_SECTIONS = [
"# 文书处理结果",
"## 一、任务类型识别",
"## 二、处理结果",
"## 三、检索结果清单(原始条目)",
"## 六、检索执行信息",
"# 风险提示",
]
NEXT_STEP_SECTION_CANDIDATES = [
"## 四、需补充信息(如有)",
"## 五、下一步建议",
]
EXECUTION_INFO_REQUIRED_KEYS = [
"queryListLaw",
"queryListCase",
]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("file", help="Markdown output file path")
args = parser.parse_args()
text = Path(args.file).read_text(encoding="utf-8")
missing = [s for s in COMMON_SECTIONS if s not in text]
if missing:
print("INVALID")
for item in missing:
print(f"- missing section: {item}")
raise SystemExit(1)
has_next_step = any(s in text for s in NEXT_STEP_SECTION_CANDIDATES)
if not has_next_step:
print("INVALID")
print("- missing section: ## 四、需补充信息(如有) or ## 五、下一步建议")
raise SystemExit(1)
missing_exec_keys = [k for k in EXECUTION_INFO_REQUIRED_KEYS if k not in text]
if missing_exec_keys:
print("INVALID")
for key in missing_exec_keys:
print(f"- missing retrieval execution key: {key}")
raise SystemExit(1)
print("VALID")
if __name__ == "__main__":
main()