Skill: yzw23333/mugua-legal-assistant (version 1.0.4)

# 木瓜法律咨询 ## 简介 对接木瓜法律API，提供专业的法律咨询、案件要素提取和案件完整分析能力。 ## 功能 - **法律咨询**：针对用户法律问题提供专业解答 - **案件要素提取**：从文本或文件中自动提取案件关键要素 - **案件完整分析**：基于当事人信息、案由、事实和诉求生成完整案件分析报告 ## HTTP 端点（必须严格一致） `base_url` 末尾带 `/`（默认 `https://api.test.mugua.muguafabao.com/`）。本 Skill 在服务端按下列路径请求，**不得**改用其它路径或自行 `curl` 猜测地址。 | 能力 | 方法 | 完整 URL（拼接规则） | |------|------|----------------------| | 法律咨询 | POST | `{base_url}v1/legal-chat/completions` | | 案件分析 | POST | `{base_url}v1/case-analysis/generate` | **错误示例（不存在，会导致 404）**：`/v1/legal/chat`、`/api/v1/legal/chat` 等任何与上表不一致的路径。 **正确调用方式**：通过本 Skill 的 `action` 与 JSON 参数调用（见下），由运行时注入 `base_url` 与 `api_key`；**不要**把 `legal/chat` 当作接口路径。 ## 调用示例 ### 法律咨询 ```json { "action": "legal_chat", "prompt": "员工被口头辞退，我该如何维权？", "stream": false, "enable_network": false } ``` ### 案件要素提取 ```json { "action": "case_analysis", "analysis_mode": "element_extract", "input_text": "张三于2024年1月入职A公司，未签订劳动合同，2024年6月被口头辞退。" } ``` ## 配置说明 - `base_url`: 木瓜 API 根地址（与 `metadata.json` 中 `required_configs` 一致）。运行时按顺序读取 `context.credentials.base_url`、`context.config.base_url`、`event.config.base_url`；均未提供时使用代码内默认 `https://api.test.mugua.muguafabao.com/`。法律咨询实际请求为 `{base_url}v1/legal-chat/completions`。 - `api_key`: 木瓜 API 鉴权 Token（Bearer），对应 `context.credentials.api_key`。 - `requirements.txt` 仅声明 `requests`；勿将标准库 `typing` 写入 pip 依赖（与注册元数据一致）。 ## 许可证 MIT-0 ## 数据安全与隐私提示 - 本Skill会将用户提供的案件文本、上传文件及配置的`api_key`发送至您指定的`base_url`服务端，请务必确认该服务是您信任的官方服务。 - 请勿在未阅读并同意木瓜法律API隐私政策前，发送包含敏感个人信息的案件数据。 - 建议仅在隔离环境中测试，使用模拟数据验证功能后再处理真实案件。 ## 安装前必看 1. 确认`base_url`为官方可信端点（当前默认是测试环境）。 2. 必须配置`api_key`（Bearer Token）才能正常调用API。 3. 妥善保管`api_key`，避免泄露。

path: metadata.json
content: {
  "name": "木瓜法律助手",
  "version": "1.0.4",
  "description": "对接木瓜法律API，提供法律咨询、案件要素提取、案件完整分析能力",
  "author": "开发者",
  "triggers": ["法律咨询", "案件分析", "要素提取"],
  "endpoint": "/skills/mugua-legal",
  "auth_type": "api_key",
  "required_configs": [
    {
      "name": "api_key",
      "type": "string",
      "description": "木瓜法律API鉴权Bearer Token，需用户自行配置并妥善保管，平台仅在运行时传递，不存储该密钥",
      "required": true
    },
    {
      "name": "base_url",
      "type": "string",
      "description": "木瓜法律 API 根地址（与 skill.py 中 _resolve_base_url 一致：OpenClaw 注入至 context.credentials / context.config / event.config 的 base_url；未注入时使用代码内默认）。默认 https://api.test.mugua.muguafabao.com/ ，实际请求路径见 SKILL.md（v1/legal-chat/completions 与 v1/case-analysis/generate）",
      "required": true,
      "default": "https://api.test.mugua.muguafabao.com/"
    }
  ],
  "input_params": {
    "legal_chat": {
      "prompt": {"type": "string", "required": true, "description": "法律咨询问题"},
      "stream": {"type": "boolean", "required": false, "default": false},
      "enable_network": {"type": "boolean", "required": false, "default": false}
    },
    "case_analysis": {
      "analysis_mode": {"type": "string", "required": true, "enum": ["element_extract", "full_analysis"]},
      "stream": {"type": "boolean", "required": false, "default": false},
      "input_text": {"type": "string", "required": false, "description": "要素提取模式文本输入"},
      "files": {"type": "array", "required": false, "description": "文件列表"},
      "parties": {"type": "array", "required": false, "description": "当事人信息（完整分析模式）"},
      "case_reasons": {"type": "array", "required": false, "description": "案由（完整分析模式）"},
      "facts": {"type": "array", "required": false, "description": "案件事实（完整分析模式）"},
      "demands": {"type": "array", "required": false, "description": "诉求（完整分析模式）"}
    }
  },
  "output_format": {
    "code": "integer",
    "message": "string",
    "data": "object",
    "request_id": "string",
    "success": "boolean"
  }
}

path: requirements.txt
content: requests>=2.31.0
typing>=3.7.4.3

# Required configuration (for ClawHub metadata consistency)
# [REDACTED_SECRET] Token for Mugua Legal API
# base_url: Base endpoint of the Mugua Legal API service

path: skill.py
content: import requests
from typing import Dict, Optional, List

# ====================== Skill基础配置 ======================
SKILL_META = {
    "name": "木瓜法律助手",
    "description": "对接木瓜法律API，提供法律咨询、案件要素提取/完整分析能力",
    "version": "1.0.4",
    "triggers": ["法律咨询", "案件分析", "要素提取"],
    "endpoint": "/skills/mugua-legal",
    "auth_type": "api_key",
    "config": {
        "base_url": "https://api.test.mugua.muguafabao.com/",
        "timeout": 30,  # 请求超时时间
        "max_file_size": 10 * 1024 * 1024  # 10MB文件大小限制
    }
}


def _resolve_base_url(event: Dict, context: Dict) -> str:
    """与 metadata.json 中 base_url 配置一致：优先运行时注入，否则用 SKILL_META 默认。"""
    creds = context.get("credentials") or {}
    ctx_cfg = context.get("config") or {}
    ev_cfg = event.get("config") or {}
    url = creds.get("base_url") or ctx_cfg.get("base_url") or ev_cfg.get("base_url")
    if not url:
        url = SKILL_META["config"]["base_url"]
    return url.rstrip("/") + "/"


# ====================== 法律咨询接口封装 ======================
def legal_chat_completions(
    prompt: str,
    base_url: str,
    stream: bool = False,
    enable_network: bool = False,
    api_key: str = ""
) -> Dict:
    """
    封装木瓜法律咨询接口 /v1/legal-chat/completions
    :param prompt: 法律咨询问题
    :param stream: 是否流式响应
    :param enable_network: 是否联网检索
    :param base_url: API 根地址（含尾斜杠）
    :param [REDACTED_SECRET]
    :return: 标准化响应结果
    """
    url = f"{base_url}v1/legal-chat/completions"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}"  # 替换为实际鉴权方式
    }
    payload = {
        "prompt": prompt,
        "stream": stream,
        "enable_network": enable_network
    }

    try:
        response = requests.post(
            url=url,
            json=payload,
            headers=headers,
            timeout=SKILL_META['config']['timeout']
        )
        response.raise_for_status()
        resp_json = response.json()
        return {
            "success": True,
            "data": resp_json,
            "request_id": resp_json.get("request_id", ""),
            "code": response.status_code
        }
    except requests.exceptions.HTTPError as e:
        return {
            "success": False,
            "error": f"HTTP错误: {str(e)}",
            "code": e.response.status_code if e.response else None,
            "request_id": None
        }
    except requests.exceptions.RequestException as e:
        return {
            "success": False,
            "error": f"请求异常: {str(e)}",
            "code": None,
            "request_id": None
        }

# ====================== 案件分析接口封装 ======================
def case_analysis_generate(
    analysis_mode: str,
    base_url: str,
    stream: bool = False,
    input_text: Optional[str] = None,
    files: Optional[List[Dict]] = None,
    parties: Optional[List[Dict]] = None,
    case_reasons: Optional[List[Dict]] = None,
    facts: Optional[List[Dict]] = None,
    demands: Optional[List[Dict]] = None,
    api_key: str = ""
) -> Dict:
    """
    封装木瓜案件分析接口 /v1/case-analysis/generate
    :param analysis_mode: 分析模式（element_extract/full_analysis）
    :param stream: 是否流式响应
    :param input_text: 文本输入（要素提取模式）
    :param files: 文件列表 [{"name": 文件名, "content": 文件二进制, "type": 文件类型}]
    :param parties: 当事人信息（完整分析模式）
    :param case_reasons: 案由（完整分析模式）
    :param facts: 案件事实（完整分析模式）
    :param demands: 诉求（完整分析模式）
    :param base_url: API 根地址（含尾斜杠）
    :param api_key: 鉴权Key
    :return: 标准化响应结果
    """
    # 校验分析模式
    if analysis_mode not in ["element_extract", "full_analysis"]:
        return {
            "success": False,
            "error": f"无效的分析模式: {analysis_mode}，仅支持element_extract/full_analysis",
            "code": 400,
            "request_id": None
        }

    url = f"{base_url}v1/case-analysis/generate"
    headers = {"Authorization": f"Bearer {api_key}"}

    # 文件上传模式（form-data）
    if files:
        # 校验文件大小
        for f in files:
            if len(f.get("content", b"")) > SKILL_META['config']['max_file_size']:
                return {
                    "success": False,
                    "error": f"文件 {f['name']} 超过10MB大小限制",
                    "code": 400,
                    "request_id": None
                }
        data = {
            "analysis_mode": analysis_mode,
            "stream": str(stream).lower(),
            "input": input_text or ""
        }
        files_data = [("files[]", (f["name"], f["content"], f["type"])) for f in files]
        try:
            response = requests.post(
                url=url,
                data=data,
                files=files_data,
                headers=headers,
                timeout=60  # 文件上传超时时间更长
            )
            response.raise_for_status()
            resp_json = response.json()
            return {
                "success": True,
                "data": resp_json,
                "request_id": resp_json.get("request_id", ""),
                "code": response.status_code
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"文件上传失败: {str(e)}",
                "code": getattr(e.response, "status_code", None) if hasattr(e, "response") else None,
                "request_id": None
            }
    # JSON请求模式
    else:
        payload = {"analysis_mode": analysis_mode, "stream": stream}
        if analysis_mode == "element_extract":
            if not input_text:
                return {
                    "success": False,
                    "error": "要素提取模式必须提供input_text参数",
                    "code": 400,
                    "request_id": None
                }
            payload["input"] = input_text
        elif analysis_mode == "full_analysis":
            # 校验完整分析模式必填参数
            required_fields = [parties, case_reasons, facts, demands]
            if not all(required_fields):
                return {
                    "success": False,
                    "error": "完整分析模式必须提供parties/case_reasons/facts/demands参数",
                    "code": 400,
                    "request_id": None
                }
            payload.update({
                "parties": parties,
                "selected_party_names": [p["name"] for p in parties],
                "case_reasons": case_reasons,
                "facts": facts,
                "demands": demands
            })
        try:
            response = requests.post(
                url=url,
                json=payload,
                headers=headers,
                timeout=SKILL_META['config']['timeout']
            )
            response.raise_for_status()
            resp_json = response.json()
            return {
                "success": True,
                "data": resp_json,
                "request_id": resp_json.get("request_id", ""),
                "code": response.status_code
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"JSON请求失败: {str(e)}",
                "code": getattr(e.response, "status_code", None) if hasattr(e, "response") else None,
                "request_id": None
            }

# ====================== OpenClaw Skill入口 ======================
def skill_handler(event: Dict, context: Dict) -> Dict:
    """
    OpenClaw Skill统一入口函数
    :param event: OpenClaw传入参数（含用户输入、Skill配置）
    :param context: 运行上下文（含鉴权信息）
    :return: OpenClaw标准化响应
    """
    # 解析入参
    skill_params = event.get("params", {})
    action = skill_params.get("action")
    [REDACTED_SECRET]"credentials", {}).get("api_key", "")
    base_url = _resolve_base_url(event, context)

    # 路由到对应接口
    if not action:
        result = {"success": False, "error": "缺少action参数（legal_chat/case_analysis）", "code": 400}
    elif action == "legal_chat":
        result = legal_chat_completions(
            prompt=skill_params.get("prompt", ""),
            base_url=base_url,
            stream=skill_params.get("stream", False),
            enable_network=skill_params.get("enable_network", False),
            [REDACTED_SECRET]
        )
    elif action == "case_analysis":
        result = case_analysis_generate(
            analysis_mode=skill_params.get("analysis_mode", ""),
            base_url=base_url,
            stream=skill_params.get("stream", False),
            input_text=skill_params.get("input_text"),
            files=skill_params.get("files"),
            parties=skill_params.get("parties"),
            case_reasons=skill_params.get("case_reasons"),
            facts=skill_params.get("facts"),
            demands=skill_params.get("demands"),
            [REDACTED_SECRET]
        )
    else:
        result = {"success": False, "error": f"无效的action: {action}，仅支持legal_chat/case_analysis", "code": 400}

    # 标准化响应
    return {
        "code": result.get("code", 200 if result["success"] else 500),
        "message": "success" if result["success"] else result["error"],
        "data": result.get("data", {}),
        "request_id": result.get("request_id", ""),
        "success": result["success"]
    }

# 本地测试入口
if __name__ == "__main__":
    # 测试法律咨询接口
    test_event = {
        "params": {
            "action": "legal_chat",
            "prompt": "员工被口头辞退，应该先做什么？",
            "stream": False,
            "enable_network": False
        }
    }
    test_context = {
        "credentials": {
            "api_key": "your_mugua_api_key"  # 替换为真实API Key
        }
    }
    print("法律咨询接口测试结果：")
    print(skill_handler(test_event, test_context))

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)