Spaces:
Sleeping
Sleeping
Commit
·
6c8af71
1
Parent(s):
f1664d6
Refactor LLM service and analysis logic for improved structure and error handling
Browse files- Updated `llm_service.py` to streamline LLM calls and response handling.
- Enhanced `analyse_service.py` with Pydantic models for structured analysis results.
- Improved JSON parsing in `_parse_analysis_response` to handle various response formats.
- Refined prompt templates in `prompt_templates.py` for better clarity and structure.
- Modified `generation_service.py` to support direct job description input for cover letter generation.
- Added test cases in service modules to validate functionality and ensure robustness.
- Updated documentation in `spec.md` to reflect changes in service architecture and functionality.
- docs/spec.md +58 -33
- src/README.md +5 -1
- src/llm/litellm_client.py +1 -1
- src/llm/prompt_templates.py +111 -33
- src/services/analyse_service.py +222 -5
- src/services/generation_service.py +174 -11
- src/services/llm_service.py +12 -28
docs/spec.md
CHANGED
|
@@ -46,50 +46,66 @@ apply-helper/
|
|
| 46 |
|
| 47 |
### 2.2 LLM 集成层
|
| 48 |
|
| 49 |
-
#### `src/services/llm_service.py` —
|
| 50 |
-
- **核心功能**:
|
| 51 |
- **主要函数**:
|
| 52 |
-
- `analyse_llm(jd: str, user_info: str) ->
|
| 53 |
-
- `refine_llm(
|
| 54 |
-
- `generate_resume_llm(
|
| 55 |
-
- `generate_cover_letter_llm(
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
-
|
| 60 |
-
- **自动配置**: 基于环境变量自动检测和配置Azure客户端
|
| 61 |
- **主要函数**:
|
| 62 |
- `call_llm(prompt: str, model: str = None, max_tokens: int = 800, temperature: float = 1.0) -> str`
|
| 63 |
-
- `get_azure_client() ->
|
| 64 |
-
-
|
|
|
|
| 65 |
|
| 66 |
#### `src/llm/prompt_templates.py` — 提示词模板管理
|
| 67 |
- **模板类型**: analyse, refine, generate_resume, generate_cover_letter
|
| 68 |
- **函数**: `get_template(name: str) -> str`
|
| 69 |
-
- **特点**:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
#### 核心处理逻辑
|
| 72 |
-
-
|
| 73 |
-
-
|
| 74 |
-
- **错误处理**:
|
| 75 |
-
-
|
| 76 |
|
| 77 |
|
| 78 |
### 2.3 业务服务层
|
| 79 |
|
| 80 |
#### `src/services/analyse_service.py` — 分析服务
|
| 81 |
-
- **功能**:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
- **主要逻辑**:
|
| 83 |
- 验证输入参数非空
|
| 84 |
- 委托LLM服务进行分析
|
| 85 |
-
-
|
| 86 |
-
-
|
|
|
|
| 87 |
|
| 88 |
#### `src/services/generation_service.py` — 文档生成服务
|
| 89 |
- **功能**: 编排简历和求职信的生成
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
- **主要逻辑**:
|
| 91 |
-
-
|
| 92 |
-
-
|
|
|
|
| 93 |
- 返回生成的文档元组
|
| 94 |
|
| 95 |
### 2.4 模拟数据系统
|
|
@@ -164,11 +180,17 @@ apply-helper/
|
|
| 164 |
|
| 165 |
### 4.2 LLM集成配置
|
| 166 |
|
| 167 |
-
|
| 168 |
|
| 169 |
-
|
| 170 |
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
### 4.3 开发和测试
|
| 174 |
|
|
@@ -177,9 +199,11 @@ apply-helper/
|
|
| 177 |
- `src/mock_data.py` 包含完整测试数据集
|
| 178 |
- 点击"Mock Analyse"按钮即可加载预设结果
|
| 179 |
|
| 180 |
-
|
| 181 |
-
- `src/llm/litellm_client.py`
|
| 182 |
-
-
|
|
|
|
|
|
|
| 183 |
|
| 184 |
### 4.4 架构特点
|
| 185 |
|
|
@@ -189,7 +213,8 @@ apply-helper/
|
|
| 189 |
- 服务层薄包装,便于替换实现
|
| 190 |
|
| 191 |
**扩展点**:
|
| 192 |
-
- 添加新的LLM provider:
|
| 193 |
-
- 添加PDF导出: 实现 `pdf_service.py`
|
| 194 |
-
- 添加新的生成模板:
|
| 195 |
-
-
|
|
|
|
|
|
| 46 |
|
| 47 |
### 2.2 LLM 集成层
|
| 48 |
|
| 49 |
+
#### `src/services/llm_service.py` — LLM服务包装层
|
| 50 |
+
- **核心功能**: 提供LLM调用的统一接口,负责提示词模板格式化和LiteLLM调用
|
| 51 |
- **主要函数**:
|
| 52 |
+
- `analyse_llm(jd: str, user_info: str) -> str`: 工作分析,返回原始LLM响应字符串
|
| 53 |
+
- `refine_llm(summary_json: str, feedback: str) -> str`: 基于反馈优化分析,返回原始LLM响应
|
| 54 |
+
- `generate_resume_llm(summary_json: str, user_info: str) -> str`: 生成Markdown格式简历
|
| 55 |
+
- `generate_cover_letter_llm(job_description: str, user_info: str) -> str`: 生成德语求职信
|
| 56 |
+
|
| 57 |
+
#### `src/llm/litellm_client.py` — LiteLLM客户端集成
|
| 58 |
+
- **多Provider支持**: 优先支持Azure OpenAI,备用支持标准OpenAI
|
| 59 |
+
- **自动配置**: 基于环境变量自动检测和配置最优客户端
|
|
|
|
| 60 |
- **主要函数**:
|
| 61 |
- `call_llm(prompt: str, model: str = None, max_tokens: int = 800, temperature: float = 1.0) -> str`
|
| 62 |
+
- `get_azure_client() -> dict`: 配置Azure OpenAI客户端
|
| 63 |
+
- `get_openai_client() -> dict`: 配置标准OpenAI客户端
|
| 64 |
+
- 包含连接测试和调试模式,支持litellm debug
|
| 65 |
|
| 66 |
#### `src/llm/prompt_templates.py` — 提示词模板管理
|
| 67 |
- **模板类型**: analyse, refine, generate_resume, generate_cover_letter
|
| 68 |
- **函数**: `get_template(name: str) -> str`
|
| 69 |
+
- **特点**:
|
| 70 |
+
- 字典存储,包含详细的JSON输出格式要求
|
| 71 |
+
- ANALYSE_PROMPT: 德国科技市场专家角色,8-12个技能提取,4-6个匹配点分析
|
| 72 |
+
- GENERATE_COVER_LETTER_PROMPT: 德语商务求职信生成,符合德国商务信函规范
|
| 73 |
+
- 所有模板包含明确的输出格式要求和角色设定
|
| 74 |
|
| 75 |
#### 核心处理逻辑
|
| 76 |
+
- **提示词管理**: 从模板获取提示词,使用Python字符串format动态替换参数
|
| 77 |
+
- **LLM调用**: 通过LiteLLM统一接口调用不同provider的模型
|
| 78 |
+
- **错误处理**: LiteLLM层处理API调用异常,服务层处理业务逻辑异常
|
| 79 |
+
- **响应处理**: 返回原始字符串响应,由上层服务负责结构化解析
|
| 80 |
|
| 81 |
|
| 82 |
### 2.3 业务服务层
|
| 83 |
|
| 84 |
#### `src/services/analyse_service.py` — 分析服务
|
| 85 |
+
- **功能**: 职位描述与用户背景的匹配分析,使用Pydantic数据模型
|
| 86 |
+
- **数据模型**: `AnalysisResult(BaseModel)` - 结构化分析结果
|
| 87 |
+
- key_skills: List[str] - 关键技能列表
|
| 88 |
+
- match_points: List[str] - 匹配优势点
|
| 89 |
+
- gap_points: List[str] - 技能差距点
|
| 90 |
+
- suggestions: List[str] - 改进建议
|
| 91 |
+
- pitch: str - 价值主张
|
| 92 |
- **主要逻辑**:
|
| 93 |
- 验证输入参数非空
|
| 94 |
- 委托LLM服务进行分析
|
| 95 |
+
- `_parse_analysis_response()` - 复杂JSON解析逻辑,处理代码块包装
|
| 96 |
+
- `analyse()` - 初始分析,返回AnalysisResult对象
|
| 97 |
+
- `refine()` - 基于反馈优化分析,使用model_dump_json()序列化
|
| 98 |
|
| 99 |
#### `src/services/generation_service.py` — 文档生成服务
|
| 100 |
- **功能**: 编排简历和求职信的生成
|
| 101 |
+
- **主要接口**:
|
| 102 |
+
- `generate_resume(summary: AnalysisResult, user_info: str) -> str` - 生成简历
|
| 103 |
+
- `generate_cover_letter(job_description: str, user_info: str) -> str` - 生成求职信
|
| 104 |
+
- `generate_both(summary: AnalysisResult, user_info: str, job_description: str) -> Tuple[str, str]` - 生成完整文档对
|
| 105 |
- **主要逻辑**:
|
| 106 |
+
- 使用AnalysisResult对象和原始文本作为输入
|
| 107 |
+
- 调用LLM服务生成简历(Markdown)和求职信(德语文本)
|
| 108 |
+
- 支持动态导入处理,兼容模块和独立运行
|
| 109 |
- 返回生成的文档元组
|
| 110 |
|
| 111 |
### 2.4 模拟数据系统
|
|
|
|
| 180 |
|
| 181 |
### 4.2 LLM集成配置
|
| 182 |
|
| 183 |
+
**多Provider支持**: 优先Azure OpenAI,备用标准OpenAI
|
| 184 |
|
| 185 |
+
**Azure OpenAI配置**: AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_MODEL, AZURE_OPENAI_API_VERSION(默认2024-12-01-preview)
|
| 186 |
|
| 187 |
+
**OpenAI配置**: OPENAI_API_KEY(使用gpt-3.5-turbo模型)
|
| 188 |
+
|
| 189 |
+
**配置逻辑**:
|
| 190 |
+
- 优先检测Azure配置,如果完整则使用Azure
|
| 191 |
+
- Azure不可用时回退到标准OpenAI
|
| 192 |
+
- 支持模型参数覆盖,但仅支持azure/前缀的模型
|
| 193 |
+
- 包含详细的配置验证和错误提示
|
| 194 |
|
| 195 |
### 4.3 开发和测试
|
| 196 |
|
|
|
|
| 199 |
- `src/mock_data.py` 包含完整测试数据集
|
| 200 |
- 点击"Mock Analyse"按钮即可加载预设结果
|
| 201 |
|
| 202 |
+
**调试和测试模式**:
|
| 203 |
+
- `python src/llm/litellm_client.py` - 测试LLM连接和配置验证
|
| 204 |
+
- `python src/services/analyse_service.py` - 测试分析服务完整流程
|
| 205 |
+
- `python src/services/generation_service.py` - 测试文档生成流程
|
| 206 |
+
- 包含litellm._turn_on_debug()调试支持
|
| 207 |
|
| 208 |
### 4.4 架构特点
|
| 209 |
|
|
|
|
| 213 |
- 服务层薄包装,便于替换实现
|
| 214 |
|
| 215 |
**扩展点**:
|
| 216 |
+
- 添加新的LLM provider: 扩展 `litellm_client.py` 中的客户端配置函数
|
| 217 |
+
- 添加PDF导出: 实现 `pdf_service.py` 集成WeasyPrint
|
| 218 |
+
- 添加新的生成模板: 在 `prompt_templates.py` 中添加新模板常量
|
| 219 |
+
- 数据验证增强: 当前使用Pydantic AnalysisResult模型,可扩展更多验证模型
|
| 220 |
+
- 多语言支持: 扩展prompt_templates支持不同语言的求职信生成
|
src/README.md
CHANGED
|
@@ -69,4 +69,8 @@ apply-helper/
|
|
| 69 |
│ └── prompt_templates.py
|
| 70 |
├── exports/ # Directory for exported PDF files
|
| 71 |
└── tests/ # Unit tests
|
| 72 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
│ └── prompt_templates.py
|
| 70 |
├── exports/ # Directory for exported PDF files
|
| 71 |
└── tests/ # Unit tests
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
PYTHONPATH=src python -m services.analyse_service
|
| 76 |
+
|
src/llm/litellm_client.py
CHANGED
|
@@ -92,7 +92,7 @@ def call_llm(prompt: str, model: Optional[str] = None, max_tokens: int = 800, te
|
|
| 92 |
|
| 93 |
except Exception as e:
|
| 94 |
print(f"An error occurred while calling the LLM: {e}")
|
| 95 |
-
raise
|
| 96 |
|
| 97 |
|
| 98 |
if __name__ == "__main__":
|
|
|
|
| 92 |
|
| 93 |
except Exception as e:
|
| 94 |
print(f"An error occurred while calling the LLM: {e}")
|
| 95 |
+
raise e # Re-raise the original exception
|
| 96 |
|
| 97 |
|
| 98 |
if __name__ == "__main__":
|
src/llm/prompt_templates.py
CHANGED
|
@@ -2,27 +2,66 @@
|
|
| 2 |
Manages all prompt templates for the application.
|
| 3 |
"""
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
|
| 10 |
**Job Description:**
|
| 11 |
{jd}
|
| 12 |
|
| 13 |
-
**
|
| 14 |
{user}
|
| 15 |
|
| 16 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
{{
|
| 18 |
-
"key_skills": ["skill1", "skill2",
|
| 19 |
-
"match_points": ["point1", "point2",
|
| 20 |
-
"gap_points": ["
|
| 21 |
-
"suggestions": ["suggestion1", "suggestion2",
|
| 22 |
-
"pitch": "
|
| 23 |
}}
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
You are a professional career coach. A summary has been generated based on a Job Description and User Information.
|
| 27 |
Now, refine this summary based on the user's feedback.
|
| 28 |
|
|
@@ -32,16 +71,21 @@ Now, refine this summary based on the user's feedback.
|
|
| 32 |
**User Feedback:**
|
| 33 |
{feedback}
|
| 34 |
|
|
|
|
|
|
|
| 35 |
**Your refined analysis should be in the same JSON format as the original:**
|
|
|
|
| 36 |
{{
|
| 37 |
-
"key_skills": ["skill1", "skill2",
|
| 38 |
-
"match_points": ["point1", "point2",
|
| 39 |
-
"gap_points": ["point1", "point2",
|
| 40 |
-
"suggestions": ["suggestion1", "suggestion2",
|
| 41 |
"pitch": "A short, compelling pitch for the user."
|
| 42 |
}}
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
You are a professional resume writer. Based on the following analysis and user information, generate a professional resume in Markdown format.
|
| 46 |
The resume should be tailored to the job description implied in the analysis.
|
| 47 |
|
|
@@ -56,30 +100,64 @@ The resume should be tailored to the job description implied in the analysis.
|
|
| 56 |
- Highlight the most relevant skills and experiences.
|
| 57 |
- Use professional and action-oriented language.
|
| 58 |
- Do not include any introductory text like "Here is the resume:".
|
| 59 |
-
"""
|
| 60 |
-
"generate_cover_letter": """
|
| 61 |
-
You are a professional cover letter writer. Based on the following analysis and user information, generate a compelling cover letter.
|
| 62 |
-
The cover letter should be tailored to the job description implied in the analysis.
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
{user}
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
-
|
| 75 |
"""
|
| 76 |
-
}
|
| 77 |
|
| 78 |
def get_template(name: str) -> str:
|
| 79 |
"""
|
| 80 |
Retrieves a prompt template by name.
|
| 81 |
"""
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
if not template:
|
| 84 |
raise ValueError(f"Template '{name}' not found.")
|
| 85 |
return template
|
|
|
|
| 2 |
Manages all prompt templates for the application.
|
| 3 |
"""
|
| 4 |
|
| 5 |
+
ANALYSE_PROMPT = """
|
| 6 |
+
You are an expert career strategist specializing in the German tech job market with deep understanding of software development roles and industry requirements.
|
| 7 |
+
|
| 8 |
+
**Task:** Analyze the Job Description against the candidate's background to provide comprehensive career guidance.
|
| 9 |
|
| 10 |
**Job Description:**
|
| 11 |
{jd}
|
| 12 |
|
| 13 |
+
**Candidate Information:**
|
| 14 |
{user}
|
| 15 |
|
| 16 |
+
**Analysis Requirements:**
|
| 17 |
+
|
| 18 |
+
1. **Key Skills Extraction** - Identify 8-12 critical technical and soft skills from the JD, prioritizing:
|
| 19 |
+
- Programming languages and frameworks specifically mentioned
|
| 20 |
+
- Cloud platforms and DevOps tools
|
| 21 |
+
- Development methodologies (Agile, TDD, etc.)
|
| 22 |
+
- Domain-specific requirements (e.g., scalability, testing)
|
| 23 |
+
- Soft skills (teamwork, communication, learning agility)
|
| 24 |
+
|
| 25 |
+
2. **Match Analysis** - Identify 4-6 strong alignment points where candidate excels:
|
| 26 |
+
- Direct technology matches with concrete project examples
|
| 27 |
+
- Relevant experience depth and breadth
|
| 28 |
+
- Methodological alignment (Agile, CI/CD, etc.)
|
| 29 |
+
- Cultural fit indicators
|
| 30 |
+
- Language requirements fulfillment
|
| 31 |
+
|
| 32 |
+
3. **Gap Analysis** - Identify 3-5 key areas for improvement:
|
| 33 |
+
- Missing or limited experience with required technologies
|
| 34 |
+
- Lack of specific domain knowledge
|
| 35 |
+
- Experience depth gaps (junior vs senior expectations)
|
| 36 |
+
- Certification or formal training gaps
|
| 37 |
+
|
| 38 |
+
4. **Strategic Recommendations** - Provide 4-6 actionable, specific suggestions:
|
| 39 |
+
- Concrete learning paths for technology gaps
|
| 40 |
+
- Project ideas to demonstrate missing skills
|
| 41 |
+
- Networking or certification opportunities
|
| 42 |
+
- Interview preparation focus areas
|
| 43 |
+
- Portfolio enhancement recommendations
|
| 44 |
+
|
| 45 |
+
5. **Value Proposition** - Craft a compelling 2-3 sentence pitch highlighting:
|
| 46 |
+
- Unique combination of candidate's strengths
|
| 47 |
+
- How their background solves company's specific needs
|
| 48 |
+
- Growth potential and learning agility
|
| 49 |
+
|
| 50 |
+
**CRITICAL: Response must be valid JSON only. No additional text.**
|
| 51 |
+
|
| 52 |
+
**Required JSON format:**
|
| 53 |
+
```json
|
| 54 |
{{
|
| 55 |
+
"key_skills": ["skill1", "skill2", "skill3", "skill4", "skill5", "skill6", "skill7", "skill8"],
|
| 56 |
+
"match_points": ["point1", "point2", "point3", "point4"],
|
| 57 |
+
"gap_points": ["gap1", "gap2", "gap3"],
|
| 58 |
+
"suggestions": ["suggestion1", "suggestion2", "suggestion3", "suggestion4"],
|
| 59 |
+
"pitch": "Compelling 2-3 sentence value proposition for this specific role."
|
| 60 |
}}
|
| 61 |
+
```
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
REFINE_PROMPT = """
|
| 65 |
You are a professional career coach. A summary has been generated based on a Job Description and User Information.
|
| 66 |
Now, refine this summary based on the user's feedback.
|
| 67 |
|
|
|
|
| 71 |
**User Feedback:**
|
| 72 |
{feedback}
|
| 73 |
|
| 74 |
+
**IMPORTANT: Your response must be valid JSON only. Do not include any text before or after the JSON.**
|
| 75 |
+
|
| 76 |
**Your refined analysis should be in the same JSON format as the original:**
|
| 77 |
+
```json
|
| 78 |
{{
|
| 79 |
+
"key_skills": ["skill1", "skill2", "skill3"],
|
| 80 |
+
"match_points": ["point1", "point2", "point3"],
|
| 81 |
+
"gap_points": ["point1", "point2", "point3"],
|
| 82 |
+
"suggestions": ["suggestion1", "suggestion2", "suggestion3"],
|
| 83 |
"pitch": "A short, compelling pitch for the user."
|
| 84 |
}}
|
| 85 |
+
```
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
GENERATE_RESUME_PROMPT = """
|
| 89 |
You are a professional resume writer. Based on the following analysis and user information, generate a professional resume in Markdown format.
|
| 90 |
The resume should be tailored to the job description implied in the analysis.
|
| 91 |
|
|
|
|
| 100 |
- Highlight the most relevant skills and experiences.
|
| 101 |
- Use professional and action-oriented language.
|
| 102 |
- Do not include any introductory text like "Here is the resume:".
|
| 103 |
+
"""
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
+
GENERATE_COVER_LETTER_PROMPT = """
|
| 106 |
+
You are an expert HR recruiter in the German IT job market.
|
| 107 |
|
| 108 |
+
Generate a professional, tailored German Cover Letter (Anschreiben) for the given Job Description and Candidate CV. Follow these updated style rules:
|
| 109 |
+
|
| 110 |
+
### Structure
|
| 111 |
+
1. **Opening**
|
| 112 |
+
- 1–2 sentences only.
|
| 113 |
+
- First sentence: Mention the company/job role and express motivation.
|
| 114 |
+
- Second sentence: Short self-introduction (degree, focus, current status).
|
| 115 |
+
|
| 116 |
+
2. **Main Section**
|
| 117 |
+
- Use **bullet points** (•) to highlight 3–5 most relevant skills and experiences from the CV, directly aligned with the Job Description.
|
| 118 |
+
- Each bullet point should be max. 2 lines, clear and specific.
|
| 119 |
+
- Where possible, mention concrete results/outcomes (STAR method).
|
| 120 |
+
|
| 121 |
+
3. **Summary Paragraph**
|
| 122 |
+
- 1 short paragraph (2–3 sentences).
|
| 123 |
+
- Summarize why the candidate fits the role, combining motivation and competence.
|
| 124 |
+
|
| 125 |
+
4. **Closing**
|
| 126 |
+
- Avoid overly flattering phrases.
|
| 127 |
+
- Clearly state availability (e.g., "Ab Oktober stehe ich Ihnen flexibel zur Verfügung.").
|
| 128 |
+
- Express interest in an interview in a confident, neutral tone (e.g., "Gerne erläutere ich Ihnen in einem persönlichen Gespräch, wie ich Ihr Team unterstützen kann.").
|
| 129 |
+
- End with: "Mit freundlichen Grüßen, [Name]"
|
| 130 |
+
|
| 131 |
+
### Style Guidelines
|
| 132 |
+
- Professional, concise, maximum 1 page.
|
| 133 |
+
- Use active voice and confident tone.
|
| 134 |
+
- Avoid generic filler sentences.
|
| 135 |
+
- Balance between motivation and technical skills.
|
| 136 |
+
- Output in proper German business letter style.
|
| 137 |
+
|
| 138 |
+
### Inputs
|
| 139 |
+
- Candidate CV:
|
| 140 |
{user}
|
| 141 |
|
| 142 |
+
- Job Description:
|
| 143 |
+
{jd}
|
| 144 |
+
|
| 145 |
+
### Output
|
| 146 |
+
- A complete Cover Letter (Anschreiben) in German, following the structure and style above.
|
| 147 |
"""
|
|
|
|
| 148 |
|
| 149 |
def get_template(name: str) -> str:
|
| 150 |
"""
|
| 151 |
Retrieves a prompt template by name.
|
| 152 |
"""
|
| 153 |
+
templates = {
|
| 154 |
+
"analyse": ANALYSE_PROMPT,
|
| 155 |
+
"refine": REFINE_PROMPT,
|
| 156 |
+
"generate_resume": GENERATE_RESUME_PROMPT,
|
| 157 |
+
"generate_cover_letter": GENERATE_COVER_LETTER_PROMPT
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
template = templates.get(name)
|
| 161 |
if not template:
|
| 162 |
raise ValueError(f"Template '{name}' not found.")
|
| 163 |
return template
|
src/services/analyse_service.py
CHANGED
|
@@ -1,18 +1,235 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
| 2 |
from . import llm_service
|
| 3 |
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
Analyzes the job description and user information.
|
| 7 |
"""
|
| 8 |
if not jd or not user_info:
|
| 9 |
raise ValueError("Job Description and User Info cannot be empty.")
|
| 10 |
-
|
|
|
|
| 11 |
|
| 12 |
-
def refine(summary:
|
| 13 |
"""
|
| 14 |
Refines the analysis based on user feedback.
|
| 15 |
"""
|
| 16 |
if not summary or not feedback:
|
| 17 |
raise ValueError("Summary and feedback cannot be empty.")
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from typing import List
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
from . import llm_service
|
| 5 |
|
| 6 |
+
|
| 7 |
+
class AnalysisResult(BaseModel):
|
| 8 |
+
"""Structured result from job description and user analysis."""
|
| 9 |
+
key_skills: List[str] = Field(description="Key skills required for the job")
|
| 10 |
+
match_points: List[str] = Field(description="Points where user matches job requirements")
|
| 11 |
+
gap_points: List[str] = Field(description="Areas where user lacks required skills")
|
| 12 |
+
suggestions: List[str] = Field(description="Actionable suggestions for improvement")
|
| 13 |
+
pitch: str = Field(description="A compelling pitch for the user")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _parse_analysis_response(response: str) -> AnalysisResult:
|
| 17 |
+
"""
|
| 18 |
+
Parses a JSON string from the LLM response into AnalysisResult.
|
| 19 |
+
"""
|
| 20 |
+
try:
|
| 21 |
+
# Remove code block markers if present
|
| 22 |
+
if "```json" in response:
|
| 23 |
+
start = response.find("```json") + 7
|
| 24 |
+
end = response.find("```", start)
|
| 25 |
+
if end != -1:
|
| 26 |
+
json_block = response[start:end].strip()
|
| 27 |
+
else:
|
| 28 |
+
json_block = response[start:].strip()
|
| 29 |
+
elif "```" in response and "{" in response:
|
| 30 |
+
# Handle cases where code blocks exist but without json marker
|
| 31 |
+
start = response.find("```") + 3
|
| 32 |
+
end = response.find("```", start)
|
| 33 |
+
if end != -1:
|
| 34 |
+
json_block = response[start:end].strip()
|
| 35 |
+
else:
|
| 36 |
+
json_block = response[start:].strip()
|
| 37 |
+
else:
|
| 38 |
+
# Find JSON object boundaries
|
| 39 |
+
start = response.find('{')
|
| 40 |
+
end = response.rfind('}') + 1
|
| 41 |
+
if start == -1 or end == 0:
|
| 42 |
+
raise ValueError("No JSON object found in response")
|
| 43 |
+
json_block = response[start:end]
|
| 44 |
+
|
| 45 |
+
# Clean up the JSON block
|
| 46 |
+
json_block = json_block.strip()
|
| 47 |
+
|
| 48 |
+
data = json.loads(json_block)
|
| 49 |
+
return AnalysisResult(**data)
|
| 50 |
+
except (json.JSONDecodeError, IndexError, ValueError) as e:
|
| 51 |
+
print(f"Failed to parse JSON response: {e}\nResponse was: {response[:500]}...")
|
| 52 |
+
raise ValueError("LLM response was not in the expected JSON format.")
|
| 53 |
+
|
| 54 |
+
def analyse(jd: str, user_info: str) -> AnalysisResult:
|
| 55 |
"""
|
| 56 |
Analyzes the job description and user information.
|
| 57 |
"""
|
| 58 |
if not jd or not user_info:
|
| 59 |
raise ValueError("Job Description and User Info cannot be empty.")
|
| 60 |
+
response_text = llm_service.analyse_llm(jd, user_info)
|
| 61 |
+
return _parse_analysis_response(response_text)
|
| 62 |
|
| 63 |
+
def refine(summary: AnalysisResult, feedback: str) -> AnalysisResult:
|
| 64 |
"""
|
| 65 |
Refines the analysis based on user feedback.
|
| 66 |
"""
|
| 67 |
if not summary or not feedback:
|
| 68 |
raise ValueError("Summary and feedback cannot be empty.")
|
| 69 |
+
summary_json = summary.model_dump_json(indent=2)
|
| 70 |
+
response_text = llm_service.refine_llm(summary_json, feedback)
|
| 71 |
+
return _parse_analysis_response(response_text)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
# Test data
|
| 76 |
+
test_jd = """
|
| 77 |
+
Seit 25 Jahren unterstützen wir bei inovex mit rund 500 IT-Expert:innen Unternehmen bei der Digitalisierung und Agilisierung ihres Kerngeschäfts an 8 Standorten deutschlandweit.
|
| 78 |
+
|
| 79 |
+
Im Team Application Development entwickeln wir die verschiedensten Anwendungen, von skalierbaren Backends über performante Web Frontends bis hin zu modernen Apps, die teilweise von mehreren Millionen Menschen täglich benutzt werden. Unsere Konzepte implementieren wir bevorzugt mit Open Source Software im Rahmen agiler Projekte. Bei uns verläuft kein Projekt wie das andere – je nach Kunde, Team und Technologie-Stack ist jede Lösung einzigartig.
|
| 80 |
+
|
| 81 |
+
*Wir bei inovex mögen Vielfalt und Individualität. Deshalb freuen wir uns, wenn du unser Team noch bunter machst.
|
| 82 |
+
|
| 83 |
+
Was du bei uns bewegen kannst
|
| 84 |
+
Gemeinsam mit deinem agilen Projektteam setzt du Projekte für Kunden unterschiedlicher Branchen um. Ihr seid cross-funktional aufgestellt und liefert alles aus einer Hand – von der Architektur über die Entwicklung bis zum DevOps-Betrieb. You build it, you run it.
|
| 85 |
+
|
| 86 |
+
Dein Job beginnt nicht erst mit der Entwicklung von Software: Du unterstützt bei der Aufnahme und Analyse der Kundenanforderungen und stimmst dich eng mit deinem Team ab.
|
| 87 |
+
|
| 88 |
+
Du fühlst dich gleichermaßen im Frontend wie im Backend zu Hause und bringst deine Erfahrung in beiden Bereichen ein – egal, ob es um Architekturen oder die Gestaltung von Single Page Applications geht.
|
| 89 |
+
|
| 90 |
+
Du unterstützt deine Kolleg:innen mithilfe automatisierter Testing-Verfahren (Unit Testing, CI/CD, Ende-zu-Ende-Tests u. v. m.) und sorgst somit für eine kontinuierliche Qualitätskontrolle und -optimierung.
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
In unseren Projekten verwenden wir häufig folgende Technologien:
|
| 95 |
+
|
| 96 |
+
Kotlin, Java, Go, Python oder C#
|
| 97 |
+
|
| 98 |
+
Angular, React, TypeScript, Vue.js., Next.js
|
| 99 |
+
|
| 100 |
+
Docker, Gitlab
|
| 101 |
+
|
| 102 |
+
AWS, GCP, Azure, Kubernetes
|
| 103 |
+
|
| 104 |
+
Wer gut zu uns passen würde
|
| 105 |
+
Ob du ein Studium oder eine Ausbildung im IT-Bereich abgeschlossen hast, ist für uns nebensächlich – für uns zählen deine fachlichen Skills und deine Persönlichkeit:
|
| 106 |
+
|
| 107 |
+
Du hast mehr als ein Jahr Berufserfahrung und fundierte Kenntnisse in mehreren der oben genannten Technologien. Idealerweise hast du bereits erste Berührungspunkte mit Cloud-Umgebungen gesammelt.
|
| 108 |
+
|
| 109 |
+
Du hast den Anspruch, dich in neue Technologien einzuarbeiten und sie in Bezug auf den Projekteinsatz zu prüfen.
|
| 110 |
+
|
| 111 |
+
Du beteiligst dich gerne an Code Reviews und setzt dich konstruktiv mit deiner Arbeit auseinander.
|
| 112 |
+
|
| 113 |
+
Es ist dir wichtig, ein umfassendes Verständnis der Standpunkte deiner Team-Mitglieder und Kunden zu entwickeln.
|
| 114 |
+
|
| 115 |
+
Sehr gute Deutschkenntnisse sind Voraussetzung (mind. Level C1), gute Englischkenntnisse runden dein Profil ab.
|
| 116 |
+
|
| 117 |
+
Ab Oktober freuen wir uns auf Verstärkung in unserem Team!
|
| 118 |
+
|
| 119 |
+
Was wir dir bieten
|
| 120 |
+
inovex Culture: Kommunikation auf Augenhöhe, flache Hierarchien, Arbeiten in selbstorganisierten und agilen Teams, Vertrauen und Offenheit, starker Zusammenhalt
|
| 121 |
+
Level Up: Weiterbildungsbudget, eigene inovex Academy, Onboarding- und Mentoring-Programm, enger Austausch mit der Community
|
| 122 |
+
Balance: Flexibles und mobiles Arbeiten, keine Kernarbeitszeiten, Workation, 30 Tage Urlaub
|
| 123 |
+
Familienfreundlichkeit: Flexibles Arbeitszeitmodell, Zuschuss zur Kinderbetreuung, Überstunden nur im Ausnahmefall
|
| 124 |
+
Individualität: Betriebliche Altersvorsorge, E-Gym Wellpass, inovex Sportgruppen/ Sport-Community
|
| 125 |
+
Mobilität: JobRad, Firmenwagen-Leasing
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
test_user_info = """
|
| 129 |
+
Start Hanbin Chen
|
| 130 |
+
Master Informatik-Absolvent | LLM-Forscher | Full-Stack-Softwareentwickler
|
| 131 |
+
Ausbildung
|
| 132 |
+
RWTH Aachen University, Master | Informatik 2020.10 - 2025.07
|
| 133 |
+
Masterarbeit: MedKGC (Medical Knowledge Graph Construction)
|
| 134 |
+
• Entwicklung einer End-to-End-Pipeline für die medizinische Wissensextraktion aus Ra-
|
| 135 |
+
diologieberichten mittels LLMs und deren Darstellung in Knowledge Graphs.
|
| 136 |
+
• Implementierung von Named Entity Recognition mit GPT-4o und Llama3 unter Verwen-
|
| 137 |
+
dung eines neuartigen RAG-Ansatzes und Integration von UMLS-Ontologien.
|
| 138 |
+
• Durchführung von Few-Shot Prompting zur Bewertung der LLM-Leistung.
|
| 139 |
+
Fachhochschule Hannover(HsH), Bachelor | Angewandte Informatik 2017.09 - 2020.09
|
| 140 |
+
Berufsprofil
|
| 141 |
+
Erfahrener Softwareentwickler
|
| 142 |
+
spezialisiert auf skalierbare Agenten-
|
| 143 |
+
basierte LLM-Anwendungen.
|
| 144 |
+
Vertraut mit modernen KI-Tools
|
| 145 |
+
und nutzt diese zur Steigerung
|
| 146 |
+
von Produktivität und Qualität.
|
| 147 |
+
Projekterfahrung
|
| 148 |
+
Technische Fähigkeiten
|
| 149 |
+
Programmiersprachen
|
| 150 |
+
Python JavaScript Java
|
| 151 |
+
Webtechnologien & Frameworks
|
| 152 |
+
React Next.js TailwindCSS
|
| 153 |
+
Backend & Frameworks
|
| 154 |
+
RESTful API FASTapi Spring Boot
|
| 155 |
+
Datenbank PostgreSQL MySQL
|
| 156 |
+
ORM SQLAlchemy Hibernate
|
| 157 |
+
Entwicklung & Project
|
| 158 |
+
Agile Scrum TDD DDD
|
| 159 |
+
KI & LLMs
|
| 160 |
+
RAG Agent n8n Dify
|
| 161 |
+
GitHub Copilot Cursor Claude Code
|
| 162 |
+
DevOps & Cloud
|
| 163 |
+
Docker CI/CD GitHub Actions
|
| 164 |
+
AWS Vercel Supabase
|
| 165 |
+
Sprachen
|
| 166 |
+
• Full-Stack Entwickler | EmAilX - KI-gestützte E-Mail-Management SaaS (2025.07)
|
| 167 |
+
– Aufbau einer Web Anwendung mit React, Next.js und TailwindCSS.
|
| 168 |
+
– Integration von Azure OpenAI und Agent LLM für intelligente E-Mail-Erstellung.
|
| 169 |
+
– Implementierung Unternehmens-Authentifizierung und Deployment auf Vercel.
|
| 170 |
+
• Full-Stack-Entwickler | KI-verstärkte Scrum-Management-Plattform (2025.06)
|
| 171 |
+
– Aufbau einer Full-Stack-Plattform mit React, Next.js und TailwindCSS.
|
| 172 |
+
– Implementierung interaktiver Kanban-Boards und Chatbot-UI.
|
| 173 |
+
– Aufbau eines LLM-Agent-Systems zur Anforderungsanalyse und automatischen
|
| 174 |
+
Generierung von Projekt-Issues.
|
| 175 |
+
• Full-Stack-Entwickler | AWS Hackathon (Top 25/1000+ auf Devpost) (2024.09–
|
| 176 |
+
2024.10)
|
| 177 |
+
– Aufbau eines Vue.js-Frontends und Gradio-Chatbots mit Amazon Bedrock und LLaMA
|
| 178 |
+
3.1, ermöglicht RAG-basierte Teambildung und Spielereinblicke für VALORANT.
|
| 179 |
+
– Leitung der Datenvorverarbeitung und des SQL-Agent-Designs.
|
| 180 |
+
– Deployment der Full-Stack-App auf AWS EC2.
|
| 181 |
+
• Android-Anwendungsentwickler | Should I Stay or Should I Go (2019.09 - 2020.09)
|
| 182 |
+
– Entwicklung einer standortbasierten Anwendung mit MVVM-Architektur und Kotlin.
|
| 183 |
+
– Anwendung der Test-Driven Development (TDD)-Methodik, mit Tests vor der Im-
|
| 184 |
+
plementierung zur Gewährleistung der Codequalität.
|
| 185 |
+
• Java-Desktop-Entwickler | Vier-Gewinnt-Spiel (2018.09 - 2019.05)
|
| 186 |
+
– Implementierung eines Brettspiels in Java mit KI-Gegner (Minimax-Algorithmus).
|
| 187 |
+
– Anwendung von TDD mit Unit-Tests für robuste Entwicklung und Codequalität.
|
| 188 |
+
Studentische Tätigkeiten
|
| 189 |
+
Deutsch (C1) Englisch (Fließend)
|
| 190 |
+
Kontaktinformationen
|
| 191 |
+
+49 15732636211
|
| 192 | |
| 193 |
+
HanbinChen97
|
| 194 |
+
Hanbin Chen
|
| 195 |
+
Persönliche Website
|
| 196 |
+
• Full-Stack-Entwickler | MedAgent (RWTH Aachen University) (2024.03 -2025.07)
|
| 197 |
+
– Architektur und Entwicklung des Frontends mit React und Next.js auf Basis einer
|
| 198 |
+
modernen Architektur.
|
| 199 |
+
– Implementierung eines RESTful-API-Backends mit Fokus auf Skalierbarkeit und
|
| 200 |
+
Wartbarkeit.
|
| 201 |
+
– Nutzung von CI/CD-Pipelines mit GitHub Actions und Docker.
|
| 202 |
+
• Datenanalyst | KlimDim (ISAC GmbH) (2023.03 - 2023.12)
|
| 203 |
+
– Entwicklung von Datenverarbeitungs-Pipelines und -visualisierung.
|
| 204 |
+
– Nutzung von GitLab-Pipeline und Docker für Entwicklung und automatisierte Tests.
|
| 205 |
+
"""
|
| 206 |
+
|
| 207 |
+
try:
|
| 208 |
+
# Test analyse function
|
| 209 |
+
print("Testing analyse function...")
|
| 210 |
+
analysis_result = analyse(test_jd, test_user_info)
|
| 211 |
+
|
| 212 |
+
print("Analysis Result:")
|
| 213 |
+
print("=" * 50)
|
| 214 |
+
print(f"key_skills: {analysis_result.key_skills}")
|
| 215 |
+
print(f"match_points: {analysis_result.match_points}")
|
| 216 |
+
print(f"gap_points: {analysis_result.gap_points}")
|
| 217 |
+
print(f"suggestions: {analysis_result.suggestions}")
|
| 218 |
+
print(f"pitch: {analysis_result.pitch}")
|
| 219 |
+
print("\n")
|
| 220 |
+
|
| 221 |
+
# Test refine function
|
| 222 |
+
print("Testing refine function...")
|
| 223 |
+
test_feedback = "Please emphasize more on my Python skills and cloud experience"
|
| 224 |
+
|
| 225 |
+
refined_result = refine(analysis_result, test_feedback)
|
| 226 |
+
print("Refined Result:")
|
| 227 |
+
print("=" * 50)
|
| 228 |
+
print(f"key_skills: {refined_result.key_skills}")
|
| 229 |
+
print(f"match_points: {refined_result.match_points}")
|
| 230 |
+
print(f"gap_points: {refined_result.gap_points}")
|
| 231 |
+
print(f"suggestions: {refined_result.suggestions}")
|
| 232 |
+
print(f"pitch: {refined_result.pitch}")
|
| 233 |
+
|
| 234 |
+
except Exception as e:
|
| 235 |
+
print(f"Error during analysis: {e}")
|
src/services/generation_service.py
CHANGED
|
@@ -1,25 +1,37 @@
|
|
| 1 |
-
from typing import Tuple
|
| 2 |
-
|
|
|
|
|
|
|
| 3 |
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
Generates the resume (Markdown).
|
| 7 |
"""
|
| 8 |
if not summary:
|
| 9 |
raise ValueError("Analysis summary cannot be empty.")
|
| 10 |
|
| 11 |
-
|
|
|
|
| 12 |
|
| 13 |
-
def generate_cover_letter(
|
| 14 |
"""
|
| 15 |
-
Generates the cover letter (text).
|
| 16 |
"""
|
| 17 |
-
if not
|
| 18 |
-
raise ValueError("
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
return llm_service.generate_cover_letter_llm(
|
| 21 |
|
| 22 |
-
def generate_both(summary:
|
| 23 |
"""
|
| 24 |
Generates both the resume (Markdown) and cover letter (text).
|
| 25 |
"""
|
|
@@ -27,6 +39,157 @@ def generate_both(summary: Dict[str, Any], user_info: str) -> Tuple[str, str]:
|
|
| 27 |
raise ValueError("Analysis summary cannot be empty.")
|
| 28 |
|
| 29 |
resume_md = generate_resume(summary, user_info)
|
| 30 |
-
cover_letter_txt = generate_cover_letter(
|
| 31 |
|
| 32 |
return resume_md, cover_letter_txt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
| 5 |
|
| 6 |
+
try:
|
| 7 |
+
from .analyse_service import AnalysisResult
|
| 8 |
+
from . import llm_service
|
| 9 |
+
except ImportError:
|
| 10 |
+
from services.analyse_service import AnalysisResult
|
| 11 |
+
import services.llm_service as llm_service
|
| 12 |
+
|
| 13 |
+
def generate_resume(summary: AnalysisResult, user_info: str) -> str:
|
| 14 |
"""
|
| 15 |
Generates the resume (Markdown).
|
| 16 |
"""
|
| 17 |
if not summary:
|
| 18 |
raise ValueError("Analysis summary cannot be empty.")
|
| 19 |
|
| 20 |
+
summary_json = summary.model_dump_json(indent=2)
|
| 21 |
+
return llm_service.generate_resume_llm(summary_json, user_info)
|
| 22 |
|
| 23 |
+
def generate_cover_letter(job_description: str, user_info: str) -> str:
|
| 24 |
"""
|
| 25 |
+
Generates the cover letter (text) using direct text inputs.
|
| 26 |
"""
|
| 27 |
+
if not job_description:
|
| 28 |
+
raise ValueError("Job description cannot be empty.")
|
| 29 |
+
if not user_info:
|
| 30 |
+
raise ValueError("User info cannot be empty.")
|
| 31 |
|
| 32 |
+
return llm_service.generate_cover_letter_llm(job_description, user_info)
|
| 33 |
|
| 34 |
+
def generate_both(summary: AnalysisResult, user_info: str, job_description: str) -> Tuple[str, str]:
|
| 35 |
"""
|
| 36 |
Generates both the resume (Markdown) and cover letter (text).
|
| 37 |
"""
|
|
|
|
| 39 |
raise ValueError("Analysis summary cannot be empty.")
|
| 40 |
|
| 41 |
resume_md = generate_resume(summary, user_info)
|
| 42 |
+
cover_letter_txt = generate_cover_letter(job_description, user_info)
|
| 43 |
|
| 44 |
return resume_md, cover_letter_txt
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
# Test data
|
| 49 |
+
test_summary = """
|
| 50 |
+
key_skills: ['Full-Stack Development', 'Frontend Technologies (React, Next.js, Vue.js, Angular, TypeScript)', 'Backend Development (Java, Kotlin, Go, Python, C#, Spring Boot, FASTAPI)', 'DevOps (Docker, CI/CD, Gitlab/GitHub Actions)', 'Cloud Platforms (AWS, GCP, Azure, Kubernetes)', 'Agile Methodologies (Scrum, TDD, DDD)', 'Automated Testing (Unit Testing, End-to-End, CI/CD)', 'RESTful APIs', 'Database Management (PostgreSQL, MySQL, ORM)', 'German (C1) & English language skills']
|
| 51 |
+
match_points: ['Mehrjährige Erfahrung in Full-Stack-Entwicklung mit Fokus auf Frontend (React, Next.js, Vue.js) und Backend (Java, Spring Boot, FASTAPI, Python-Systeme)', 'Nachgewiesene Cloud-Erfahrung (AWS, Vercel, Supabase) sowie Kenntnisse in DevOps und Deployment mit Docker, CI/CD und GitHub/GitLab Actions', 'Angewendete agile Methoden (Scrum, TDD, DDD) und umfassende Nutzung moderner Softwareentwicklungspraktiken', 'Fundierte Erfahrung im Bereich automatisiertes Testen (TDD, Unit-Tests) in mehreren Projekten', 'Sehr gute Deutschkenntnisse (C1) und fließendes Englisch']
|
| 52 |
+
gap_points: ['Kaum direkte Projekterfahrung mit Angular, TypeScript oder Vue.js als primärem Framework (nur kurz im AWS Hackathon erwähnt)', 'Wenig oder keine explizite Erfahrung mit Kotlin, Go oder C# in aktuellen Projekten (Kotlin vor allem für Android in einer älteren Tätigkeit)', 'Keine Nennung von GCP, Azure oder Kubernetes im Stack (lediglich AWS und Vercel/Supabase)', 'Wenig explizite Erfahrung im Bereich DevOps-Betrieb auf Gitlab (meist GitHub Actions, einmal GitLab-Pipeline)', 'Unit Test/End-to-End Testverfahren werden zwar erwähnt, aber Umfang und Tiefe könnten konkreter dargestellt werden']
|
| 53 |
+
suggestions: ['Vertiefe dein Praxiswissen in Angular, TypeScript und Vue.js durch kleinere Side-Projekte oder Online-Kurse, um gegenüber inovex als breiter aufgestellter Frontend-Experte aufzutreten.', 'Baue gezielt Kenntnisse in Kubernetes und, falls möglich, zumindest Grundkenntnisse in einem weiteren Cloud-Anbieter (z.B. GCP oder Azure) auf.', 'Arbeite aktiv an Projekten, bei denen die Implementierung und Wartung von Gitlab-basierten DevOps-Pipelines und Cloud-Betrieb gefordert ist.', 'Dokumentiere und präsentiere mehr Beispiele für die Anwendung von End-to-End-Tests und komplexerer Testautomatisierung in deinen letzten Projekten.', 'Sprich im Bewerbungsgespräch gezielt über deine Cross-Functionality und wie du dich zügig in neue Technologien (z.B. Go, C#) einarbeiten kannst.']
|
| 54 |
+
pitch: Hanbin Chen bringt als Master-Absolvent und erfahrener Full-Stack-Entwickler die perfekte Mischung aus fundierten Software-Engineering-Skills, Cloud-Expertise und Innovationsgeist mit. Seine Projekterfolge im Bereich LLMs, agiler Full-Stack-Entwicklung sowie CI/CD und Cloud-Deployment zeichnen ihn als modernen Teamplayer mit Wachstumspotenzial aus. Er ist bereit, seine Vielseitigkeit und Begeisterung für neue Technologien in Ihr cross-funktionales Team bei inovex einzubringen und weiter auszubauen.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
test_jd = """
|
| 58 |
+
Seit 25 Jahren unterstützen wir bei inovex mit rund 500 IT-Expert:innen Unternehmen bei der Digitalisierung und Agilisierung ihres Kerngeschäfts an 8 Standorten deutschlandweit.
|
| 59 |
+
|
| 60 |
+
Im Team Application Development entwickeln wir die verschiedensten Anwendungen, von skalierbaren Backends über performante Web Frontends bis hin zu modernen Apps, die teilweise von mehreren Millionen Menschen täglich benutzt werden. Unsere Konzepte implementieren wir bevorzugt mit Open Source Software im Rahmen agiler Projekte. Bei uns verläuft kein Projekt wie das andere – je nach Kunde, Team und Technologie-Stack ist jede Lösung einzigartig.
|
| 61 |
+
|
| 62 |
+
*Wir bei inovex mögen Vielfalt und Individualität. Deshalb freuen wir uns, wenn du unser Team noch bunter machst.
|
| 63 |
+
|
| 64 |
+
Was du bei uns bewegen kannst
|
| 65 |
+
Gemeinsam mit deinem agilen Projektteam setzt du Projekte für Kunden unterschiedlicher Branchen um. Ihr seid cross-funktional aufgestellt und liefert alles aus einer Hand – von der Architektur über die Entwicklung bis zum DevOps-Betrieb. You build it, you run it.
|
| 66 |
+
|
| 67 |
+
Dein Job beginnt nicht erst mit der Entwicklung von Software: Du unterstützt bei der Aufnahme und Analyse der Kundenanforderungen und stimmst dich eng mit deinem Team ab.
|
| 68 |
+
|
| 69 |
+
Du fühlst dich gleichermaßen im Frontend wie im Backend zu Hause und bringst deine Erfahrung in beiden Bereichen ein – egal, ob es um Architekturen oder die Gestaltung von Single Page Applications geht.
|
| 70 |
+
|
| 71 |
+
Du unterstützt deine Kolleg:innen mithilfe automatisierter Testing-Verfahren (Unit Testing, CI/CD, Ende-zu-Ende-Tests u. v. m.) und sorgst somit für eine kontinuierliche Qualitätskontrolle und -optimierung.
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
In unseren Projekten verwenden wir häufig folgende Technologien:
|
| 76 |
+
|
| 77 |
+
Kotlin, Java, Go, Python oder C#
|
| 78 |
+
|
| 79 |
+
Angular, React, TypeScript, Vue.js., Next.js
|
| 80 |
+
|
| 81 |
+
Docker, Gitlab
|
| 82 |
+
|
| 83 |
+
AWS, GCP, Azure, Kubernetes
|
| 84 |
+
|
| 85 |
+
Wer gut zu uns passen würde
|
| 86 |
+
Ob du ein Studium oder eine Ausbildung im IT-Bereich abgeschlossen hast, ist für uns nebensächlich – für uns zählen deine fachlichen Skills und deine Persönlichkeit:
|
| 87 |
+
|
| 88 |
+
Du hast mehr als ein Jahr Berufserfahrung und fundierte Kenntnisse in mehreren der oben genannten Technologien. Idealerweise hast du bereits erste Berührungspunkte mit Cloud-Umgebungen gesammelt.
|
| 89 |
+
|
| 90 |
+
Du hast den Anspruch, dich in neue Technologien einzuarbeiten und sie in Bezug auf den Projekteinsatz zu prüfen.
|
| 91 |
+
|
| 92 |
+
Du beteiligst dich gerne an Code Reviews und setzt dich konstruktiv mit deiner Arbeit auseinander.
|
| 93 |
+
|
| 94 |
+
Es ist dir wichtig, ein umfassendes Verständnis der Standpunkte deiner Team-Mitglieder und Kunden zu entwickeln.
|
| 95 |
+
|
| 96 |
+
Sehr gute Deutschkenntnisse sind Voraussetzung (mind. Level C1), gute Englischkenntnisse runden dein Profil ab.
|
| 97 |
+
|
| 98 |
+
Ab Oktober freuen wir uns auf Verstärkung in unserem Team!
|
| 99 |
+
|
| 100 |
+
Was wir dir bieten
|
| 101 |
+
inovex Culture: Kommunikation auf Augenhöhe, flache Hierarchien, Arbeiten in selbstorganisierten und agilen Teams, Vertrauen und Offenheit, starker Zusammenhalt
|
| 102 |
+
Level Up: Weiterbildungsbudget, eigene inovex Academy, Onboarding- und Mentoring-Programm, enger Austausch mit der Community
|
| 103 |
+
Balance: Flexibles und mobiles Arbeiten, keine Kernarbeitszeiten, Workation, 30 Tage Urlaub
|
| 104 |
+
Familienfreundlichkeit: Flexibles Arbeitszeitmodell, Zuschuss zur Kinderbetreuung, Überstunden nur im Ausnahmefall
|
| 105 |
+
Individualität: Betriebliche Altersvorsorge, E-Gym Wellpass, inovex Sportgruppen/ Sport-Community
|
| 106 |
+
Mobilität: JobRad, Firmenwagen-Leasing
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
test_user_info = """
|
| 110 |
+
Start Hanbin Chen
|
| 111 |
+
Master Informatik-Absolvent | LLM-Forscher | Full-Stack-Softwareentwickler
|
| 112 |
+
Ausbildung
|
| 113 |
+
RWTH Aachen University, Master | Informatik 2020.10 - 2025.07
|
| 114 |
+
Masterarbeit: MedKGC (Medical Knowledge Graph Construction)
|
| 115 |
+
• Entwicklung einer End-to-End-Pipeline für die medizinische Wissensextraktion aus Ra-
|
| 116 |
+
diologieberichten mittels LLMs und deren Darstellung in Knowledge Graphs.
|
| 117 |
+
• Implementierung von Named Entity Recognition mit GPT-4o und Llama3 unter Verwen-
|
| 118 |
+
dung eines neuartigen RAG-Ansatzes und Integration von UMLS-Ontologien.
|
| 119 |
+
• Durchführung von Few-Shot Prompting zur Bewertung der LLM-Leistung.
|
| 120 |
+
Fachhochschule Hannover(HsH), Bachelor | Angewandte Informatik 2017.09 - 2020.09
|
| 121 |
+
Berufsprofil
|
| 122 |
+
Erfahrener Softwareentwickler
|
| 123 |
+
spezialisiert auf skalierbare Agenten-
|
| 124 |
+
basierte LLM-Anwendungen.
|
| 125 |
+
Vertraut mit modernen KI-Tools
|
| 126 |
+
und nutzt diese zur Steigerung
|
| 127 |
+
von Produktivität und Qualität.
|
| 128 |
+
Projekterfahrung
|
| 129 |
+
Technische Fähigkeiten
|
| 130 |
+
Programmiersprachen
|
| 131 |
+
Python JavaScript Java
|
| 132 |
+
Webtechnologien & Frameworks
|
| 133 |
+
React Next.js TailwindCSS
|
| 134 |
+
Backend & Frameworks
|
| 135 |
+
RESTful API FASTapi Spring Boot
|
| 136 |
+
Datenbank PostgreSQL MySQL
|
| 137 |
+
ORM SQLAlchemy Hibernate
|
| 138 |
+
Entwicklung & Project
|
| 139 |
+
Agile Scrum TDD DDD
|
| 140 |
+
KI & LLMs
|
| 141 |
+
RAG Agent n8n Dify
|
| 142 |
+
GitHub Copilot Cursor Claude Code
|
| 143 |
+
DevOps & Cloud
|
| 144 |
+
Docker CI/CD GitHub Actions
|
| 145 |
+
AWS Vercel Supabase
|
| 146 |
+
Sprachen
|
| 147 |
+
• Full-Stack Entwickler | EmAilX - KI-gestützte E-Mail-Management SaaS (2025.07)
|
| 148 |
+
– Aufbau einer Web Anwendung mit React, Next.js und TailwindCSS.
|
| 149 |
+
– Integration von Azure OpenAI und Agent LLM für intelligente E-Mail-Erstellung.
|
| 150 |
+
– Implementierung Unternehmens-Authentifizierung und Deployment auf Vercel.
|
| 151 |
+
• Full-Stack-Entwickler | KI-verstärkte Scrum-Management-Plattform (2025.06)
|
| 152 |
+
– Aufbau einer Full-Stack-Plattform mit React, Next.js und TailwindCSS.
|
| 153 |
+
– Implementierung interaktiver Kanban-Boards und Chatbot-UI.
|
| 154 |
+
– Aufbau eines LLM-Agent-Systems zur Anforderungsanalyse und automatischen
|
| 155 |
+
Generierung von Projekt-Issues.
|
| 156 |
+
• Full-Stack-Entwickler | AWS Hackathon (Top 25/1000+ auf Devpost) (2024.09–
|
| 157 |
+
2024.10)
|
| 158 |
+
– Aufbau eines Vue.js-Frontends und Gradio-Chatbots mit Amazon Bedrock und LLaMA
|
| 159 |
+
3.1, ermöglicht RAG-basierte Teambildung und Spielereinblicke für VALORANT.
|
| 160 |
+
– Leitung der Datenvorverarbeitung und des SQL-Agent-Designs.
|
| 161 |
+
– Deployment der Full-Stack-App auf AWS EC2.
|
| 162 |
+
• Android-Anwendungsentwickler | Should I Stay or Should I Go (2019.09 - 2020.09)
|
| 163 |
+
– Entwicklung einer standortbasierten Anwendung mit MVVM-Architektur und Kotlin.
|
| 164 |
+
– Anwendung der Test-Driven Development (TDD)-Methodik, mit Tests vor der Im-
|
| 165 |
+
plementierung zur Gewährleistung der Codequalität.
|
| 166 |
+
• Java-Desktop-Entwickler | Vier-Gewinnt-Spiel (2018.09 - 2019.05)
|
| 167 |
+
– Implementierung eines Brettspiels in Java mit KI-Gegner (Minimax-Algorithmus).
|
| 168 |
+
– Anwendung von TDD mit Unit-Tests für robuste Entwicklung und Codequalität.
|
| 169 |
+
Studentische Tätigkeiten
|
| 170 |
+
Deutsch (C1) Englisch (Fließend)
|
| 171 |
+
Kontaktinformationen
|
| 172 |
+
+49 15732636211
|
| 173 | |
| 174 |
+
HanbinChen97
|
| 175 |
+
Hanbin Chen
|
| 176 |
+
Persönliche Website
|
| 177 |
+
• Full-Stack-Entwickler | MedAgent (RWTH Aachen University) (2024.03 -2025.07)
|
| 178 |
+
– Architektur und Entwicklung des Frontends mit React und Next.js auf Basis einer
|
| 179 |
+
modernen Architektur.
|
| 180 |
+
– Implementierung eines RESTful-API-Backends mit Fokus auf Skalierbarkeit und
|
| 181 |
+
Wartbarkeit.
|
| 182 |
+
– Nutzung von CI/CD-Pipelines mit GitHub Actions und Docker.
|
| 183 |
+
• Datenanalyst | KlimDim (ISAC GmbH) (2023.03 - 2023.12)
|
| 184 |
+
– Entwicklung von Datenverarbeitungs-Pipelines und -visualisierung.
|
| 185 |
+
– Nutzung von GitLab-Pipeline und Docker für Entwicklung und automatisierte Tests.
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
try:
|
| 190 |
+
cover_letter = generate_cover_letter(test_jd, test_user_info)
|
| 191 |
+
print("Generated Cover Letter:")
|
| 192 |
+
print("=" * 50)
|
| 193 |
+
print(cover_letter)
|
| 194 |
+
except Exception as e:
|
| 195 |
+
print(f"Error generating cover letter: {e}")
|
src/services/llm_service.py
CHANGED
|
@@ -1,54 +1,38 @@
|
|
| 1 |
-
import
|
| 2 |
-
|
|
|
|
| 3 |
|
| 4 |
from llm.litellm_client import call_llm
|
| 5 |
from llm.prompt_templates import get_template
|
| 6 |
|
| 7 |
-
def _parse_json_response(response: str) -> Dict[str, Any]:
|
| 8 |
-
"""
|
| 9 |
-
Parses a JSON string from the LLM response.
|
| 10 |
-
"""
|
| 11 |
-
try:
|
| 12 |
-
# The LLM might return a string enclosed in ```json ... ```, so we find the JSON block.
|
| 13 |
-
json_block = response[response.find('{'):response.rfind('}')+1]
|
| 14 |
-
return json.loads(json_block)
|
| 15 |
-
except (json.JSONDecodeError, IndexError) as e:
|
| 16 |
-
print(f"Failed to parse JSON response: {e}\nResponse was: {response}")
|
| 17 |
-
raise ValueError("LLM response was not in the expected JSON format.")
|
| 18 |
-
|
| 19 |
|
| 20 |
-
def analyse_llm(jd: str, user_info: str) ->
|
| 21 |
"""
|
| 22 |
Calls the LLM to analyse the JD and user info.
|
| 23 |
"""
|
| 24 |
prompt = get_template("analyse").format(jd=jd, user=user_info)
|
| 25 |
-
|
| 26 |
-
return _parse_json_response(response_text)
|
| 27 |
|
| 28 |
|
| 29 |
-
def refine_llm(
|
| 30 |
"""
|
| 31 |
Calls the LLM to refine an existing analysis based on user feedback.
|
| 32 |
"""
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
response_text = call_llm(prompt)
|
| 36 |
-
return _parse_json_response(response_text)
|
| 37 |
|
| 38 |
|
| 39 |
-
def generate_resume_llm(
|
| 40 |
"""
|
| 41 |
Calls the LLM to generate a resume.
|
| 42 |
"""
|
| 43 |
-
|
| 44 |
-
prompt = get_template("generate_resume").format(summary=summary_str, user=user_info)
|
| 45 |
return call_llm(prompt)
|
| 46 |
|
| 47 |
|
| 48 |
-
def generate_cover_letter_llm(
|
| 49 |
"""
|
| 50 |
Calls the LLM to generate a cover letter.
|
| 51 |
"""
|
| 52 |
-
|
| 53 |
-
prompt = get_template("generate_cover_letter").format(summary=summary_str, user=user_info)
|
| 54 |
return call_llm(prompt)
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
| 4 |
|
| 5 |
from llm.litellm_client import call_llm
|
| 6 |
from llm.prompt_templates import get_template
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
+
def analyse_llm(jd: str, user_info: str) -> str:
|
| 10 |
"""
|
| 11 |
Calls the LLM to analyse the JD and user info.
|
| 12 |
"""
|
| 13 |
prompt = get_template("analyse").format(jd=jd, user=user_info)
|
| 14 |
+
return call_llm(prompt)
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
+
def refine_llm(summary_json: str, feedback: str) -> str:
|
| 18 |
"""
|
| 19 |
Calls the LLM to refine an existing analysis based on user feedback.
|
| 20 |
"""
|
| 21 |
+
prompt = get_template("refine").format(summary=summary_json, feedback=feedback)
|
| 22 |
+
return call_llm(prompt)
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
+
def generate_resume_llm(summary_json: str, user_info: str) -> str:
|
| 26 |
"""
|
| 27 |
Calls the LLM to generate a resume.
|
| 28 |
"""
|
| 29 |
+
prompt = get_template("generate_resume").format(summary=summary_json, user=user_info)
|
|
|
|
| 30 |
return call_llm(prompt)
|
| 31 |
|
| 32 |
|
| 33 |
+
def generate_cover_letter_llm(job_description: str, user_info: str) -> str:
|
| 34 |
"""
|
| 35 |
Calls the LLM to generate a cover letter.
|
| 36 |
"""
|
| 37 |
+
prompt = get_template("generate_cover_letter").format(jd=job_description, user=user_info)
|
|
|
|
| 38 |
return call_llm(prompt)
|