Metadata-Version: 2.4
Name: profile-claw
Version: 0.3.3
Summary: 大模型简历处理工具：格式化、评估、邮件生成、总结概括
License-Expression: MIT
Keywords: cli,formatter,llm,profile,resume
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Text Processing
Requires-Python: >=3.10
Requires-Dist: fastapi>=0.100.0
Requires-Dist: jinja2>=3.1.0
Requires-Dist: llmdog>=0.0.3
Requires-Dist: python-multipart>=0.0.5
Requires-Dist: rich>=13.0.0
Requires-Dist: typer>=0.9.0
Requires-Dist: uvicorn>=0.20.0
Description-Content-Type: text/markdown

# profile-claw

**大模型简历处理工具** - 基于大语言模型的简历格式化、评估、邮件生成与总结概括命令行工具。

profile-claw 是一个 PyPI 可安装的 Python 模块，通过 [llmdog](https://pypi.org/project/llmdog/) 库调用大语言模型，支持兼容 OpenAI Chat Completions 格式的任意 API 端点。CLI 命令为 `profile`，同时提供 Python API 两种使用方式。支持超时容错、断点续传和实时进度显示，适合大规模简历批量处理。

---

## 功能特性

- **格式化（format）**：将非结构化简历文本转换为标准 JSON 格式
- **评估（eval）**：对简历进行完整性、专业性、技能匹配度、呈现效果四维度评分分析，支持通用模式和目标岗位匹配模式
- **邮件生成（email）**：基于候选人简历生成招聘 outreach 邮件
- **总结概括（summary）**：提取简历核心信息，生成精炼总结
- **初始化（init）**：一键初始化本地 prompt 模板文件，支持自定义
- **批量处理**：支持单文件和目录批量处理
- **超时控制与容错**：LLM 调用超时自动跳过，单文件失败不影响整个批量任务
- **断点续传**：`--resume` 跳过已完成文件，`--force` 强制覆盖，中断后可从断点继续
- **实时进度条**：基于 rich 的实时进度显示，清晰展示处理状态（成功/跳过/失败）
- **双重使用方式**：CLI 命令行 + Python 函数调用
- **Web 管理界面**：基于 FastAPI + Alpine.js 的 prompt 模板和 LLM 配置管理
- **灵活配置**：支持多种 LLM 模型、自定义 prompt 模板

---

## 安装和环境配置

### 前置要求

- Python >= 3.10
- pip 包管理器

### 方式一：直接安装（推荐用于生产）

```bash
pip install profile-claw
```

### 方式二：开发模式安装

1. **克隆或下载项目**

```bash
cd /path/to/profile-claw
```

2. **创建虚拟环境**

```bash
# 在项目根目录创建名为 venv 的虚拟环境
python -m venv venv

# 激活虚拟环境
# macOS / Linux:
source venv/bin/activate

# Windows:
venv\Scripts\activate
```

3. **安装依赖并以开发模式安装**

```bash
pip install -e .
```

4. **验证安装**

```bash
# 验证 CLI 命令
profile --version
# 输出: profile-claw 0.3.0

# 验证 Python 导入
python -c "import profile_claw; print(profile_claw.__version__)"
# 输出: 0.3.0
```

### 虚拟环境说明

虚拟环境（venv）会将 Python 解释器和所有依赖项隔离在项目目录中，不影响系统全局环境。

```
project-root/
├── venv/              # 虚拟环境目录（隔离的 Python 环境）
├── src/
│   └── profile_claw/  # 源代码
└── pyproject.toml     # 项目配置
```

**常用虚拟环境操作：**

| 操作 | 命令 |
|------|------|
| 激活 | `source venv/bin/activate` (macOS/Linux) 或 `venv\Scripts\activate` (Windows) |
| 退出 | `deactivate` |
| 运行命令 | `venv/bin/profile --version`（无需激活） |
| 安装包 | `venv/bin/pip install <package>` |

---

## 使用示例

### CLI 命令行使用

#### 查看帮助信息

```bash
profile --help
```

输出：
```
Usage: profile [OPTIONS] COMMAND [ARGS]...

智能简历处理工具：格式化、评估、邮件生成、总结概括

Options:
  --version, -v  显示版本号
  --help         Show this message and exit.

Commands:
  init      初始化本地 prompt 模板文件到 profile-prompts/ 目录
  format    格式化简历：将简历文本转换为标准 JSON 格式
  eval      评估简历：对简历进行多维度评分和分析
  email     生成招聘邮件：基于简历内容生成招聘 outreach 邮件
  summary   总结简历：提取核心信息生成精炼总结
```

#### 公共选项（format/eval/email/summary 均支持）

| 参数 | 短名 | 说明 | 默认值 |
|------|------|------|--------|
| `--output` | `-o` | 输出目录 | 自动计算 |
| `--prompt` | `-p` | 自定义 prompt 内容 | - |
| `--prompt-file` | `-P` | 自定义 prompt 文件路径 | - |
| `--model` | `-m` | 指定 LLM 模型 | - |
| `--timeout` | `-t` | LLM 调用超时时间（秒） | 60 |
| `--resume` | `-r` | 断点续传：跳过已有输出文件 | False |
| `--force` | `-f` | 强制覆盖已有输出文件 | False |
| `--target-role` | `-T` | 目标岗位：启用岗位匹配评估模式（仅 eval） | - |

#### 初始化本地 prompt 模板

```bash
# 在当前目录创建 profile-prompts/ 目录
profile init

# 指定目标目录
profile init --dir /path/to/project
```

执行后会在目标目录创建 `profile-prompts/` 子目录，包含以下 prompt 模板文件：
- `format_prompt.md`
- `eval_prompt.md`
- `eval_role_prompt.md`
- `email_prompt.md`
- `summary_prompt.md`

#### 格式化简历

```bash
# 处理单个文件（输出到同目录：resume_format.json）
profile format resume.txt

# 处理目录（输出到同级 resume_format/ 目录）
profile format resume_dir/

# 指定输出目录
profile format resume.txt --output output/

# 使用自定义 prompt
profile format resume.txt --prompt "请将以下简历转为JSON格式：{content}"

# 使用自定义 prompt 文件
profile format resume.txt --prompt-file my_prompt.md

# 指定 LLM 模型
profile format resume.txt --model gpt-4

# 自定义超时时间（120 秒）
profile format resume_dir/ --timeout 120

# 断点续传：跳过已处理的文件，从中断处继续
profile format resume_dir/ --resume

# 强制重新处理所有文件（覆盖已有输出）
profile format resume_dir/ --force
```

#### 评估简历

```bash
# 通用评估模式：多维度评分 + 推荐职位/技能
profile eval resume.txt
profile eval resume_dir/ --output eval_results/
profile eval resume.txt --model gpt-4
profile eval resume_dir/ --resume   # 断点续传

# 目标岗位匹配模式：评估候选人与指定岗位的匹配度
profile eval resume.txt --target-role "高级后端工程师"
profile eval resume.txt -T "前端技术负责人" --model gpt-4
profile eval resume_dir/ --target-role "AI算法工程师" --resume
```

**两种评估模式：**

| 模式 | 触发条件 | 新增字段 | 说明 |
|------|----------|----------|------|
| 通用评估 | 默认 | `recommended_job_roles`、`recommended_skills` | 基于简历本身推荐职位和技能 |
| 目标岗位匹配 | `--target-role` | `target_role`、`role_fit_analysis`、`recommended_skills` | 评估候选人与特定岗位的匹配度 |

#### 生成招聘邮件

```bash
profile email resume.txt
profile email resume.txt --output emails/
profile email resume_dir/ --timeout 90 --resume
```

#### 总结简历

```bash
profile summary resume.txt
profile summary resume_dir/ --output summaries/
profile summary resume_dir/ --force  # 强制重新生成
```

### Python API 使用

```python
import profile_claw

# 初始化本地 prompt 模板
profile_claw.init()
profile_claw.init(dir="/path/to/project")

# 格式化简历（返回 ProcessStats 统计对象）
stats = profile_claw.format_resume("resume.txt")
print(stats.summary_text())  # "总计: 1 | 成功: 1"

# 批量处理 + 断点续传 + 超时控制
stats = profile_claw.format_resume(
    "resume_dir/",
    timeout=120,      # 超时 120 秒
    resume=True,      # 跳过已完成文件
)
print(f"成功: {stats.success}, 失败: {stats.failed}, 跳过: {stats.skipped}")

# 带进度回调
stats = profile_claw.format_resume(
    "resume_dir/",
    progress_callback=lambda f, s, m: print(f"[{s}] {f}: {m}"),
)

# 评估简历（通用模式）
stats = profile_claw.eval_resume("resume.txt")

# 评估简历（目标岗位匹配模式）
stats = profile_claw.eval_resume("resume.txt", target_role="高级后端工程师")

# 生成招聘邮件
stats = profile_claw.generate_email("resume.txt")

# 总结简历
stats = profile_claw.summary("resume.txt")

# 强制覆盖已有输出
stats = profile_claw.format_resume("resume_dir/", force=True)
```

**函数签名：**

```python
def format_resume(
    input_path: str | Path,
    output_dir: str | Path | None = None,
    prompt: str | None = None,
    prompt_file: str | Path | None = None,
    model: str | None = None,
    timeout: int = 60,       # LLM 超时秒数
    resume: bool = False,    # 断点续传
    force: bool = False,     # 强制覆盖
    progress_callback=None,  # 进度回调 (filename, status, message)
) -> ProcessStats:
    """格式化简历，返回 ProcessStats 统计对象。"""
```

`eval_resume` 额外支持 `target_role` 参数：

```python
def eval_resume(
    input_path: str | Path,
    output_dir: str | Path | None = None,
    prompt: str | None = None,
    prompt_file: str | Path | None = None,
    model: str | None = None,
    timeout: int = 60,
    resume: bool = False,
    force: bool = False,
    target_role: str | None = None,  # 目标岗位匹配模式
    progress_callback=None,
) -> ProcessStats:
    """评估简历，提供 target_role 时启用目标岗位匹配模式。"""
```

其他函数（`generate_email`、`summary`）签名与 `format_resume` 相同。

**ProcessStats 对象属性：**

| 属性 | 类型 | 说明 |
|------|------|------|
| `total` | `int` | 文件总数 |
| `success` | `int` | 成功处理数 |
| `skipped` | `int` | 跳过数（断点续传） |
| `failed` | `int` | 失败数（超时/异常） |
| `written` | `list[Path]` | 生成的输出文件路径列表 |
| `errors` | `list[tuple[str, str]]` | 失败详情 [(文件名, 错误信息)] |

### Web 管理界面使用

启动 Web 管理界面：

```bash
# 默认 127.0.0.1:7070
profile web

# 自定义地址和端口
profile web --host 0.0.0.0 --port 8080
```

打开浏览器访问 `http://127.0.0.1:7070`，界面提供：

| 功能 | 说明 |
|------|------|
| Prompt 模板管理 | 查看/编辑/新建/删除/重置 prompt 模板 |
| 内置模板 | 查看包内默认模板，可一键复制到本地 |
| 本地模板 | 管理 `profile-prompts/` 目录下的自定义模板 |
| LLM 配置 | 设置 API Key、URL、模型、超时时间、SSL 验证 |

**提示：** Web 界面配置的 LLM 参数仅在当前会话有效，重启后需重新设置。持久化配置请使用环境变量。

---

## API 接口说明

### 公共 API

| 函数 | 说明 |
|------|------|
| `profile_claw.__version__` | 版本号字符串 |
| `profile_claw.init(dir=None)` | 初始化本地 prompt 模板，返回 prompt 目录路径 |
| `profile_claw.format_resume(input_path, ...)` | 格式化简历，返回 ProcessStats |
| `profile_claw.eval_resume(input_path, ...)` | 评估简历，返回 ProcessStats |
| `profile_claw.generate_email(input_path, ...)` | 生成招聘邮件，返回 ProcessStats |
| `profile_claw.summary(input_path, ...)` | 总结简历，返回 ProcessStats |
| `profile_claw.ProcessStats` | 处理统计类 |

### 参数说明

| 参数 | 类型 | 说明 |
|------|------|------|
| `input_path` | `str \| Path` | 输入文件或目录路径（必填） |
| `output_dir` | `str \| Path \| None` | 输出目录，默认自动计算 |
| `prompt` | `str \| None` | 自定义 prompt 内容（优先级最高） |
| `prompt_file` | `str \| Path \| None` | 自定义 prompt 文件路径 |
| `model` | `str \| None` | 指定 LLM 模型名称 |
| `timeout` | `int` | LLM 调用超时秒数，默认 60 |
| `resume` | `bool` | 断点续传，跳过已有输出文件，默认 False |
| `force` | `bool` | 强制覆盖已有输出文件，默认 False |
| `target_role` | `str \| None` | 目标岗位名称，仅 `eval_resume` 有效，默认 None |
| `progress_callback` | `Callable \| None` | 进度回调 `(filename, status, message)` |

### 输出格式

**格式化输出（`*_format.json`）：**
```json
{
  "name": "张三",
  "title": "高级后端工程师",
  "company": "XX科技有限公司",
  "position": "高级后端工程师",
  "location": "北京",
  "email": "zhangsan@example.com",
  "phone": "13800138000",
  "linkedin": "https://linkedin.com/in/zhangsan",
  "github": "https://github.com/zhangsan",
  "summary": "English bio...",
  "bio_summary": "本科毕业于 xxx，工作 5 年，现任 xxx 公司 xxx...",
  "open_to_work": "开放机会",
  "highest_degree": "本科",
  "experience_years": 5,
  "skills": ["Python", "Django", "PostgreSQL"],
  "suitable_job_roles": ["高级后端工程师", "技术负责人"],
  "tags": ["分布式系统/微服务", "云原生"],
  "experience": [
    {
      "company": "XX科技有限公司",
      "title": "高级后端工程师",
      "start_date": "2021-03",
      "end_date": "9999-12",
      "duration": 5,
      "is_current": 1,
      "description": "负责核心业务系统架构设计与开发..."
    }
  ],
  "education": [
    {
      "school": "北京大学",
      "degree": "本科",
      "field": "计算机科学与技术",
      "start_year": 2015,
      "end_year": 2019,
      "ranking_info": "C9"
    }
  ]
}
```

**评估输出（`*_eval.json`）——通用模式：**
```json
{
  "candidate_info": { "name": "张三", "email": "...", ... },
  "overall_score": 85,
  "overall_level": "良好",
  "dimensions": {
    "completeness": { "score": 80, "comment": "..." },
    "professionalism": { "score": 85, "comment": "..." },
    "skill_match": { "score": 90, "comment": "..." },
    "presentation": { "score": 80, "comment": "..." }
  },
  "recommended_job_roles": ["高级后端工程师", "技术负责人", ...],
  "recommended_skills": ["系统设计", "Kubernetes", ...],
  "highlights": [...],
  "weaknesses": [...],
  "suggestions": [...],
  "summary": "..."
}
```

**评估输出（`*_eval.json`）——目标岗位匹配模式（`--target-role`）：**
```json
{
  "candidate_info": { "name": "张三", ... },
  "target_role": "高级后端工程师",
  "overall_score": 82,
  "overall_level": "良好",
  "dimensions": { ... },
  "role_fit_analysis": {
    "fit_score": 75,
    "strengths_for_role": ["Python开发经验丰富", "..."],
    "gaps_for_role": ["缺少大规模分布式系统经验", "..."],
    "recommendation": "建议推进面试，关注系统设计能力"
  },
  "recommended_skills": ["系统设计", "微服务架构", ...],
  "highlights": [...],
  "weaknesses": [...],
  "suggestions": [...],
  "summary": "..."
}
```

**邮件输出（`*_email.json`）：**
```json
{
  "candidate_info": {
    "name": "张三",
    "email": "zhangsan@example.com",
    "phone": "13800138000",
    "current_company": "XX科技有限公司",
    "current_position": "高级后端工程师"
  },
  "subject": "高级Python工程师机会 - [公司名称]",
  "greeting": "尊敬的张三：",
  "body": {
    "introduction": "我是XX公司技术招聘负责人...",
    "highlights": ["您在Python后端开发方面的5年经验", "主导的高并发项目成果", "开源社区贡献"],
    "motivation": "我们正在寻找...",
    "closing": "期待与您进一步交流..."
  },
  "signature": "此致\n敬礼\n\n李四\n技术招聘经理",
  "full_text": "完整邮件文本..."
}
```

**总结输出（`*_summary.json`）：**
```json
{
  "candidate_info": {
    "name": "张三",
    "email": "zhangsan@example.com",
    "phone": "13800138000",
    "current_company": "XX科技有限公司",
    "current_position": "高级后端工程师"
  },
  "one_line_summary": "5年经验的Python后端工程师，擅长高并发系统设计",
  "years_of_experience": 5,
  "core_skills": ["Python", "Django", ...],
  "education_summary": "北京大学计算机科学与技术本科",
  "career_trajectory": "从初级工程师成长为高级后端工程师...",
  "key_achievements": [...],
  "industry_domains": ["互联网", "金融科技"],
  "strengths": ["技术深度好", "项目经验丰富"],
  "summary_paragraph": "综合总结..."
}
```

---

## 依赖项清单

| 依赖 | 版本要求 | 用途 |
|------|----------|------|
| `typer` | >= 0.9.0 | CLI 命令行框架 |
| `llmdog` | >= 0.0.3 | LLM 调用库，支持 OpenAI 兼容格式 |
| `rich` | >= 13.0.0 | 终端美化和格式化输出 |
| `fastapi` | >= 0.100.0 | Web 框架 |
| `uvicorn` | >= 0.20.0 | ASGI 服务器 |
| `jinja2` | >= 3.1.0 | HTML 模板引擎 |
| `python-multipart` | >= 0.0.5 | 表单数据解析 |

**间接依赖（由 llmdog 和 typer 引入）：**
- `requests` - HTTP 请求
- `tenacity` - 重试策略
- `pyyaml` - YAML 配置解析
- `typing-extensions` - 类型扩展

---

## 配置方法详解

### LLM API 配置

profile-claw 通过 [llmdog](https://pypi.org/project/llmdog/) 库与 LLM API 通信，支持任何兼容 OpenAI Chat Completions 格式的 API 端点。

#### 配置优先级

配置按以下优先级生效（从高到低）：

| 优先级 | 配置方式 | 说明 |
|--------|----------|------|
| 1（最高） | 命令行 `--model` 参数 | 仅覆盖模型名称，单次生效 |
| 2 | 环境变量 | 当前终端会话生效 |
| 3 | llmdog 配置文件 | `~/.llmdog/config.yaml` 全局生效 |
| 4（最低） | 内置默认值 | 见下表默认值列 |

> **Web 管理界面说明：** 通过 `profile web` 启动的 Web 界面提供 LLM 配置面板，其本质是写入当前进程的环境变量。Web 界面配置**仅在当前会话有效**，重启后需重新设置。如需持久化配置，请使用环境变量（写入 `~/.bashrc` 或 `~/.zshrc`）。

#### 1. 环境变量配置（推荐）

| 变量名 | 必填 | 默认值 | 说明 |
|--------|------|--------|------|
| `LLM_API_KEY` | **是** | - | API 密钥，各提供商获取方式不同 |
| `LLM_API_URL` | 否 | `https://api.openai.com/v1` | API 端点地址 |
| `LLM_MODEL` | 否 | llmdog 默认模型 | 默认使用的模型名称 |
| `LLM_TIMEOUT` | 否 | `60` | HTTP 请求超时时间（秒） |
| `LLM_VERIFY_SSL` | 否 | `true` | 是否验证 SSL 证书，本地/内网模型可设为 `false` |

**基本配置（写入 shell 配置文件持久化）：**

```bash
# 添加到 ~/.bashrc 或 ~/.zshrc
export LLM_API_KEY="your-api-key-here"
export LLM_API_URL="https://api.openai.com/v1"   # 可选，默认为 OpenAI
export LLM_MODEL="gpt-4"                          # 可选
export LLM_TIMEOUT=60                             # 可选
export LLM_VERIFY_SSL=true                        # 可选
```

**临时配置（仅当前终端会话）：**

```bash
# 在同一行设置并运行
LLM_API_KEY="sk-xxx" LLM_MODEL="gpt-4" profile format resume.txt
```

#### 2. 常见 LLM 提供商配置示例

##### OpenAI

```bash
export LLM_API_KEY="sk-xxxxxxxxxxxxxxxx"
export LLM_API_URL="https://api.openai.com/v1"
export LLM_MODEL="gpt-4"
```

##### DeepSeek

```bash
export LLM_API_KEY="sk-xxxxxxxxxxxxxxxx"
export LLM_API_URL="https://api.deepseek.com/v1"
export LLM_MODEL="deepseek-chat"
```

##### Azure OpenAI

```bash
export LLM_API_KEY="your-azure-api-key"
export LLM_API_URL="https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT_NAME"
export LLM_MODEL="gpt-4"
```

##### Ollama 本地模型

```bash
# 先启动 Ollama 服务：ollama serve
export LLM_API_KEY="ollama"                          # Ollama 不校验 Key，填任意非空值
export LLM_API_URL="http://localhost:11434/v1"
export LLM_MODEL="llama3"
export LLM_VERIFY_SSL=false                          # 本地服务无需 SSL 验证
```

##### LM Studio 本地模型

```bash
# 先在 LM Studio 中启动 Local Server
export LLM_API_KEY="lm-studio"                       # 填任意非空值
export LLM_API_URL="http://localhost:1234/v1"
export LLM_MODEL="local-model"                       # LM Studio 自动选择已加载模型
export LLM_VERIFY_SSL=false
```

##### 其他兼容端点（通义千问、Moonshot 等）

```bash
# 通义千问（DashScope）
export LLM_API_KEY="sk-xxxxxxxxxxxxxxxx"
export LLM_API_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
export LLM_MODEL="qwen-plus"

# Moonshot（Kimi）
export LLM_API_KEY="sk-xxxxxxxxxxxxxxxx"
export LLM_API_URL="https://api.moonshot.cn/v1"
export LLM_MODEL="moonshot-v1-8k"
```

#### 3. 命令行参数

命令行 `--model` / `-m` 参数可在每次执行时临时指定模型，覆盖环境变量中的 `LLM_MODEL`：

```bash
# 使用环境变量中的 API 配置，但指定不同模型
profile format resume.txt --model gpt-4
profile eval resume.txt -m deepseek-chat
profile summary resume.txt --model llama3 --timeout 120
```

> **注意：** 命令行仅支持指定 `--model` 和 `--timeout`，API Key 和 URL 需通过环境变量或 llmdog 配置文件设置。

#### 4. llmdog 配置文件

llmdog 支持 YAML 配置文件，路径为 `~/.llmdog/config.yaml`：

```yaml
# ~/.llmdog/config.yaml
api_key: "sk-xxxxxxxxxxxxxxxx"
api_url: "https://api.openai.com/v1"
model: "gpt-4"
timeout: 60
verify_ssl: true
```

配置文件适合设置全局默认值，环境变量可在此基础上按会话覆盖。详细配置项参见 [llmdog 文档](https://pypi.org/project/llmdog/)。

#### 5. 配置验证与连通性测试

设置完成后，建议按以下步骤验证配置是否正确：

**Step 1：检查环境变量是否已设置**

```bash
echo "API Key: ${LLM_API_KEY:0:8}..."   # 仅显示前 8 位
echo "API URL: $LLM_API_URL"
echo "Model:   $LLM_MODEL"
```

**Step 2：测试 API 连通性**

```bash
# 使用 curl 直接测试 API 端点（以 OpenAI 为例）
curl -s "$LLM_API_URL/models" \
  -H "Authorization: Bearer $LLM_API_KEY" | head -c 200

# 若返回模型列表 JSON，说明 API Key 和 URL 配置正确
# 若返回 401 Unauthorized，说明 API Key 无效或已过期
# 若连接超时，检查 URL 是否正确、网络是否畅通
```

**Step 3：使用 profile-claw 实际测试**

```bash
# 准备一个测试文件
echo "张三\n电话: 13800138000\n教育: XX大学 2020-2024" > /tmp/test_resume.txt

# 执行格式化，观察输出
profile format /tmp/test_resume.txt --verbose

# --verbose 会显示 DEBUG 级别日志，包括：
# - 实际使用的 model、url
# - LLM 调用耗时
# - 具体错误信息（如有）
```

#### 6. 常见配置问题排查

| 错误信息 | 可能原因 | 解决方法 |
|----------|----------|----------|
| `❌ 未提供 API Key` | 未设置 `LLM_API_KEY` 环境变量 | `export LLM_API_KEY="your-key"` |
| `401 Unauthorized` | API Key 无效、过期或复制不完整 | 重新生成/复制 API Key，注意首尾无空格 |
| `Connection refused` | API URL 错误或本地服务未启动 | 检查 URL；Ollama 需先执行 `ollama serve` |
| `Connection timed out` | 网络不通或 URL 错误 | 检查网络连接和代理设置；确认 URL 可访问 |
| `SSL: CERTIFICATE_VERIFY_FAILED` | SSL 证书验证失败 | 设置 `export LLM_VERIFY_SSL=false`（内网/本地模型） |
| `LLM 调用超时（60s）` | 模型响应过慢 | 增大超时：`--timeout 120` 或 `export LLM_TIMEOUT=120` |
| `LLM 返回 None` | API 返回异常或 Key 权限不足 | 检查 Key 权限；用 `--verbose` 查看详细日志 |
| `404 Not Found` / `model not found` | 模型名称错误 | 通过 API `/models` 端点查询可用模型列表 |

**开启详细日志排查：**

```bash
# 使用 --verbose 显示完整 LLM 调用参数
profile format resume.txt --verbose

# 输出示例：
# DEBUG  开始调用 LLM (timeout=60s, model=gpt-4, prompt长度=3487字符)
# DEBUG  chat() 调用参数: backend=llmapi model=gpt-4 url=https://api.openai.com/v1/...
# DEBUG  LLM 调用完成，耗时 3.2s
```

### 自定义 Prompt 模板

#### 方式一：使用 init 命令初始化后修改

```bash
# 1. 初始化本地 prompt 模板
profile init

# 2. 编辑生成的模板文件
# 修改 profile-prompts/format_prompt.md 等文件

# 3. 后续执行会自动使用本地模板
profile format resume.txt
```

**Prompt 优先级（从高到低）：**
1. 显式指定：`--prompt` 或 `--prompt-file` 参数
2. 本地文件：`profile-prompts/{action}_prompt.md`
3. 内置默认：包内 `profile_claw/prompts/` 目录

#### 方式二：命令行直接传入

```bash
# 直接传入 prompt 内容
profile format resume.txt --prompt "请将简历转为JSON：{content}"

# 传入 prompt 文件
profile format resume.txt --prompt-file my_custom_prompt.md
```

**注意：** prompt 模板必须包含 `{content}` 占位符，运行时会被替换为实际简历内容。`eval_role_prompt.md` 额外包含 `{target_role}` 占位符。

### Prompt 模板变更日志

| 版本 | 变更内容 |
|------|----------|
| 0.3.0 | `format_prompt.md` 全面替换为新结构化解析规则：新增 `candidate_info`、`bio_summary`、`open_to_work`、`tags` 等字段；`experience`/`education` 改为扁平结构；新增技术标签枚举表 |
| 0.3.0 | `eval_prompt.md` 新增 `recommended_job_roles`（推荐职位）和 `recommended_skills`（建议技能）字段 |
| 0.3.0 | 新增 `eval_role_prompt.md` 目标岗位匹配评估模板，含 `{target_role}` 占位符和 `role_fit_analysis` 输出 |
| 0.3.0 | `eval_prompt.md`、`summary_prompt.md`、`email_prompt.md` 统一新增 `candidate_info` 顶层字段 |
| 0.2.0 | 所有模板统一使用 `{content}` 占位符 |

---

## 扩展性说明

### 项目架构

```
src/profile_claw/
├── __init__.py          # 公共 API 导出
├── cli.py               # Typer CLI 命令行界面
├── core.py              # 核心处理逻辑
├── web.py               # FastAPI Web 管理界面后端
├── templates/
│   └── index.html       # Web 管理界面前端（Alpine.js + Tailwind CSS）
└── prompts/
    ├── __init__.py
    ├── format_prompt.md      # 内置格式化 prompt（结构化解析）
    ├── eval_prompt.md        # 内置评估 prompt（通用模式）
    ├── eval_role_prompt.md   # 内置评估 prompt（目标岗位匹配模式）
    ├── email_prompt.md       # 内置邮件生成 prompt
    └── summary_prompt.md     # 内置总结 prompt
```

**核心模块职责：**
- `core.py`：文件读写、prompt 解析、LLM 调用、JSON 清理、输出路径计算
- `cli.py`：命令行参数解析、命令注册、用户交互
- `web.py`：FastAPI 后端，prompt CRUD 和 LLM 配置管理 API
- `templates/`：Web 管理界面 HTML 页面
- `prompts/`：内置 prompt 模板，与代码逻辑分离

### Web API 接口

启动 Web 服务后，可通过以下 API 进行编程管理：

| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/prompts` | 列出所有 prompt 模板（内置 + 本地） |
| `GET` | `/api/prompts/builtin/{action}` | 获取内置 prompt |
| `GET` | `/api/prompts/local/{filename}` | 获取本地 prompt |
| `POST` | `/api/prompts/local` | 创建本地 prompt |
| `PUT` | `/api/prompts/local/{filename}` | 更新本地 prompt |
| `DELETE` | `/api/prompts/local/{filename}` | 删除本地 prompt |
| `POST` | `/api/prompts/local/{filename}/reset` | 从内置模板重置 |
| `GET` | `/api/config/llm` | 获取 LLM 配置 |
| `PUT` | `/api/config/llm` | 更新 LLM 配置 |
| `DELETE` | `/api/config/llm` | 清除 LLM 配置 |

**请求示例：**

```bash
# 获取所有 prompt
curl http://127.0.0.1:7070/api/prompts

# 创建本地 prompt
curl -X POST http://127.0.0.1:7070/api/prompts/local \
  -H 'Content-Type: application/json' \
  -d '{"name": "translate", "filename": "translate_prompt.md", "content": "翻译以下简历：{content}"}'

# 更新 LLM 配置
curl -X PUT http://127.0.0.1:7070/api/config/llm \
  -H 'Content-Type: application/json' \
  -d '{"api_key": "sk-...", "api_url": "https://api.openai.com/v1", "model": "gpt-4", "timeout": 60, "verify_ssl": true}'
```

### 添加新的处理功能

以添加"翻译（translate）"功能为例：

1. **创建 prompt 模板**

在 `src/profile_claw/prompts/` 目录创建 `translate_prompt.md`：

```markdown
# 简历翻译

请将以下简历翻译成英文，保持专业术语的准确性。

## 简历内容

{content}
```

2. **注册到 PROMPT_FILES**

编辑 `src/profile_claw/core.py`：

```python
PROMPT_FILES = {
    "format": "format_prompt.md",
    "eval": "eval_prompt.md",
    "email": "email_prompt.md",
    "summary": "summary_prompt.md",
    "translate": "translate_prompt.md",  # 新增
}
ACTIONS = {"format", "eval", "email", "summary", "translate"}  # 新增
```

3. **添加 API 函数**

在 `src/profile_claw/core.py` 中添加：

```python
def translate_resume(
    input_path: str | Path,
    output_dir: str | Path | None = None,
    prompt: str | None = None,
    prompt_file: str | Path | None = None,
    model: str | None = None,
) -> list[Path]:
    """翻译简历，返回输出文件路径列表。"""
    return process_resume("translate", input_path, output_dir, prompt, prompt_file, model)
```

4. **添加 CLI 命令**

在 `src/profile_claw/cli.py` 中添加：

```python
from profile_claw.core import translate_resume  # 导入新函数

@app.command()
def translate(
    input_path: Path = typer.Argument(..., help="输入文件或目录路径"),
    output: Optional[Path] = typer.Option(None, "--output", "-o", help="输出目录"),
    prompt: Optional[str] = typer.Option(None, "--prompt", "-p", help="自定义 prompt 内容"),
    prompt_file: Optional[Path] = typer.Option(None, "--prompt-file", "-P", help="自定义 prompt 文件路径"),
    model: Optional[str] = typer.Option(None, "--model", "-m", help="指定 LLM 模型"),
) -> None:
    """翻译简历：将简历翻译成目标语言"""
    _process_action("translate", input_path, output, prompt, prompt_file, model)
```

5. **导出新函数**

在 `src/profile_claw/__init__.py` 中添加导出：

```python
from profile_claw.core import translate_resume

__all__ = [
    # ... 其他导出
    "translate_resume",
]
```

### 修改现有 prompt 模板

**方式一：修改内置模板（需要重新安装包）**

直接编辑 `src/profile_claw/prompts/` 目录下的 `.md` 文件，然后重新安装：

```bash
pip install -e .
```

**方式二：使用本地模板（推荐）**

```bash
# 1. 初始化本地模板
profile init

# 2. 修改本地模板
# 编辑 profile-prompts/format_prompt.md

# 3. 后续执行自动使用本地模板
profile format resume.txt
```

---

## 贡献指南与许可证信息

### 贡献指南

欢迎贡献代码、报告问题或提出改进建议！

1. **Fork 项目** 并创建功能分支
2. **编写代码** 遵循项目代码风格
3. **添加测试** 确保新功能有相应测试覆盖
4. **提交 PR** 描述清楚改动内容和原因

**开发环境设置：**

```bash
# 克隆项目
git clone <repo-url>
cd profile-claw

# 创建虚拟环境
python -m venv venv
source venv/bin/activate

# 开发模式安装
pip install -e .

# 运行测试（如有）
pytest
```

### 许可证

本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。

---

## 常见问题

**Q: LLM 调用失败怎么办？**

检查 `LLM_API_KEY` 环境变量是否正确设置，以及 API URL 是否可访问。

```bash
echo $LLM_API_KEY
curl $LLM_API_URL/models -H "Authorization: Bearer $LLM_API_KEY"
```

**Q: 批量处理时某个文件超时/失败会中断整个任务吗？**

不会。当某个文件的 LLM 调用超时、失败或返回 `None` 时，profile-claw 会自动跳过该文件并在终端输出警告日志，继续处理其余文件。可通过 `--timeout` 参数调整超时时间。

**Q: 批量处理中途断了，如何从断点继续？**

使用 `--resume` 参数，已处理的文件（输出文件已存在）会被自动跳过：

```bash
profile format resume_dir/ --resume
```

如需重新处理所有文件（包括已完成的），使用 `--force`：

```bash
profile format resume_dir/ --force
```

**Q: 如何查看当前使用的 prompt？**

使用 `init` 命令导出内置 prompt 到本地查看：

```bash
profile init
cat profile-prompts/format_prompt.md
```

**Q: 支持哪些文件格式？**

输入支持：`.txt`、`.json`、`.md`、`.markdown`
输出格式：JSON（2 空格缩进，中文不转义）
