fc / apps /litigation_ai /chains /litigation_draft_chain.py
jun2024's picture
Upload 2241 files
b4f2238 verified
Raw History Blame Contribute Delete
6.02 kB
"""Module for litigation draft chain."""
from __future__ import annotations
import logging
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from apps.core.llm.structured_output import clean_text, parse_json_content
logger = logging.getLogger("apps.litigation_ai")
@dataclass
class DraftResult:
display_text: str
draft: dict[str, str]
model: str
token_usage: dict[str, int]
class LitigationDraftChain:
def __init__(self, model: str | None = None) -> None:
self._model = model
async def _build_messages(
self, document_type: str, case_info: dict[str, Any], litigation_goal: str, evidence_text: str
) -> list[dict[str, str]]:
from apps.litigation_ai.services.prompt_template_service import PromptTemplateService
template_service = PromptTemplateService()
from asgiref.sync import sync_to_async
system_template = await sync_to_async(template_service.get_system_template)(document_type) or (
"你是一位专业的诉讼律师,擅长撰写诉讼文书.\n"
"你只能生成文书的可变内容,不要编造或改写当事人身份信息、审理机构、签名盖章等.\n"
"输出必须是中文、专业、逻辑清晰.\n"
)
system = template_service.replace_variables(system_template, case_info)
import json
from .schemas import ComplaintDraft, DefenseDraft
schema = (
ComplaintDraft.model_json_schema()
if document_type in ["complaint", "counterclaim"]
else DefenseDraft.model_json_schema()
)
schema_text = json.dumps(schema, ensure_ascii=False)
output_contract = (
"你必须只输出一个 JSON 对象,不要输出 Markdown,不要输出多余文字.\n"
f"该 JSON 必须严格符合以下 JSON Schema:{schema_text}\n"
"注意:不要输出任何当事人信息、审理机构、签名盖章等固定块内容.\n"
"如果给出了证据摘要,请在 evidence_citations 中列出你实际引用到的证据,\n"
"并尽量填写 evidence_item_id(来自 [证据#ID])、pages(来自页码)、used_in(用于说明引用位置).\n"
)
user = "\n".join(
[
"# 案件信息",
f"案件名称:{case_info.get('case_name', '')}",
f"案由:{case_info.get('cause_of_action', '')}",
"",
"# 系统生成的基础信息(以此为准,不要改写)",
str(case_info.get("system_generated_court_block", "")),
str(case_info.get("system_generated_party_block", "")),
str(case_info.get("system_generated_signature_block", "")),
"",
"# 诉讼目标",
litigation_goal,
"",
"# 证据摘要",
evidence_text or "无",
]
)
return [
{"role": "system", "content": f"{system}\n{output_contract}"},
{"role": "user", "content": user},
]
def _clean_llm_output(self, text: str) -> str:
return clean_text(text)
def _format_display_text(self, document_type: str, draft: dict[str, Any]) -> str:
if document_type in ["complaint", "counterclaim"]:
return "\n\n".join(
[
"诉讼请求:",
(draft.get("litigation_request") or "").strip(),
"",
"事实与理由:",
(draft.get("facts_and_reasons") or "").strip(),
]
).strip()
parts = []
if (draft.get("defense_opinion") or "").strip():
parts.extend(["答辩意见:", (draft.get("defense_opinion") or "").strip(), ""])
parts.extend(["答辩理由:", (draft.get("defense_reason") or "").strip()])
rebuttal = draft.get("rebuttal_to_opponent_evidence") or []
if rebuttal:
parts.extend(["", "质证/反驳要点:", "\n".join([f"- {x}" for x in rebuttal if str(x).strip()]).strip()])
return "\n\n".join([p for p in parts if p is not None]).strip()
async def arun(
self,
*,
case_info: dict[str, Any],
document_type: str,
litigation_goal: str,
evidence_text: str,
stream_callback: Callable[[str], Any] | None = None,
) -> DraftResult:
from asgiref.sync import sync_to_async
from apps.litigation_ai.services.wiring import get_llm_service
llm_service = await sync_to_async(get_llm_service, thread_sensitive=True)()
messages = await self._build_messages(document_type, case_info, litigation_goal, evidence_text)
response = await llm_service.achat(messages=messages, model=self._model, temperature=0.2)
model_name = response.model or self._model or ""
content = self._clean_llm_output(response.content or "")
token_usage = {
"prompt_tokens": int(getattr(response, "prompt_tokens", 0) or 0),
"completion_tokens": int(getattr(response, "completion_tokens", 0) or 0),
"total_tokens": int(getattr(response, "total_tokens", 0) or 0),
}
from .schemas import ComplaintDraft, DefenseDraft
parsed = parse_json_content(content) if content else {}
if document_type in ["complaint", "counterclaim"]:
draft_obj: ComplaintDraft | DefenseDraft = ComplaintDraft.model_validate(parsed)
else:
draft_obj = DefenseDraft.model_validate(parsed)
draft = draft_obj.model_dump()
display_text = self._format_display_text(document_type, draft)
if stream_callback and display_text:
await stream_callback(display_text)
return DraftResult(
display_text=display_text,
draft=draft,
model=model_name,
token_usage=token_usage,
)