85 lines
2.3 KiB
Python
85 lines
2.3 KiB
Python
"""Claude CLI 后端 — 调用 claude CLI 子进程生成总结。
|
|
|
|
和 pi_client.py 对称的接口,复用 prompt 构建、PDF 文本提取、JSON 提取逻辑。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import uuid
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ClaudeTimeoutError(Exception):
|
|
pass
|
|
|
|
|
|
class ClaudeProcessError(Exception):
|
|
def __init__(self, returncode: int, stderr: str):
|
|
self.returncode = returncode
|
|
self.stderr = stderr
|
|
super().__init__(f"claude exited with code {returncode}: {stderr[:500]}")
|
|
|
|
|
|
async def call_claude(
|
|
prompt: str,
|
|
session_id: str | None = None,
|
|
fix_errors: list[str] | None = None,
|
|
) -> tuple[str, str]:
|
|
"""调用 claude CLI print 模式,返回 (stdout 文本, session_id)。
|
|
|
|
和 call_pi() 对称的接口,但 claude CLI 不需要文件路径和 pdf_mode——
|
|
所有内容已在 prompt 中准备好。
|
|
|
|
Args:
|
|
prompt: 完整的 prompt 文本
|
|
session_id: session ID(首次为 None 时自动生成)
|
|
fix_errors: 上一轮验证错误列表(用于重试)
|
|
"""
|
|
if session_id is None:
|
|
session_id = f"claude-summary-{uuid.uuid4().hex[:8]}"
|
|
|
|
cmd = [settings.CLAUDE_BIN, "-p", "--output-format", "text"]
|
|
|
|
if fix_errors and session_id:
|
|
# 重试:延续 session
|
|
cmd += ["--session-id", session_id, "--continue"]
|
|
else:
|
|
cmd += ["--session-id", session_id]
|
|
|
|
cmd.append(prompt)
|
|
|
|
logger.info(
|
|
"Calling claude (session=%s, fix=%s)",
|
|
session_id,
|
|
bool(fix_errors),
|
|
)
|
|
|
|
proc = await asyncio.create_subprocess_exec(
|
|
*cmd,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
try:
|
|
stdout, stderr = await asyncio.wait_for(
|
|
proc.communicate(),
|
|
timeout=settings.SUMMARY_TIMEOUT_SECONDS,
|
|
)
|
|
except asyncio.TimeoutError:
|
|
proc.kill()
|
|
await proc.wait()
|
|
raise ClaudeTimeoutError(
|
|
f"claude timed out after {settings.SUMMARY_TIMEOUT_SECONDS}s"
|
|
)
|
|
|
|
if proc.returncode != 0:
|
|
raise ClaudeProcessError(
|
|
proc.returncode, stderr.decode("utf-8", errors="replace")
|
|
)
|
|
|
|
return stdout.decode("utf-8", errors="replace"), session_id
|