Metadata-Version: 2.4
Name: jobdeer
Version: 0.1.0
Summary: A CLI and Web-based job position management tool with full-text search, Excel import/export, and LLM-assisted entry.
Author-email: Chandler <275737875@qq.com>
License-Expression: MIT
Keywords: job,position,management,cli,tantivy,full-text-search
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.9.0
Requires-Dist: rich>=13.0.0
Requires-Dist: tantivy>=0.22.0
Requires-Dist: jieba>=0.42.1
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: flask>=2.3.0
Requires-Dist: filelock>=3.12.0
Requires-Dist: llmdog>=0.0.1
Requires-Dist: larkfunc>=0.1.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Provides-Extra: semantic
Requires-Dist: sentence-transformers>=2.2.0; extra == "semantic"
Requires-Dist: numpy>=1.24.0; extra == "semantic"

# JobDeer - 智能职位管理系统

> 一个基于 CLI 和 Web 双界面的职位管理工具，集成 Tantivy 全文检索、向量语义搜索、Excel 导入导出，以及 LLM 辅助录入功能。

## 📖 项目简介

JobDeer 是一个专为 HR 和技术团队设计的职位管理工具，支持通过**命令行**和 **Web 浏览器**两种方式进行职位信息的录入、查询、搜索和管理。

### 为什么选择 JobDeer？

- **零配置启动**：无需安装数据库，基于 JSON 文件存储，开箱即用
- **双界面操作**：技术人员用 CLI，HR 用 Web 界面，各取所需
- **智能搜索**：支持关键词、正则表达式、语义相似度三种搜索模式
- **AI 辅助**：集成 LLM 自动从 JD 文本中提取结构化字段
- **数据便携**：支持 Excel 导入导出，便于数据迁移和报表生成

---

## ✨ 功能特性

| 功能 | 说明 |
|------|------|
| 🔍 关键词搜索 | 基于 Tantivy BM25 算法的全文检索，支持中文分词 |
| 🔎 正则搜索 | 使用 Python re 模块进行正则表达式匹配 |
| 🧠 语义搜索 | 基于 Embedding 向量的语义相似度检索（可选） |
| 🎯 按字段精确检索 | 支持 26 个字段的组合 AND 查询 |
| ➕ 职位增删改查 | 完整的 CRUD 操作，自动备份数据 |
| 📊 Excel 导入导出 | 支持 .xlsx 格式的批量导入导出 |
| 🤖 LLM 辅助录入 | 粘贴原始 JD 文本，AI 自动提取结构化数据 |
| ⚙️ LLM 配置管理 | 交互式配置向导、API 连接测试、多种配置方式 |
| 🌐 Web 管理界面 | 基于 Alpine.js + TailwindCSS 的现代化 UI |
| 💻 CLI 命令行工具 | 基于 Typer + Rich 的交互式终端 |
| 🔄 数据自动备份 | 每次修改自动创建备份文件 |
| 🔒 并发安全 | 使用文件锁保证多进程写入安全 |

---

## 🏗️ 项目架构

### 整体架构设计

```
┌─────────────────────────────────────────────────────────┐
│                    JobDeer 应用层                        │
├──────────────────────┬──────────────────────────────────┤
│   CLI 命令行界面      │      Web Web 界面                │
│   (Typer + Rich)     │   (Flask + Alpine.js + Tailwind) │
├──────────────────────┴──────────────────────────────────┤
│                    核心业务层                            │
├──────────────┬──────────────┬──────────────┬────────────┤
│ models.py    │ search.py    │ excel_io.py  │ llm_helper │
│ (数据模型)   │ (搜索引擎)   │ (Excel处理)  │ (AI辅助)   │
├──────────────┴──────────────┴──────────────┴────────────┤
│                    数据存储层                            │
├─────────────────────────────────────────────────────────┤
│  joblist.json  │  tantivy_index/  │  vector_index/      │
│  (JSON文件)    │  (全文索引)      │  (向量索引)          │
└─────────────────────────────────────────────────────────┘
```

### 模块划分说明

| 模块文件 | 职责 | 技术栈 |
|---------|------|--------|
| `jobdeer/models.py` | 数据模型定义、JSON 文件存储、CRUD 操作 | filelock (并发锁) |
| `jobdeer/search.py` | 全文检索、向量语义搜索、按字段搜索 | Tantivy-py、jieba、sentence-transformers |
| `jobdeer/excel_io.py` | Excel 文件导入导出、字段映射 | openpyxl |
| `jobdeer/llm_helper.py` | LLM 辅助文本提取 | llmdog、larkfunc |
| `jobdeer/cli.py` | 命令行入口、交互式命令 | Typer、Rich |
| `jobdeer/web.py` | Web 服务端、RESTful API | Flask |
| `jobdeer/templates/index.html` | Web 前端页面模板 | HTML + TailwindCSS |
| `jobdeer/static/app.js` | 前端交互逻辑 | Alpine.js |
| `jobdeer/static/style.css` | 自定义样式 | CSS |

### 技术栈选型原因

1. **Flask**：轻量级 Web 框架，适合内部工具开发，易于部署
2. **Typer + Rich**：现代化的 CLI 框架，自动生成 help 文档，输出美观
3. **Tantivy-py**：Rust 实现的全文检索引擎，性能比 Whoosh 高 5-10x，活跃维护
4. **jieba**：成熟的中文分词库，与 Tantivy 预分词策略完美配合
5. **sentence-transformers**：提供轻量中文 Embedding 模型，支持语义搜索
6. **Alpine.js**：轻量级前端框架，无需构建工具，直接浏览器运行
7. **TailwindCSS**：实用优先的 CSS 框架，快速构建现代化 UI
8. **openpyxl**：Python 处理 Excel 的标准库，支持 .xlsx 格式
9. **filelock**：保证多进程并发写入 JSON 文件时的数据安全

---

## 🚀 安装与配置

### 环境要求

- Python 3.8+（推荐 3.10+）
- macOS / Linux / Windows

### 快速安装

```bash
# 1. 从 PyPI 安装（推荐）
pip install jobdeer

# 2. 或从源码安装
git clone https://github.com/jobdeer/jobdeer.git
cd jobdeer
pip install -e .
```

### 安装语义搜索支持（可选）

语义搜索功能需要额外安装 sentence-transformers 依赖（约 400MB 模型文件）：

```bash
pip install "jobdeer[semantic]"
```

### 验证安装

```bash
# 查看版本
jobdeer --help

# 测试搜索功能
jobdeer search "测试" --limit 5
```

### 数据目录配置

默认情况下，数据存储在运行目录：

```
./
├── joblist.json          # 职位数据文件
├── joblist_backup_*.json # 自动备份文件
├── tantivy_index/        # 全文检索索引
└── vector_index/         # 向量索引（使用语义搜索时）
```

可通过环境变量自定义数据目录：

```bash
export JOBDEER_DATA_DIR="/path/to/your/data"
```

---

## 📖 使用指南

### CLI 命令行使用

#### 添加职位

```bash
jobdeer add \
  --title "Python后端开发工程师" \
  --department "技术部" \
  --description "负责公司后端系统开发" \
  --company "某某科技" \
  --location "北京" \
  --requirements "熟悉Python, Django, Redis" \
  --category "后端开发" \
  --tech-stack "Python,Django,Redis" \
  --priority "高" \
  --urgency "紧急" \
  --job-level "高级" \
  --salary-range "20k,30k" \
  --contact-person "张三" \
  --contact-email "hr@example.com"
```

#### 查询职位

```bash
# 按 ID 查询
jobdeer get <job_id>

# 列表分页查看
jobdeer list --page 1 --per-page 20
```

#### 搜索功能

**关键词搜索**（默认）：

```bash
jobdeer search "Python后端"
jobdeer search "Python" --limit 10
```

**正则表达式搜索**：

```bash
# 使用 --regex 快捷参数
jobdeer search "Python|Java" --regex

# 或使用 --mode regex（等效）
jobdeer search "Python|Java" --mode regex --ignore-case
```

**语义搜索**（需安装 semantic 依赖）：

```bash
jobdeer search "做AI应用的后端开发" --mode semantic
```

**按指定字段搜索**：

```bash
# 仅搜索职位名称
jobdeer search "Python" --field title

# 仅搜索技术栈
jobdeer search "React" --field tech_stack
```

#### 更新职位

```bash
# 使用 --field 参数指定字段和值
jobdeer update <job_id> \
  --field "priority=非常高" \
  --field "urgency=紧急" \
  --field "salary_range=25k,35k"
```

#### 删除职位

```bash
# 交互式确认
jobdeer delete <job_id>

# 跳过确认
jobdeer delete <job_id> --yes
```

#### 重建索引

每次添加/删除大量职位后，建议重建索引：

```bash
jobdeer rebuild-index
```

#### 启动 Web 界面

```bash
jobdeer web --port 5000 --host 127.0.0.1
```

然后在浏览器访问：`http://127.0.0.1:5000`

#### Excel 导入导出

```bash
# 导出所有职位到 Excel
jobdeer export jobs.xlsx

# 从 Excel 导入职位
jobdeer import_excel jobs.xlsx --conflict skip
```

### LLM 辅助录入

```bash
# 交互模式：粘贴原始 JD 文本，AI 自动提取
jobdeer llm-add

# 使用自定义 API 配置
jobdeer llm-add --api-url "https://api.example.com/v1" --api-key "sk-xxx" --model "gpt-3.5-turbo"
```

系统会提示你粘贴职位描述文本，按 `Ctrl+D`（Unix）或 `Ctrl+Z` + `Enter`（Windows）结束输入。

### LLM 配置管理

JobDeer 提供了完整的 LLM API 配置管理命令 `jobdeer llm-config`，支持多种配置方式。

#### 命令功能

**1. `--show` - 显示当前配置**

```bash
jobdeer llm-config --show
```

显示内容：
- 配置文件位置（`~/.llmdog.yaml` 或 `./.llmdog.yaml`）
- 环境变量设置状态（`LLM_API_KEY`、`LLM_API_URL`）
- 当前所有配置项（API Key 自动脱敏，只显示前 8 位）
- 配置完整性验证提示

**2. `--test` - 测试 API 连接**

```bash
jobdeer llm-config --test
```

功能：
- 使用 `test_llm_api()` 函数验证 API 连接
- 显示测试结果和详细的错误诊断信息
- 提供故障排除建议（API Key 无效、URL 错误、网络问题等）

**3. `--create` - 创建配置文件模板**

```bash
jobdeer llm-config --create
```

功能：
- 复制 `.llmdog.yaml.example` 到当前目录
- 检查文件是否已存在，避免意外覆盖
- 提供下一步操作指引

**4. `--wizard` - 交互式配置向导（推荐新用户）**

```bash
jobdeer llm-config --wizard
```

功能：
- 逐步引导配置 API URL、API Key、Model、Timeout
- 显示常用配置示例（OpenAI、Azure、Ollama）
- 支持使用默认值（按 Enter 键）
- 配置摘要确认
- 自动保存为 YAML 格式配置文件
- 可选的 API 连接测试

**5. 快速设置 - 命令行参数直接配置**

```bash
jobdeer llm-config --api-url "https://api.example.com/v1" \
                   --api-key "sk-xxx" \
                   --model "gpt-3.5-turbo"
```

功能：
- 自动读取现有配置并更新指定字段
- 保存为 YAML 格式配置文件
- 支持部分更新（只传需要的参数）

```bash
# 查看当前生效的配置
jobdeer llm-config --show

# 测试 API 连接
jobdeer llm-config --test

# 创建示例配置文件到当前目录
jobdeer llm-config --create

# 启动交互式配置向导（推荐新用户）
jobdeer llm-config --wizard

# 快速设置配置
jobdeer llm-config --api-url "https://api.example.com/v1" --api-key "sk-xxx" --model "gpt-3.5-turbo"
```

#### 使用场景示例

**场景 1：新用户快速开始**

```bash
# 方式 1：使用交互式向导（推荐）
jobdeer llm-config --wizard

# 方式 2：创建配置文件后手动编辑
jobdeer llm-config --create
vim .llmdog.yaml

# 方式 3：直接通过命令行设置
jobdeer llm-config --api-url "https://api.openai.com/v1" \
                   --api-key "sk-xxx" \
                   --model "gpt-3.5-turbo"
```

**场景 2：日常使用**

```bash
# 查看当前配置
jobdeer llm-config --show

# 测试配置是否有效
jobdeer llm-config --test

# 使用 LLM 辅助录入（自动使用已配置的 API）
jobdeer llm-add
```

**场景 3：高级用法**

```bash
# 临时使用不同的 API（不修改配置文件）
jobdeer llm-add --api-url "http://localhost:11434/v1" \
                --api-key "ollama" \
                --model "qwen2.5:32b"

# 使用环境变量覆盖配置
LLM_API_KEY="sk-different-key" jobdeer llm-config --show

# 测试不同的 API 提供商
jobdeer llm-config --api-url "https://api.anthropic.com/v1" --api-key "sk-ant-xxx"
jobdeer llm-config --test
```

#### 配置优先级

llmdog 的配置加载遵循以下优先级（从高到低）：

```
1. 命令行参数 (--api-url, --api-key, --model)
   ↓
2. 环境变量 (LLM_API_URL, LLM_API_KEY, LLM_MODEL)
   ↓
3. 配置文件 (~/.llmdog.yaml 或 ./.llmdog.yaml)
   ↓
4. 内置默认值
```

**重要说明**：
- 环境变量优先级高于配置文件
- 命令行参数优先级最高，可临时覆盖所有配置
- 配置文件支持 YAML 和 JSON 格式

#### 配置文件格式

使用 YAML 格式（通过 PyYAML 库）：

```yaml
# API 配置（必填）
api_key: "sk-your-api-key-here"
api_url: "https://api.openai.com/v1/chat/completions"
model: "gpt-3.5-turbo"

# 可选配置
timeout: 120          # 请求超时（秒）
max_retries: 3        # 最大重试次数
backoff_multiplier: 1 # 指数退避倍率（秒）
verify_ssl: false     # 是否验证 SSL 证书
backend: "llmapi"     # 后端标识符
```

#### 最佳实践建议

1. **开发环境**：使用 `.llmdog.yaml` 配置文件，方便管理
2. **生产环境**：使用环境变量，避免敏感信息泄露
3. **团队协作**：提供 `.llmdog.yaml.example` 模板，每个人复制后填写自己的配置
4. **安全注意**：**永远不要**将包含真实 API Key 的配置文件提交到 Git 仓库（已自动添加到 `.gitignore`）

#### 技术实现细节

**核心函数**：
- `_print_llm_config_help()` - 显示帮助信息（支持 Rich Markdown）
- `_show_current_config(config)` - 显示当前配置
- `_test_api_connection(config)` - 测试 API 连接
- `_create_config_file()` - 创建配置文件
- `_quick_set_config(config, api_url, api_key, model)` - 快速设置
- `_run_config_wizard()` - 交互式向导

**设计特点**：
- 遵循 Typer CLI 规范，使用 `@app.command()` 和 `Annotated` 类型提示
- 复用现有的 `test_llm_api()` 函数，保持一致性
- 使用 `print_info`、`print_success`、`print_error` 等输出函数
- API Key 显示时自动脱敏（只显示前 8 位）
- 错误提示包含详细的解决建议

#### 扩展功能（未来计划）

1. 支持多配置配置文件（如 `--config-file` 参数）
2. 支持配置导入/导出
3. 支持配置历史记录
4. 支持配置验证规则自定义
5. 集成到 Web 界面的配置管理

---

### Web 界面使用

#### 搜索功能

Web 界面提供两种搜索方式：

**1. 全局搜索（顶部搜索栏）**

- **关键词模式**：输入关键词，使用 Tantivy BM25 算法全文检索
- **正则模式**：输入正则表达式，使用 Python re 模块匹配
- **语义模式**：输入自然语言描述，基于向量相似度匹配

**2. 高级搜索（点击"高级搜索"按钮展开）**

- 列出 26 个可搜索字段，按 5 组分类排列：
  - 基本信息：职位名称、所属部门、公司名称、工作地点、职位描述、任职要求
  - 分类标签：职位类别、标签、技术栈、级别、聘用年份
  - 薪酬福利：薪酬范围、福利
  - 目标画像：目标公司、目标职位、目标产品、目标项目、目标部门、目标角色、目标论文、目标GitHub、目标关键词、目标候选人
  - 联系信息：联系人、联系邮箱、面试官

- 可填写多个字段，之间为 **AND 逻辑**（必须同时满足）
- 高级搜索与全局搜索互斥：展开高级搜索时，全局搜索框自动禁用

#### 职位管理

- **新增职位**：点击"+ 新增职位"按钮，填写表单后保存
- **编辑职位**：点击每行的"编辑"按钮，修改后保存
- **删除职位**：点击每行的"删除"按钮，确认后删除
- **重建索引**：点击"重建索引"按钮，重建搜索索引

---

## 🔌 API 接口说明

### RESTful API

Web 界面基于以下 RESTful API：

#### 获取职位列表

```
GET /api/jobs?page=1&per_page=20
```

**响应示例**：
```json
{
  "jobs": [...],
  "page": 1,
  "per_page": 20,
  "total": 100
}
```

#### 创建职位

```
POST /api/jobs
Content-Type: application/json

{
  "title": "Python后端开发工程师",
  "department": "技术部",
  "description": "负责公司后端系统开发",
  "company": "某某科技",
  "location": "北京"
}
```

**响应**：返回创建的职位对象（HTTP 201）

#### 获取单个职位

```
GET /api/jobs/<job_id>
```

#### 更新职位

```
PUT /api/jobs/<job_id>
Content-Type: application/json

{
  "priority": "非常高",
  "urgency": "紧急"
}
```

#### 删除职位

```
DELETE /api/jobs/<job_id>
```

#### 搜索职位

```
GET /api/search?q=Python&mode=keyword&field=title&limit=100
```

**参数说明**：

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| q | string | 是 | - | 搜索关键词/正则/语义查询文本 |
| mode | string | 否 | keyword | 搜索模式：`keyword` / `regex` / `semantic` |
| field | string | 否 | 无（全字段） | 指定搜索字段名（仅 keyword/regex 模式有效） |
| ignore_case | bool | 否 | true | 大小写不敏感（仅 regex 模式有效） |
| limit | int | 否 | 100 | 最大返回数 |

**向后兼容**：`regex=true` 参数仍然生效（等价于 `mode=regex`）

**响应示例**：
```json
{
  "jobs": [...],
  "total": 10,
  "mode": "keyword"
}
```

语义模式下，每个 job 额外包含 `_score` 字段（相似度分数 0-1）。

#### 高级搜索（多字段组合）

```
POST /api/search/fields
Content-Type: application/json

{
  "fields": {
    "title": "Python",
    "company": "字节",
    "tech_stack": "React"
  },
  "mode": "keyword",
  "limit": 100
}
```

**响应**：与 GET /api/search 相同格式

#### 重建索引

```
POST /api/rebuild-index
```

**响应**：
```json
{
  "success": true
}
```

---

## 📦 依赖清单

### 核心依赖

| 包名 | 版本要求 | 用途 |
|------|---------|------|
| `typer` | >=0.9.0 | CLI 框架 |
| `rich` | >=13.0.0 | 终端美化输出 |
| `tantivy` | >=0.22.0 | 全文检索引擎（Rust 底层） |
| `jieba` | >=0.42.1 | 中文分词 |
| `openpyxl` | >=3.1.0 | Excel 文件处理 |
| `flask` | >=2.3.0 | Web 框架 |
| `filelock` | >=3.12.0 | 文件锁（并发安全） |
| `llmdog` | >=0.1.0 | LLM 服务客户端 |
| `larkfunc` | >=0.1.0 | LLM 函数调用工具 |
| `pyyaml` | >=6.0 | YAML 配置文件处理 |

### 可选依赖

| 依赖组 | 包名 | 用途 |
|--------|------|------|
| `semantic` | `sentence-transformers>=2.2.0`, `numpy>=1.24.0` | 语义向量搜索 |
| `dev` | `pytest>=7.0`, `black>=23.0`, `ruff>=0.1.0` | 开发工具 |

### 前端依赖（CDN 引入）

- `TailwindCSS`（v3.x）：CSS 框架
- `Alpine.js`（v3.x）：前端交互框架

---

## ⚙️ LLM API 配置指南

JobDeer 的 LLM 辅助录入功能依赖于 `llmdog` 库，需要配置 API URL、API Key 和 Model 等参数。支持以下三种配置方式（按优先级从高到低）：

### 方式 1：环境变量（推荐用于生产环境）

在终端中设置环境变量：

```bash
# macOS / Linux
export LLM_API_KEY="sk-your-api-key-here"
export LLM_API_URL="https://api.openai.com/v1/chat/completions"
export LLM_MODEL="gpt-3.5-turbo"

# Windows (CMD)
set LLM_API_KEY=sk-your-api-key-here
set LLM_API_URL=https://api.openai.com/v1/chat/completions
set LLM_MODEL=gpt-3.5-turbo

# Windows (PowerShell)
$env:LLM_API_KEY="sk-your-api-key-here"
$env:LLM_API_URL="https://api.openai.com/v1/chat/completions"
$env:LLM_MODEL="gpt-3.5-turbo"
```

**支持的环境变量列表**：

| 环境变量 | 对应配置 | 必填 | 默认值 | 说明 |
|---------|---------|------|--------|------|
| `LLM_API_KEY` | api_key | ✅ 是 | - | API 鉴权密钥 |
| `LLM_API_URL` | api_url | ✅ 是 | - | API 端点地址 |
| `LLM_MODEL` | model | 否 | `qwen2.5-coder-32b-instruct` | 模型名称 |
| `LLM_TIMEOUT` | timeout | 否 | `120` | 请求超时（秒） |
| `LLM_MAX_RETRIES` | max_retries | 否 | `3` | 最大重试次数 |
| `LLM_VERIFY_SSL` | verify_ssl | 否 | `false` | 是否验证 SSL 证书 |
| `LLM_BACKEND` | backend | 否 | `llmapi` | 后端标识符 |

**持久化配置**（添加到 shell 配置文件）：

```bash
# 添加到 ~/.bashrc 或 ~/.zshrc
echo 'export LLM_API_KEY="sk-your-api-key-here"' >> ~/.bashrc
echo 'export LLM_API_URL="https://api.openai.com/v1/chat/completions"' >> ~/.bashrc
source ~/.bashrc
```

### 方式 2：本地配置文件（推荐用于开发环境）

创建 YAML 或 JSON 格式的配置文件，llmdog 会自动按以下顺序查找：

1. 当前目录：`./.llmdog.yaml` 或 `./.llmdog.json`
2. 用户目录：`~/.llmdog.yaml` 或 `~/.llmdog.json`

**YAML 配置示例**（`.llmdog.yaml`）：

```yaml
# API 配置（必填）
api_key: "sk-your-api-key-here"
api_url: "https://api.openai.com/v1/chat/completions"
model: "gpt-3.5-turbo"

# 可选配置
timeout: 120
max_retries: 3
verify_ssl: false
```

**JSON 配置示例**（`.llmdog.json`）：

```json
{
  "api_key": "sk-your-api-key-here",
  "api_url": "https://api.openai.com/v1/chat/completions",
  "model": "gpt-3.5-turbo",
  "timeout": 120,
  "max_retries": 3,
  "verify_ssl": false
}
```

**快速创建配置文件**：

```bash
# 复制示例配置文件
cp .llmdog.yaml.example .llmdog.yaml

# 编辑配置文件
vim .llmdog.yaml
```

### 方式 3：Web 界面动态配置（仅影响当前会话）

在 Web 界面中使用 LLM 功能时，会在浏览器中临时配置 API 参数：

1. 打开 Web 界面：`jobdeer web`
2. 点击"LLM 辅助录入"按钮
3. 填写 API URL 和 API Key
4. 点击"测试连接"验证配置
5. 配置会保存到 `localStorage`（仅在当前浏览器有效）

**注意**：Web 界面配置仅用于前端临时调用，不会写入本地配置文件或环境变量。

### 配置优先级说明

llmdog 的配置加载遵循以下优先级（从高到低）：

```
1. 函数调用时显式传入的参数（代码内部使用）
   ↓
2. 环境变量（LLM_API_KEY、LLM_API_URL 等）
   ↓
3. 配置文件（~/.llmdog.yaml 或 ./.llmdog.yaml）
   ↓
4. 内置默认值
```

**最佳实践建议**：

1. **开发环境**：使用 `.llmdog.yaml` 配置文件，方便版本控制（记得添加到 `.gitignore`）
2. **生产环境**：使用环境变量，避免敏感信息泄露
3. **团队协作**：提供 `.llmdog.yaml.example` 模板，每个人复制后填写自己的配置
4. **安全注意**：**永远不要**将包含真实 API Key 的配置文件提交到 Git 仓库

### 验证配置

配置完成后，可以通过以下方式验证：

**方法 1：使用 CLI 测试**

```bash
# 尝试使用 LLM 辅助录入功能
jobdeer llm-add
```

**方法 2：使用 Python 代码测试**

```python
from jobdeer.llm_helper import test_llm_api

# 如果已配置环境变量或配置文件，可以直接调用
from llmdog.config import load_config
config = load_config()
print(f"API URL: {config.api_url}")
print(f"Model: {config.model}")

# 测试 API 连接
success = test_llm_api(config.api_url, config.api_key)
print(f"API 连接: {'成功' if success else '失败'}")
```

**方法 3：使用 Web 界面测试**

1. 启动 Web 服务：`jobdeer web`
2. 访问 `http://127.0.0.1:5000`
3. 点击"LLM 辅助录入"
4. 填写 API URL 和 API Key
5. 点击"测试连接"按钮

### 常见问题

**Q1: 提示 "api_key 未配置" 错误？**

A: 检查是否正确设置了 `LLM_API_KEY` 环境变量或 `.llmdog.yaml` 配置文件中的 `api_key` 字段。

**Q2: 如何切换不同的 LLM 提供商？**

A: 修改 `api_url` 和 `model` 即可。例如：

```yaml
# OpenAI
api_url: "https://api.openai.com/v1/chat/completions"
model: "gpt-3.5-turbo"

# Azure OpenAI
api_url: "https://your-resource.openai.azure.com/openai/deployments/your-deployment/chat/completions?api-version=2023-05-15"
model: "gpt-35-turbo"

# 本地部署（如 Ollama）
api_url: "http://localhost:11434/v1/chat/completions"
model: "qwen2.5:32b"
```

**Q3: CLI 的 `llm-add` 命令是否支持自定义 API 配置？**

A: 当前版本的 `jobdeer llm-add` 命令依赖于环境变量或配置文件。如果需要临时使用不同的 API 配置，可以先设置环境变量：

```bash
# 场景 1：开发环境（使用配置文件）
cp .llmdog.yaml.example .llmdog.yaml
vim .llmdog.yaml  # 填写真实 API Key
jobdeer llm-add   # 直接使用

# 场景 2：生产环境（使用环境变量）
export LLM_API_KEY="sk-prod-key"
export LLM_API_URL="https://api.openai.com/v1/chat/completions"
jobdeer llm-add

# 场景 3：临时测试不同 API
jobdeer llm-add --api-url "http://localhost:11434/v1" --api-key "ollama" --model "qwen2.5:32b"

# 场景 4：验证配置
python3 -c "from llmdog.config import load_config; c = load_config(); print(f'URL: {c.api_url}, Model: {c.model}')"

```



**Q4: 配置文件和环境变量同时存在时，哪个生效？**

A: 环境变量优先级高于配置文件。如果同时设置了 `LLM_API_KEY` 环境变量和 `.llmdog.yaml` 中的 `api_key`，环境变量的值会覆盖配置文件。

---

## 🔍 搜索功能技术原理

### 1. 关键词搜索（Tantivy BM25）

**实现流程**：

1. **写入索引**：职位数据写入时，使用 jieba 对中文文本进行预分词，以空格连接后存入 Tantivy
2. **搜索查询**：用户输入查询词同样经过 jieba 分词，构建 Tantivy 查询语句
3. **BM25 排序**：Tantivy 使用 BM25 算法计算每个文档的相关性得分，按得分降序返回

**中文分词策略**：

```python
# 写入索引前
text = "Python后端开发工程师"
tokens = jieba.cut(text)  # ["Python", "后端", "开发", "工程师"]
indexed_text = " ".join(tokens)  # "Python 后端 开发 工程师"

# 搜索时
query = "后端开发"
query_tokens = jieba.cut(query)  # ["后端", "开发"]
indexed_query = " ".join(query_tokens)  # "后端 开发"
```

**Tantivy 查询语法**：

```python
# 单字段搜索
query = ix.parse_query("后端", ["title"])

# 多字段搜索
query = ix.parse_query("后端 开发", ["title", "description", "requirements"])

# 高级搜索（AND 逻辑）
query_str = "+title:后端 +title:开发 +company:字节"
query = ix.parse_query(query_str, all_fields)
```

### 2. 正则搜索（Python re）

**实现流程**：

1. 使用 `re.compile()` 编译正则表达式
2. 遍历所有职位数据，将每个职位的所有字段拼接为文本
3. 使用 `prog.search()` 匹配，返回所有匹配的职位

**支持选项**：

- `ignore_case=True`：忽略大小写（默认）
- `field="title"`：仅在指定字段搜索

**示例**：

```python
# 匹配包含 Python 或 Java 的职位
search_regex("Python|Java", ignore_case=True)

# 仅搜索 title 字段，忽略大小写
search_regex("^Python", ignore_case=True, field="title")
```

### 3. 语义搜索（向量相似度）

**实现流程**：

1. **向量化**：使用 `sentence-transformers` 的 `shibing624/text2vec-base-chinese` 模型，将所有职位的可搜索字段拼接后生成 768 维向量
2. **存储**：向量矩阵保存为 `vectors.npy`，对应的 job_id 列表保存为 `ids.json`
3. **搜索**：用户查询文本同样生成向量，与所有职位向量计算余弦相似度
4. **排序**：按相似度降序返回 top-N 结果

**余弦相似度计算**：

```python
# 向量已归一化，直接点积即为余弦相似度
scores = vectors @ query_vec  # (N,) 数组
top_indices = np.argsort(-scores)[:limit]
```

**为什么用 text2vec-base-chinese？**

- 轻量模型（约 400MB），适合本地部署
- 支持中英双语，适合技术职位搜索
- 基于 SentenceTransformers，API 简洁

**注意事项**：

- 语义搜索为可选功能，不安装 `sentence-transformers` 不影响其他搜索
- 首次使用会自动下载模型文件（约 400MB），后续缓存到本地
- 每次添加/删除/更新职位后，需同步更新向量索引

---

## 🧩 扩展与维护

### 如何添加新的可搜索字段？

1. 在 `jobdeer/models.py` 的 `CORE_FIELDS` 集合中添加字段名
2. 在 `jobdeer/search.py` 的 `SEARCHABLE_FIELDS` 列表中添加字段名
3. 在 `jobdeer/templates/index.html` 的高级搜索面板中添加对应的输入框
4. 在 `jobdeer/static/app.js` 的 `advancedFields` 对象中添加初始值
5. 执行 `jobdeer rebuild-index` 重建索引

### 如何更换语义搜索模型？

修改 `jobdeer/search.py` 中的 `_get_embedding_model()` 函数：

```python
def _get_embedding_model():
    global _embedding_model
    if _embedding_model is None:
        from sentence_transformers import SentenceTransformer
        # 更换为你想使用的模型
        _embedding_model = SentenceTransformer("your-model-name")
    return _embedding_model
```

**注意**：更换模型后需执行 `jobdeer rebuild-index` 重新构建向量索引。

### 如何添加新的搜索模式？

1. 在 `jobdeer/search.py` 中实现新的搜索函数：

```python
def search_custom(query_str: str, limit: int = 100) -> List[Dict[str, Any]]:
    """自定义搜索逻辑"""
    # 你的实现
    return results
```

2. 在 `jobdeer/web.py` 的 `api_search()` 路由中添加新模式处理：

```python
elif mode == "custom":
    results = search_custom(q, limit=limit)
```

3. 在 `jobdeer/cli.py` 的 `cmd_search()` 中添加新模式处理
4. 在 `jobdeer/templates/index.html` 的下拉选择器中添加新选项
5. 在 `jobdeer/static/app.js` 的 `searchMode` 逻辑中处理新模式

### 如何迁移到其他数据库？

当前使用 JSON 文件存储，适合小规模数据（<10,000 条）。如需迁移到数据库：

1. 修改 `jobdeer/models.py` 中的 `JobStore` 类，替换为 SQLAlchemy 或 ORM
2. 替换 `store.add()`、`store.get()` 等方法为数据库操作
3. Tantivy 索引和向量索引无需修改，它们独立于数据存储

### 性能优化建议

| 场景 | 优化方案 |
|------|---------|
| 数据量 > 10,000 条 | 考虑迁移到 PostgreSQL/SQLite |
| 搜索速度慢 | 检查索引是否最新，执行 `rebuild-index` |
| 语义搜索加载慢 | 预加载模型到内存，或使用更轻量的模型 |
| 并发写入冲突 | filelock 已处理，无需额外配置 |
| Web 响应慢 | 使用 gunicorn 替代 Flask 内置服务器 |

### 代码结构规范

```
jobdeer/
├── __init__.py          # 包入口，导出公共 API
├── models.py            # 数据模型和存储
├── search.py            # 搜索引擎实现
├── excel_io.py          # Excel 导入导出
├── llm_helper.py        # LLM 辅助功能
├── output.py            # CLI 输出工具函数
├── cli.py               # CLI 入口
├── web.py               # Web 服务端
├── templates/
│   └── index.html       # Web 页面模板
└── static/
    ├── app.js           # 前端逻辑
    └── style.css        # 自定义样式
```

**开发建议**：

- 新增功能优先在对应模块中实现
- CLI 和 Web 共用业务逻辑（models/search/excel_io）
- 前端修改只需刷新浏览器（Alpine.js 响应式）
- 每次修改搜索相关代码后，务必重建索引测试

---

## 🤝 贡献指南

欢迎提交 Issue 和 Pull Request！

### 开发环境设置

```bash
# 1. 克隆仓库
git clone https://github.com/jobdeer/jobdeer.git
cd jobdeer

# 2. 安装开发依赖
pip install -e ".[dev,semantic]"

# 3. 运行测试
pytest

# 4. 代码格式化
black jobdeer/
ruff check jobdeer/
```

### 提交 PR 流程

1. Fork 本仓库
2. 创建特性分支：`git checkout -b feature/amazing-feature`
3. 提交修改：`git commit -m 'Add amazing feature'`
4. 推送分支：`git push origin feature/amazing-feature`
5. 提交 Pull Request

---

## 📄 许可证

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

---

## 🙏 致谢

- [Tantivy](https://github.com/quickwit-oss/tantivy) - Rust 全文检索引擎
- [sentence-transformers](https://github.com/UKPLab/sentence-transformers) - 语义向量模型
- [jieba](https://github.com/fxsjy/jieba) - 中文分词
- [Typer](https://typer.tiangolo.com/) - CLI 框架
- [Flask](https://flask.palletsprojects.com/) - Web 框架
- [Alpine.js](https://alpinejs.dev/) - 前端框架
- [TailwindCSS](https://tailwindcss.com/) - CSS 框架

---

## ⚠️ 免责声明

**重要提示**：

1. **使用风险**：本软件按“现状”提供，不提供任何形式的明示或暗示保证，包括但不限于适销性、特定用途适用性和非侵权性的保证。使用本软件所产生的任何风险由用户自行承担。

2. **数据安全**：
   - 本工具处理的数据（包括职位信息、API Key 等）由用户自行管理
   - 建议定期备份 `joblist.json` 数据文件
   - **切勿**将包含 API Key 的配置文件（`.llmdog.yaml`）提交到版本控制系统
   - 使用强密码和安全的 API Key，并定期更换

3. **第三方服务**：
   - 本工具集成的 LLM 服务（通过 llmdog 库）由第三方提供商提供
   - LLM 服务的质量、可用性和准确性不由本工具保证
   - 使用 LLM 功能时，请遵守相关服务提供商的条款和条件
   - LLM 生成的内容可能存在错误，请人工审核后使用

4. **隐私保护**：
   - 本工具在本地运行，数据默认存储在本地文件系统
   - 使用 LLM API 时，职位描述等数据会发送到第三方 API 服务器
   - 请确保您有权限将相关数据发送到外部服务
   - 对于敏感职位信息，建议使用本地部署的 LLM 服务

5. **合规性**：
   - 用户在使用本工具时应遵守适用的法律法规
   - 在收集、存储和处理个人信息时，请遵守相关数据保护法规（如 GDPR、个人信息保护法等）
   - 本工具不提供法律建议，如有合规性问题请咨询专业法律顾问

6. **技术支持**：
   - 本工具为开源软件，社区提供支持
   - 不保证及时的技术响应和问题修复
   - 鼓励用户通过 Issue 和 Pull Request 参与贡献

7. **版本兼容性**：
   - 不同版本之间可能存在不兼容的变更
   - 升级前请仔细阅读更新日志
   - 建议在测试环境验证后再升级到生产环境

**通过使用本软件，您同意接受上述所有条款。如果您不同意这些条款，请不要使用本软件。**

