FabianHildebrandt commited on
Commit
7259e45
·
verified ·
1 Parent(s): 2248107

Upload 11 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ application_assistant.png filter=lfs diff=lfs merge=lfs -text
37
+ interface.png filter=lfs diff=lfs merge=lfs -text
38
+ mas_architecture.png filter=lfs diff=lfs merge=lfs -text
CV.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # John Doe
2
+
3
+ 📍 Berlin, Germany
4
5
+ 📞 +49 123 456789
6
+ 🔗 [LinkedIn](https://linkedin.com/in/johndoe) • [GitHub](https://github.com/johndoe5629)
7
+
8
+ ---
9
+
10
+ ## 🧠 About Me
11
+
12
+ Experienced software engineer with 10+ years in full-stack development, including backend architecture and modern frontend frameworks. Known for my problem-solving mindset and adaptability across tech stacks. Recently, I have developed a strong interest in **data science and machine learning**, driven by my passion for data-driven decision-making and advanced analytics. I’m actively seeking opportunities to leverage my engineering background in the field of data science.
13
+
14
+ ---
15
+
16
+ ## 🎓 Education
17
+
18
+ **M.Sc. in Computer Science**
19
+ *Technical University of Munich (TUM)* – Munich, Germany
20
+ _2011 – 2013_
21
+
22
+ **B.Sc. in Computer Science**
23
+ *University of Stuttgart* – Stuttgart, Germany
24
+ _2008 – 2011_
25
+
26
+ **Professional Development Courses**
27
+ - *IBM Data Science Professional Certificate*, Coursera (2024)
28
+ - *Deep Learning Specialization*, deeplearning.ai (2024)
29
+ - *DataCamp Tracks*: Python for Data Science, Data Analyst in Python
30
+
31
+ ---
32
+
33
+ ## 💼 Work Experience
34
+
35
+ ### **Senior Frontend Developer**
36
+ **TechWorks GmbH** – Berlin, Germany
37
+ *2020 – Present*
38
+ - Led a team of 4 frontend developers in designing SPAs using React and TypeScript
39
+ - Collaborated with data analysts to design dashboards and data visualizations
40
+ - Integrated REST APIs and GraphQL for scalable frontend architecture
41
+
42
+ ### **Full-Stack Developer**
43
+ **NextGen Solutions** – Hamburg, Germany
44
+ *2016 – 2020*
45
+ - Designed and maintained microservice-based architecture with Node.js and Express
46
+ - Developed internal analytics tools in Python and Flask
47
+ - Contributed to DevOps initiatives: Docker, CI/CD, AWS Lambda functions
48
+
49
+ ### **Backend Developer**
50
+ **ByteForge AG** – Stuttgart, Germany
51
+ *2013 – 2016*
52
+ - Developed and optimized backend systems using Java and PostgreSQL
53
+ - Worked on authentication and authorization systems for enterprise clients
54
+ - Refactored legacy codebase improving API performance by 30%
55
+
56
+ ---
57
+
58
+ ## 📊 Data Science Projects
59
+
60
+ - **Customer Churn Prediction**
61
+ Logistic regression model trained on real-world telecom dataset; achieved 85% accuracy. Deployed using Flask and Docker.
62
+
63
+ - **Movie Recommender System**
64
+ Collaborative filtering project using Python, Pandas, and Surprise library.
65
+
66
+ - **EDA on COVID-19 Global Data**
67
+ Performed extensive exploratory data analysis and visualized trends using Seaborn and Plotly.
68
+
69
+ ---
70
+
71
+ ## 🧰 Technical Skills
72
+
73
+ **Languages:** Python, JavaScript (ES6+), Java, SQL, TypeScript
74
+ **Frameworks:** React, Node.js, Flask, Express
75
+ **Tools & Libraries:** Pandas, NumPy, scikit-learn, TensorFlow, Keras, Matplotlib, Plotly
76
+ **Databases:** PostgreSQL, MongoDB, Redis
77
+ **Cloud & DevOps:** AWS (EC2, S3, Lambda), Docker, GitHub Actions
78
+ **Others:** REST APIs, GraphQL, CI/CD, Agile (Scrum), JIRA
79
+
80
+ ---
81
+
82
+ ## 🌐 Languages
83
+
84
+ - **English:** Full professional proficiency
85
+ - **German:** Native
86
+ - **Spanish:** Intermediate
87
+
88
+ ---
89
+
90
+ ## 🏆 Certifications
91
+
92
+ - IBM Data Science Professional Certificate (2024)
93
+ - AWS Certified Developer – Associate (2023)
94
+ - Deep Learning Specialization – Andrew Ng (2024)
95
+
96
+ ---
97
+
98
+ ## 🤝 References
99
+
100
+ Available upon request.
README.md CHANGED
@@ -1,14 +1,86 @@
1
  ---
2
  title: Application Assistant
3
- emoji: 💻
4
- colorFrom: yellow
5
- colorTo: gray
6
- sdk: gradio
7
  sdk_version: 5.33.0
8
  app_file: app.py
9
- pinned: false
10
- license: mit
11
  short_description: AI-powered assistant to help you land your dream job.
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Application Assistant
3
+ emoji: 🦛
 
 
 
4
  sdk_version: 5.33.0
5
  app_file: app.py
6
+ fullWidth: true
7
+ header: default
8
  short_description: AI-powered assistant to help you land your dream job.
9
+ tags:
10
+ - agent-demo-track
11
+ pinned: true
12
  ---
13
 
14
+ # 🏆 Application Assistant
15
+
16
+ ### Submission for the: 🤖 Gradio Agents & MCP Hackathon 2025 🚀
17
+ - **Track:** 3 – *Agentic Demo (agent-demo-track)*
18
+ - **Author:** Fabian Hildebrandt
19
+
20
+ ---
21
+
22
+ ## 📌 Overview
23
+
24
+ **Application Assistant** 🦛🤗 is an intelligent, multi-agent application designed to support users throughout their job application journey.
25
+
26
+ ![Application Assistant](application_assistant.png)
27
+
28
+ This project demonstrates the power of **multi-agent collaboration** powered by **Gradio**’s UI framework and the **LangGraph**'s agent framework.
29
+
30
+ Whether you're writing a compelling cover letter, preparing for an interview, or polishing your CV, **Application Assistant** is here to help.
31
+
32
+
33
+ [Video Tutorial](https://github.com/FabianHildebrandt/Application-Assistant/tree/main) (available on the GitHub Repo)
34
+
35
+ https://github.com/user-attachments/assets/4f17e8f7-8993-40bb-9de1-ce72c0cab473
36
+
37
+
38
+
39
+ ## 🤖 What Can It Do?
40
+
41
+ - 📝 **Cover Letter Generator:** Draft, refine, and customize cover letters tailored to specific job roles.
42
+ - 🎤 **Interview Prep:** Simulate mock interview question sets and feedback.
43
+ - 👥 **Multi-Agent Collaboration:** Breaks down tasks into sub-tasks handled by specialized agents (e.g., Writer agent, critic agent, interview agent).
44
+
45
+
46
+ ## 🧠 Multi-Agent System
47
+
48
+ The Application Assistant uses a modular agent system:
49
+
50
+ ![Architecture](mas_architecture.png)
51
+
52
+
53
+ These agents communicate and collaborate via a central orchestrator.
54
+
55
+
56
+ ## 🚀 Getting Started
57
+
58
+ ### 🔧 Prerequisites
59
+ - Python installed
60
+ - `pip` installed
61
+ - Google AI API key -> [Get a free API key](https://ai.google.dev/gemini-api/docs/api-key)
62
+
63
+ ### 📥 Installation
64
+
65
+ ```bash
66
+ git clone
67
+ cd application-assistant
68
+ pip install -r requirements.txt
69
+ python app.py
70
+ ```
71
+
72
+ ---
73
+ ## 🧭 Using the Tool
74
+
75
+ Once you've launched the app (by running `python app.py`), open your default browser and navigate to the Gradio interface at `http://localhost:7860`.
76
+
77
+ ![Interface](interface.png)
78
+
79
+ From there, you can:
80
+ 1. **Input Your Details** such as job descriptions, existing CV and previous motivation letters and your general motivation.
81
+ 2. **Interact with the Agents** using the Q&A Chatbot.
82
+ 3. **Copy** your polished text or copy it directly for use in job applications.
83
+
84
+ The interface is designed to be intuitive, responsive, and ready to support you at any stage of your job application journey.
85
+
86
+ The app is also compatible with a Jupyter environment.
app.py ADDED
@@ -0,0 +1,1096 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %% [markdown]
2
+ # ## Application assistant
3
+
4
+ # %% [markdown]
5
+ # ## Import
6
+
7
+ # %%
8
+ from docling.document_converter import DocumentConverter
9
+ import tqdm as notebook_tqdm
10
+ from pydantic import BaseModel, Field
11
+ import os
12
+ from typing import Optional, Any, Literal, Dict, List, Tuple
13
+ from typing_extensions import TypedDict
14
+ from langgraph.graph import StateGraph, START, END
15
+ from langgraph.types import Command
16
+ from langchain_openai import ChatOpenAI
17
+ from langchain_google_genai import ChatGoogleGenerativeAI
18
+ from langchain_core.prompts import ChatPromptTemplate
19
+ # from langfuse.callback import CallbackHandler
20
+ import gradio as gr
21
+ import contextlib
22
+ from io import StringIO
23
+ import docx
24
+ from pathlib import Path
25
+ import re
26
+ from typing import Union
27
+ from dotenv import load_dotenv
28
+
29
+ load_dotenv()
30
+
31
+ # %% [markdown]
32
+ # ## Telemetry/ Observability
33
+ # - Used for debugging, disabled on prod
34
+
35
+ # %%
36
+ langfuse_handler = None
37
+ # langfuse_handler = CallbackHandler()
38
+ if langfuse_handler:
39
+ TRACING = True
40
+
41
+ # %% [markdown]
42
+ # ## API Key
43
+
44
+ # %%
45
+ USE_GOOGLE = False
46
+ try:
47
+ API_KEY = os.environ["NEBIUS_KEY"]
48
+ MODEL_NAME = "Qwen/Qwen3-30B-A3B-fast"
49
+ ENDPOINT_URL = "https://api.studio.nebius.com/v1/"
50
+ print("Using Nebius API")
51
+ except:
52
+ try:
53
+ API_KEY = os.environ["GOOGLE_API_KEY"]
54
+ MODEL_NAME = os.environ["GOOGLE_DEPLOYMENT_NAME"]
55
+ USE_GOOGLE = True
56
+ print("Using Google API")
57
+ except:
58
+ raise ValueError("No NEBIUS API Key was found")
59
+
60
+ # %% [markdown]
61
+ # ## Structured outputs
62
+
63
+ # %%
64
+ class Feedback(BaseModel):
65
+ feedback : str = Field("", description="Constructive feedback, stating the points that can be improved.")
66
+ quality_flag : Literal["PERFECT", "NEEDS IMPROVEMENT"] = Field("NEEDS IMPROVEMENT", description="Tells whether the cover letter needs to be reworked.")
67
+
68
+ class MultiStepPlan(BaseModel):
69
+ reasoning : str = Field("", description="The multi-step reasoning required to break down the user query in a plan.")
70
+ plan : List[Literal["critic_agent", "writer_agent", "recruiter_agent", "team_lead_agent","interview_agent"]] = Field("END", description="The list of agents required to fulfill the user request determined by the Orchestrator.")
71
+
72
+ # %% [markdown]
73
+ # ## Agent state
74
+
75
+ # %%
76
+ class AgentDescription(TypedDict):
77
+ "Agent description containing the title, system prompt and description."
78
+ title : str
79
+ description : str
80
+ system_prompt : str
81
+
82
+ class ApplicationAgentState(BaseModel):
83
+ """State of the cover letter writer agent."""
84
+ user_query : Optional[str] = Field("", description="User task for the agents to fulfill.")
85
+ iterations : Optional[int] = Field(0, description="Counter for the evaluation-optimization loop of the cover letter.")
86
+ max_iterations : Optional[int] = Field(3, description="Maximum number of iterations for the evaluation-optimization loop of the cover letter.")
87
+ available_agents : Dict[str, AgentDescription] = Field(description="A dictionary of the available agents.")
88
+ cv: Optional[str] = Field("",description="CV content parsed as a Markdown format from the document.")
89
+ job_description : Optional[str] = Field("", description="Job description.")
90
+ skills : Optional[str] = Field("", description="Required skills extracted from the job description")
91
+ motivation : Optional[str] = Field("", description="You're desired job profiles and general motivation.")
92
+ examples : Optional[str] = Field("", description="Examples of previous cover letters.")
93
+ phase : Literal["PLAN", "EXECUTE", "ANSWER"] = Field("PLAN", description="Current phase of the agent")
94
+ messages : List[Tuple[str,str]] = Field([], description="List of agent thoughts (agent, agent response).")
95
+ final_answer : str = Field("", description="Final answer generated after task execution.")
96
+ plan : List[Literal["critic_agent", "writer_agent", "recruiter_agent", "team_lead_agent","interview_agent"]] = Field([],description="The current list of tasks to execute")
97
+ cover_letter: Optional[str] = Field("", description="The cover letter for the specified job.")
98
+ connected_skills : Optional[str] = Field("", description="Skills from the job description connected to previous working experience from the CV.")
99
+ feedback : str = Field("", description="Written feedback from the critic agent regarding the cover letter.")
100
+
101
+ # %% [markdown]
102
+ # ## System prompts
103
+
104
+ # %%
105
+ general_prefix = """
106
+ You are part of a collaborative multi-agent system called the *Application Assistant*.
107
+ This system consists of specialized agents working together to evaluate, improve, and support job applications.
108
+ Each agent has a distinct role and expertise, and you are encouraged to consider and integrate relevant information or insights produced by other agents when available.
109
+ Sharing context and building on each other's outputs will help generate higher-quality and more comprehensive results.
110
+ Operate within your designated role, but feel free to utilize the shared context from other agents to inform your responses when appropriate.
111
+ """
112
+
113
+
114
+ writer_prompt = """
115
+ You are a technical recruiter with years of experience helping candidates land roles in tech companies. Your job is to assist the user in crafting exceptional, personalized cover letters for tech positions.
116
+
117
+ Strict Style Guidelines:
118
+ 1. Use professional but naturally flowing language appropriate for a proficient non-native English speaker.
119
+ 2. Use the provided information by the user containing the CV, the job description, and if available previous motivation letters, the general motivation for the intended job change
120
+ 3. Stick to the provided information. Don't ever make up facts, experience or add any quantifiable numbers, that are not explicitly mentioned.
121
+
122
+ Use the following structure to write a good motivation letter:
123
+ [STRUCTURE]
124
+ 1. Opening paragraph
125
+ Goal: Grab attention, show enthusiasm, and state the role.
126
+ - Mention the specific job title. If possible, summarize long job title names and capture the most important aspects.
127
+ - Say why you’re excited about the company (specific project, mission, tech stack, etc.).
128
+ - Say how the job aligns with your personal goals
129
+
130
+ Example:
131
+ I’m thrilled to apply for the Backend Engineer role at Stripe. As someone who’s followed your API design philosophy and admired your commitment to developer experience, I’m excited about the opportunity to contribute to a team building scalable financial infrastructure.
132
+
133
+ 2. Body Paragraph 1 – Your Value Proposition
134
+
135
+ Goal: Show how your experience matches their needs.
136
+ - Focus on 1–2 major accomplishments relevant to the role.
137
+ - Use number to underline your achievements, but only if they are explicitly mentioned in the povided information (e.g., “reduced latency by 30%”).
138
+ - Highlight how you have already used key technologies or skills they mentioned in the job ad.
139
+
140
+ Example:
141
+ At Plaid, I led a team optimizing real-time data sync across financial institutions, reducing sync errors by 40% and increasing transaction throughput by 25%. My experience with Go and distributed systems directly aligns with Stripe’s scale and architecture.
142
+
143
+ 3. Body Paragraph 2 – Culture & Fit
144
+
145
+ Goal: Show alignment with company values and team dynamics.
146
+ - Briefly show why you’re a good cultural fit.
147
+ - Mention soft skills (e. g. collaboration, leadership, adaptability).
148
+ - Tie it back to something unique about the company.
149
+
150
+ Example:
151
+ Beyond the code, I thrive in collaborative, feedback-rich environments. I appreciate how Stripe emphasizes intellectual humility and long-term thinking — qualities that have defined my best work experiences.
152
+
153
+ 4. Closing Paragraph – The Ask
154
+
155
+ Goal: Finish strong and express interest in the next step.
156
+ - Reiterate excitement.
157
+ - Thank them for considering you.
158
+ - Invite them to review your resume or portfolio.
159
+ - Mention you’re looking forward to the next step.
160
+
161
+ Example:
162
+ Thank you for considering my application. I’d love to bring my backend expertise and product-minded approach to Stripe. I’ve attached my resume and GitHub — I look forward to the opportunity to discuss how I can contribute to your team.
163
+ [END STRUCTURE]
164
+ Keep the cover letter short and limit it to one page.
165
+ """
166
+
167
+ critic_prompt = """
168
+ You are a technical recruiter with years of experience helping tech companies identify strong candidates.
169
+
170
+ Your task is to review a candidate’s cover letter and provide brief, constructive feedback on its quality and fit.
171
+
172
+ Use the following criteria to guide your evaluation:
173
+ - Does the writing flow naturally, as if written by a proficient non-native English speaker?
174
+ - Is the content clearly aligned with the job description and the candidate’s resume/motivation?
175
+ - Does the candidate effectively demonstrate the required skills and experience?
176
+ - Does the cover letter appear AI-generated? (e.g., overly polished language, unnatural structure, unusual punctuation like em-dashes)
177
+ - Is the cover letter a well-written continuous text instead of bullet points?
178
+
179
+ If any of the quality criteria is not fulfilled, the cover letter needs improvement.
180
+
181
+ Provide a short but constructive written feedback hightlighting the points of improvement.
182
+
183
+ Provide quality flag
184
+ - PERFECT: if the cover letter matches most of the criteria.
185
+ - NEEDS IMPROVEMENT: if improvements can be made
186
+ """
187
+
188
+ recruiter_prompt = """
189
+ You are a professional recruiter agent responsible for critically and constructively evaluating how well a candidate fits a specific tech job role.
190
+ Your assessment is based on the job description, the candidate’s CV, and their motivation letter (if available).
191
+ Your goal is to identify the best possible match for the role and the company, ensuring a high-quality, data-driven, and fair evaluation process
192
+
193
+ Follow these steps:
194
+ 1. Analyze the CV and Motivation Letter
195
+ Assess the following:
196
+ Relevant professional experience (projects, roles, industries)
197
+ Specific technical and soft skills
198
+ Educational background and certifications
199
+ Achievements and measurable impact
200
+ Motivation, intent, and career progression
201
+ Evidence of cultural and values alignment
202
+ Communication style and clarity
203
+
204
+ 2. Identify Top 5 Required Skills from the Job Description
205
+ Extract the five most critical skills (technical or soft) based on job responsibilities and requirements.
206
+ Prioritize skills that are essential for success in the role and aligned with the company’s needs
207
+
208
+ 3. Match Skills with Candidate Evidence
209
+ For each of the five skills:
210
+ Provide a concise bullet point explaining how the candidate demonstrates (or does not demonstrate) that skill.
211
+ Use specific excerpts or paraphrased references from the CV or motivation letter.
212
+ Indicate the strength of the match: Direct, Partial, or Missing.
213
+
214
+ Format:
215
+ • [Skill 1]: Explanation with evidence
216
+ • [Skill 2]: …
217
+
218
+ 4. Provide a Score and Feedback (1–10)
219
+ Evaluate the candidate’s overall fit and give a score from 1 (poor fit) to 10 (excellent match).
220
+ Consider: experience relevance, skill alignment, motivation, cultural fit, and growth potential.
221
+
222
+ 5. Summarize Key Findings (top 2-3 skills, biggest gaps and what to focus on during onboarding)
223
+
224
+ 6. Recommendation: Move to interview / On the fence / Not a fit
225
+
226
+ Here is an example that shows you how to structure your thoughts:
227
+ [EXAMPLE]
228
+ Top 5 Required Skills from the Job Description
229
+ • Project Management
230
+ • Data Analysis
231
+ • Stakeholder Communication
232
+ • Strategic Thinking
233
+ • Industry Knowledge (SaaS / B2B Tech)
234
+
235
+ Skill Match Analysis
236
+ • Project Management: The candidate managed multiple cross-functional initiatives at Company A, including leading a product launch across three departments. Specifically noted: “Led a 6-month cross-functional project that launched a new product line, delivered on time and under budget.” Strong, direct fit.
237
+ • Data Analysis: In their role at Company B, the candidate conducted monthly performance reporting and built dashboards using Excel and Power BI. CV excerpt: “Created automated reporting tools that reduced manual data processing by 40%.” Direct match, though more tools could enhance depth.
238
+ • Stakeholder Communication: Candidate mentions managing client communication and reporting in a consulting context: “Presented findings and strategic options to C-suite clients in quarterly business reviews.” Shows experience with high-level communication, making this a strong fit.
239
+ • Strategic Thinking: Candidate completed a strategic market entry plan as part of an MBA capstone and applied similar thinking in their work: “Developed 3-year strategic roadmap for internal operations streamlining, adopted by leadership.” Strong evidence of structured, long-term planning.
240
+ • Industry Knowledge (SaaS / B2B Tech): The CV shows indirect exposure through consulting for tech firms, but no direct work in a SaaS product company. Slight mismatch: “Consulted B2B tech clients on pricing strategy”—relevant, but not hands-on experience.
241
+
242
+ Overall Score
243
+ 8/10
244
+
245
+ Summary
246
+ The candidate demonstrates a strong alignment with four of the five core skills, with especially solid experience in project management, communication, and strategic thinking.
247
+ While their data analysis experience is good, it’s more operational than advanced.
248
+ The main gap is a lack of direct SaaS or in-house product company experience, though consulting exposure softens this issue.
249
+ Motivation letter expresses clear interest in joining a fast-growing B2B tech firm, citing alignment with company values and career growth in tech.
250
+ High potential to succeed with onboarding support in SaaS-specific environments.
251
+
252
+ Recommendation
253
+ Move to interview.
254
+ [END EXAMPLE]
255
+
256
+ Guidelines:
257
+ Be objective, structured, and concise.
258
+ Use evidence-based reasoning.
259
+ Consider both technical proficiency and soft skills, as well as cultural and motivational alignment.
260
+ Focus on how the candidate’s profile aligns with the specific requirements and values of the company.
261
+ Your goal is to ensure a thorough, fair, and efficient evaluation that supports high-quality hiring
262
+ """
263
+
264
+ team_lead_prompt = """
265
+ You are the team lead responsible for hiring a new member for your tech team.
266
+ Your goal is to critically and constructively assess how well a candidate fits the specific job role, your team’s working style, and the broader company culture.
267
+ Base your evaluation on the job description, the candidate’s CV, and their motivation letter (if available).
268
+ Your aim is to identify candidates who will excel technically, collaborate effectively, and contribute positively to your team’s success.
269
+
270
+
271
+ Follow these steps:
272
+ 1. Assess Practical Fit
273
+ • Relevant hands-on experience
274
+ • Technical or domain-specific skills
275
+ • Ability to work independently and take ownership
276
+ • Experience working in team environments (cross-functional, agile, remote, etc.)
277
+ • Communication and collaboration style
278
+
279
+ 2. Identify Key Team-Relevant Skills from the Job Description
280
+ Extract the top 3–5 competencies essential for success in the team (e.g. problem-solving, initiative, tech stack experience, ownership, adaptability).
281
+
282
+ 3. Evaluate Evidence from CV and Motivation
283
+ For each selected skill
284
+ • Describe how the candidate demonstrates it
285
+ • Use specific examples or paraphrased references from the CV or motivation
286
+ • Comment on the strength of the match (strong/partial/missing)
287
+ Format:
288
+ • [Skill]: Explanation with evidence and strength of fit
289
+
290
+ 4. Provide a Team Fit Score (1–10)
291
+ Evaluate based on contribution potential, autonomy, collaboration, and mindset.
292
+ Give a short paragraph explaining the score.
293
+
294
+ 5. Summarize Fit for the Team
295
+ Briefly mention:
296
+ • Strengths for immediate value
297
+ • Potential gaps or onboarding needs
298
+ • Whether the team would benefit from this person
299
+
300
+ 6. Recommendation
301
+ Move to interview / On the fence / Not a fit
302
+
303
+ Here is an example that should demonstrate how you can structure your thoughts:
304
+ [EXAMPLE]
305
+ Key Team-Relevant Skills from the Job Description
306
+ • English Language Proficiency
307
+ • Multicultural Team Experience
308
+ • Jira (Project Management Tool)
309
+ • Scrum or Agile Methodology
310
+ • Communication Skills
311
+
312
+ Skill Match Evaluation
313
+ • English Language Proficiency: CV and motivation letter are clearly written in fluent English. Candidate studied in an international Master’s program conducted entirely in English. Strong match.
314
+ • Multicultural Team Experience: No clear evidence of working in international or cross-cultural teams. All listed experience is local. Missing.
315
+ • Jira: Used Jira during a university group project to manage tasks and deadlines. Basic exposure, but partial match.
316
+ • Scrum or Agile Methodology: No mention of Scrum, Agile, or similar methodologies in either the CV or motivation. Missing.
317
+ • Communication Skills: Motivation letter is well-structured and reflects clear, professional language. Also gave presentations during university. Strong match for entry level.
318
+
319
+ Team Fit Score: 5.5 / 10
320
+
321
+ Summary for Team Fit
322
+ This candidate communicates clearly in English and has some basic familiarity with Jira. However, there’s a notable lack of hands-on experience in Agile/Scrum environments and no exposure to multicultural collaboration, which are key to this role. With strong onboarding and mentoring, the candidate might grow into the role, but would not be immediately effective in a globally distributed team setup.
323
+
324
+ Recommendation
325
+ Not a fit for this role — too many foundational gaps for a distributed, Scrum-based team.
326
+ [END EXAMPLE]
327
+
328
+ Guidelines:
329
+ Be objective, structured, and concise.
330
+ Use evidence-based reasoning.
331
+ Focus on both technical and interpersonal/teamwork skills.
332
+ Consider how the candidate’s profile aligns with your team’s current strengths, gaps, and working style.
333
+ Think about long-term growth and how the candidate could evolve within your team.
334
+ """
335
+
336
+
337
+ interview_prompt = """
338
+ Please generate some nice interview questions based on the provided feedback.
339
+
340
+ You are an AI interview question generator designed to help hiring teams prepare for structured, insightful, and role-relevant interviews.
341
+ Your goal is to generate a diverse set of interview questions—technical, behavioral, and situational—tailored to the job description, the candidate’s CV, and (if available) their motivation letter.
342
+ The questions should help assess both the candidate’s technical fit and their alignment with the team’s working style and company culture.
343
+
344
+ Instructions:
345
+ 1. Analyze Inputs
346
+ - Please check first, if another agent has already provided feedback. In this case, skip the step 1 and proceed directly generating the interview questions.
347
+ - Carefully review the job description for key responsibilities, required skills, and team context.
348
+ - Examine the candidate’s CV and motivation letter for relevant experience, technical and soft skills, achievements, and evidence of cultural or value alignment.
349
+ - Identify Core Competencies
350
+ - Extract the top 5 critical skills or qualities needed for the role (these may be technical, interpersonal, or related to cultural fit).
351
+ 2. Generate Interview Questions
352
+ - For each core skill or quality, create at least one targeted interview question.
353
+ Ensure a mix of question types:
354
+ - Technical questions (to assess knowledge and problem-solving)
355
+ - Behavioral questions (to explore past experiences and soft skills)
356
+ - Situational questions (to evaluate judgment and approach to real-world challenges)
357
+ - Where relevant, tailor questions to the candidate’s background (e.g., referencing specific projects or experience from their CV).
358
+ Ensure Depth and Relevance
359
+ - Questions should be open-ended, encourage detailed responses, and give insight into both expertise and working style.
360
+ - Include at least one question that probes motivation or cultural/team fit.
361
+ Output Format
362
+ - List questions grouped by skill/competency, clearly labeled.
363
+ - For each question, specify the type: [Technical], [Behavioral], or [Situational].
364
+ Examples:
365
+ Skill: Project Management
366
+ [Behavioral] Tell me about a time you managed a cross-functional project. What challenges did you face and how did you overcome them?
367
+ Skill: Team Collaboration
368
+ [Situational] If you joined a team with conflicting work styles, how would you approach building effective collaboration?
369
+ Skill: Technical Expertise (Python)
370
+ [Technical] Can you describe a complex Python project you’ve led? What were the key technical decisions and their impact?
371
+
372
+ Pool of general example questions:
373
+ [Technical]
374
+ Describe how you would design a scalable system to handle millions of users. What architectural decisions would you make, and why?
375
+ Given a large dataset, how would you optimize a SQL query that is running slowly? What steps would you take to diagnose and resolve the issue?
376
+ Can you implement a function to detect cycles in a linked list? Explain your approach and its time/space complexity.
377
+ Walk me through how you would design a RESTful API for a ride-sharing application. What considerations would you prioritize for security and scalability?
378
+ Tell me about the most difficult bug you have fixed in the past six months. How did you identify and resolve it?
379
+ [Behavioral]
380
+ Tell me about a time you had to work with a difficult co-worker. How did you handle the interaction, and what was the outcome?
381
+ Describe a situation where you had to explain a complex technical problem to someone without a technical background. How did you ensure understanding?
382
+ Give an example of a time when you made a mistake because you failed to pay attention to detail. How did you recover from it?
383
+ What is the most challenging aspect of your current or most recent project? How did you tackle it?
384
+ Tell me about a time you had to influence someone to accept your point of view during a disagreement. What approach did you take, and what was the result?
385
+ [Situational]
386
+ Imagine you join a team where members have conflicting work styles. How would you approach building effective collaboration?
387
+ Suppose you are assigned a project with an extremely tight deadline and limited resources. What steps would you take to ensure successful delivery?
388
+ If you noticed a critical error in a project just before launch, what would you do? How would you communicate this to your team and stakeholders?
389
+ How would you handle a situation where a key team member suddenly leaves in the middle of a major project?
390
+ If you were given a task outside your current expertise, how would you approach learning and delivering results?
391
+
392
+ Guidelines:
393
+ Questions should be clear, concise, and tailored to the specific role and candidate profile.
394
+ Avoid generic or overly broad questions unless they directly serve the assessment goal or unless you don't have enough data available.
395
+ Focus on questions that will help the hiring team evaluate both immediate fit and long-term potential.
396
+ Your output should enable the hiring team to conduct a thorough, fair, and insightful interview that assesses the candidate’s overall suitability for the team and company.
397
+ """
398
+
399
+ # %% [markdown]
400
+ # ### Available agents
401
+
402
+ # %%
403
+ recruiter_agent_description = AgentDescription(
404
+ title="recruiter_agent",
405
+ description="The recruiter agent provides general feedback about the applicant.",
406
+ system_prompt=general_prefix+recruiter_prompt
407
+ )
408
+ writer_agent_description = AgentDescription(
409
+ title="writer_agent",
410
+ description="The writer agent can write a cover letter for the applicant.",
411
+ system_prompt=general_prefix+writer_prompt
412
+ )
413
+ critic_agent_description = AgentDescription(
414
+ title="critic_agent",
415
+ description="The critic agent provides feedback for a written cover letter.",
416
+ system_prompt=general_prefix+critic_prompt
417
+ )
418
+ interview_agent_description = AgentDescription(
419
+ title="interview_agent",
420
+ description="The interview question agent can generate interview questions for the candidate based on specialized feedback. It only makes sense to call the interview agent after feedback was provided to improve the results.",
421
+ system_prompt=general_prefix+interview_prompt
422
+ )
423
+ team_lead_agent_description = AgentDescription(
424
+ title="team_lead_agent",
425
+ description="The team lead agent provides feedback about the applicant from the perspective of the team lead of the team of the open position.",
426
+ system_prompt=general_prefix+team_lead_prompt
427
+ )
428
+ available_agents = {
429
+ "recruiter_agent" : recruiter_agent_description,
430
+ "writer_agent" : writer_agent_description,
431
+ "critic_agent" : critic_agent_description,
432
+ "interview_agent" : interview_agent_description,
433
+ "team_lead_agent": team_lead_agent_description
434
+ }
435
+
436
+ # %% [markdown]
437
+ # ## Utilities
438
+
439
+ # %% [markdown]
440
+ # ### General utilities
441
+
442
+ # %%
443
+ def docling_extraction(source : str = "CV.pdf") -> str:
444
+ "Extract CV and convert it to Markdown using docling"
445
+ converter = DocumentConverter()
446
+ result = converter.convert(source)
447
+ return result.document.export_to_markdown()
448
+
449
+ def read_file_content(file: Union[str, Path]) -> str:
450
+ file_path = Path(file)
451
+ suffix = file_path.suffix.lower()
452
+
453
+ if suffix == ".txt" or suffix == ".md":
454
+ return file_path.read_text(encoding="utf-8")
455
+
456
+ elif suffix == ".pdf":
457
+ # Extract CV and convert it to Markdown using docling
458
+ converter = DocumentConverter()
459
+ result = converter.convert(file_path)
460
+ return result.document.export_to_markdown()
461
+
462
+ elif suffix == ".docx":
463
+ return "\n".join(p.text for p in docx.Document(file_path).paragraphs)
464
+
465
+ else:
466
+ return ""
467
+
468
+ def call_llm(system_prompt, user_prompt, response_format : Any = None) -> Any:
469
+ """
470
+ Call LLM with provided system prompt and user prompt and the response format that should be enforced
471
+ """
472
+
473
+ if USE_GOOGLE:
474
+ llm = ChatGoogleGenerativeAI(
475
+ model = MODEL_NAME,
476
+ google_api_key = API_KEY,
477
+ temperature = 0,
478
+ max_tokens = None,
479
+ timeout = None,
480
+ max_retries = 2
481
+ )
482
+ else:
483
+ llm = ChatOpenAI(
484
+ model=MODEL_NAME,
485
+ api_key=API_KEY,
486
+ base_url=ENDPOINT_URL,
487
+ max_completion_tokens=None,
488
+ timeout=60,
489
+ max_retries=0,
490
+ temperature=0
491
+ )
492
+
493
+ if response_format is not None:
494
+ llm = llm.with_structured_output(response_format)
495
+
496
+ prompt = ChatPromptTemplate.from_messages([
497
+ ("system", "{system_prompt}"),
498
+ ("user", "{user_prompt}")
499
+ ])
500
+
501
+ chain = prompt | llm
502
+
503
+ response = chain.invoke({
504
+ "system_prompt":system_prompt,
505
+ "user_prompt": user_prompt
506
+ })
507
+
508
+ return response
509
+
510
+ def serialize_messages(messages : List[Tuple[str,str]]) -> str:
511
+ "Returns a formatted message history of previous messages"
512
+ return "\n" +"\n".join(f"{role}:\n{content}" for role, content in messages)
513
+
514
+ def strip_think_blocks(text: str) -> str:
515
+ return re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
516
+
517
+ # %% [markdown]
518
+ # ### Gradio utilities
519
+
520
+ # %%
521
+ # Handle different result types cleanly
522
+ def type_conversion(obj : Any, type):
523
+ "Return the object in a gradio compatible type"
524
+ if isinstance(obj, type):
525
+ result_dict = obj.model_dump()
526
+ elif isinstance(obj, Dict):
527
+ result_dict = obj
528
+ else:
529
+ # Handle possible dataclass or similar object
530
+ try:
531
+ result_dict = ApplicationAgentState.model_validate(obj).model_dump()
532
+ except Exception as e:
533
+ print(f"Error converting output of type {type(obj)}")
534
+
535
+ return result_dict
536
+
537
+ # %% [markdown]
538
+ # ## Agents
539
+
540
+ # %% [markdown]
541
+ # ### Orchestrator agent
542
+ #
543
+ #
544
+
545
+ # %%
546
+ def orchestrator_agent(state: ApplicationAgentState) -> Command[Literal["recruiter_agent", "team_lead_agent", "writer_agent", "critic_agent", "interview_agent","final_answer_tool", END]]:
547
+ """
548
+ Central orchestration logic to determine which agent to call next based on the current state and results.
549
+ """
550
+
551
+ # check if the CV and a job description are provided by the user
552
+ if not state.cv or not state.job_description:
553
+ state.final_answer = "### ❗️ The application assisant needs more information about you and the desired position to provide high-quality results.\n" \
554
+ "👈🏽 Please go to the tab 🤗 **Personal Information** and provide your CV and the job description."
555
+ return Command(
556
+ goto=END,
557
+ update=state
558
+ )
559
+
560
+ if state.phase == "PLAN":
561
+ agent_descriptions = "\n".join([
562
+ f"{agent.get("title")}\nDescription: {agent.get("description")}"
563
+ for name, agent in state.available_agents.items()
564
+ ])
565
+
566
+ system_prompt = f"""You are an orchestrator agent, that delegates tasks to specialized agents based on a user query.
567
+
568
+ Your rules:
569
+ Given a user query, your task is to determine the next agents to call.
570
+ You are only allowed to fulfil tasks that are related to application assistance (e. g. supporting to write cover letters, revise a CV, or generate a bunch of interview questions) AND match one of the following agent's capabilities.
571
+ You are allowed to do multi-step plans, if required.
572
+ This is an overview of all available agents with their descriptions.
573
+
574
+ Agents:
575
+ {agent_descriptions}
576
+
577
+ If a user query is not in the scope of the available agent capabilities, you can always call __end__ to end the workflow.
578
+
579
+ Please provide short reasoning on why you choose an agent before doing your final choice.
580
+
581
+ [EXAMPLES]
582
+ USER QUERY: Please help me to generate a cover letter for this interesting position
583
+ REASONING: The user is asking for support with a cover letter, so I might call the writer_agent specialized in writing cover letters.
584
+ NEXT_AGENT: writer_agent
585
+
586
+ USER_QUERY: I want to design an nice recepe to cook a meal for dinner
587
+ REASONING: The user request doesn't match any of the agent capabilities and seems off-topic, so I should call __end__.
588
+ NEXT_AGENT: __end__
589
+
590
+ USER_QUERY: I want you to design me some realistic interview questions for my next interview with an HR recruiter and a team lead of Meta.
591
+ REASONING: The user request asks for help with interview questions from the HR recruiter perspective. I might call the recruiter agent for that request first and then call the interview agent to generate some interview questions based on the feedback.
592
+ NEXT_AGENT: recruiter_agent, team_lead_agent, interview_agent
593
+
594
+ [END EXAMPLES]
595
+ """
596
+
597
+ user_prompt = state.user_query
598
+ state.messages.append(("user query", state.user_query))
599
+
600
+ # call the orchestrator to select the next agent
601
+ response = call_llm(system_prompt, user_prompt, MultiStepPlan)
602
+ print("="*40)
603
+ print("🤖 ORCHESTRATOR PLAN")
604
+ print("="*40)
605
+ print(f"\n📝 Reasoning:\n{response.reasoning}\n")
606
+ print("🔗 Planned Steps:")
607
+ for i, step in enumerate(response.plan, 1):
608
+ print(f" {i}. {step}")
609
+ print("="*40)
610
+ print("⚙️ EXECUTE PLAN")
611
+ print("="*40 + "\n")
612
+ state.plan = response.plan
613
+ state.phase = "EXECUTE"
614
+
615
+ if len(state.plan) == 0 and state.phase == "EXECUTE":
616
+ state.phase = "ANSWER"
617
+ return Command(
618
+ goto="final_answer_tool",
619
+ update=state
620
+ )
621
+ if state.phase == "EXECUTE":
622
+ # return next agent to call
623
+ agent = state.plan.pop(0)
624
+ try:
625
+ return Command(
626
+ goto=agent,
627
+ update=state)
628
+ except Exception as e:
629
+ print(f"Error executing the plan in step {i} - {step}: {e}")
630
+
631
+ if state.phase == "ANSWER":
632
+ state.phase = "PLAN"
633
+ state.messages = [("orchestrator_agent", f"Final answer:\n{state.final_answer}")]
634
+
635
+ # finally end the workflow execution
636
+ return Command(goto=END, update=state)
637
+
638
+ # %% [markdown]
639
+ # ### Recruiter agent
640
+
641
+ # %%
642
+ def recruiter_agent(state : ApplicationAgentState) -> Command:
643
+ "The recruiter agent has the task to provide feedback about the applicant from the perspective of a senior recruiter"
644
+
645
+ agent_description = state.available_agents.get("recruiter_agent", {})
646
+ system_prompt = agent_description.get("system_prompt", "You're a Senior recruiter agent that provides feedback about an applicant.")
647
+
648
+ user_prompt = f"""
649
+ [JOB DESCRIPTION]
650
+ {state.job_description}
651
+ [END JOB DESCRIPTION]
652
+
653
+ [CV]
654
+ {state.cv}
655
+ [END CV]
656
+ """
657
+
658
+ if len(state.messages):
659
+ user_prompt += "Other agents have already contributed to the task. Please use their contributions to improve your feedback for the applicant."
660
+ user_prompt += serialize_messages(state.messages)
661
+
662
+ user_prompt += "Provide feedback on the applicant from your perspective."
663
+
664
+ print("The recruiter agent assesses your information to provide feedback 🧑🏼‍💻")
665
+ response = call_llm(system_prompt, user_prompt).content
666
+ print("The recruiter agent has provided feedback.")
667
+
668
+ agent_contribution = ("recruiter_agent", response)
669
+ state.messages.append(agent_contribution)
670
+
671
+ return Command(
672
+ goto="orchestrator_agent",
673
+ update=state
674
+ )
675
+
676
+ # %% [markdown]
677
+ # ### Team lead agent
678
+
679
+ # %%
680
+ def team_lead_agent(state : ApplicationAgentState) -> Command:
681
+ "The team lead agent has the task to provide feedback about the applicant from the perspective of the team lead of the hiring team"
682
+
683
+ agent_description = state.available_agents.get("team_lead_agent", {})
684
+ system_prompt = agent_description.get("system_prompt", "You're a team lead and you want to hire a new person. Provide feedback on the applicant.")
685
+
686
+ user_prompt = f"""
687
+ [JOB DESCRIPTION]
688
+ {state.job_description}
689
+ [END JOB DESCRIPTION]
690
+
691
+ [CV]
692
+ {state.cv}
693
+ [END CV]
694
+ """
695
+
696
+ if len(state.messages):
697
+ user_prompt += "Other agents have already contributed to the task. Please use their contributions to improve your feedback for the applicant."
698
+ user_prompt += serialize_messages(state.messages)
699
+
700
+ user_prompt += "Provide feedback on the applicant from your perspective."
701
+
702
+ print("The team lead agent assesses your information to provide feedback 🧑🏼‍💻")
703
+ response = call_llm(system_prompt, user_prompt).content
704
+ print("The team lead agent has provided feedback.")
705
+
706
+ agent_contribution = ("team_lead_agent", response)
707
+ state.messages.append(agent_contribution)
708
+
709
+ return Command(
710
+ goto="orchestrator_agent",
711
+ update=state
712
+ )
713
+
714
+ # %% [markdown]
715
+ # ### Writer agent
716
+
717
+ # %%
718
+ def writer_agent(state: ApplicationAgentState) -> Command[Literal["critic_agent"]]:
719
+ """Generate a cover letter using job description, CV, motivation, and skill match."""
720
+
721
+ agent_description = state.available_agents.get("writer_agent", {})
722
+ system_prompt = agent_description.get("system_prompt", "You're a critic agent that provides helpful feedback for cover letters.")
723
+
724
+ user_prompt = f"""
725
+ [JOB DESCRIPTION]
726
+ {state.job_description}
727
+ [END JOB DESCRIPTION]
728
+
729
+ [CV]
730
+ {state.cv}
731
+ [END CV]
732
+ """
733
+
734
+ if state.motivation != "":
735
+ user_prompt += f"\nThis is my general motivation and my desired job profiles:\n[MOTIVATION]{state.motivation}\n[END MOTIVATION]\n"
736
+
737
+ if state.examples != "":
738
+ user_prompt += f"Use the following examples of previous cover letters to adapt to my personal writing style:\n[EXAMPLES]\n{state.examples}\n[END EXAMPLES]\n"
739
+
740
+ if len(state.messages):
741
+ user_prompt += "Other agents have already contributed to the task. Please use their contributions to improve your writing."
742
+ user_prompt += serialize_messages(state.messages)
743
+
744
+ user_prompt += "Write a professional cover letter in under 300 words in the language of the job description."
745
+ print("The writer agent writes the cover letter ✏️")
746
+ agent_contribution = ("writer_agent", call_llm(system_prompt, user_prompt).content)
747
+ print("The writer agent has completed a draft for your cover letter 📝")
748
+ state.messages.append(agent_contribution)
749
+
750
+ return Command(
751
+ goto="critic_agent",
752
+ update=state
753
+ )
754
+
755
+
756
+
757
+ # %% [markdown]
758
+ # ### Interview agent
759
+
760
+ # %%
761
+ def interview_agent(state : ApplicationAgentState) -> Command[Literal["orchestrator_agent"]]:
762
+ "Agent to generate interview questions based on the provided feedback of a recruiter of team lead agent."
763
+
764
+ agent_description = state.available_agents.get("interview_agent", {})
765
+ system_prompt = agent_description.get("system_prompt", "You're an interview agent, that generates questions for an applicant in a job interview.")
766
+
767
+ user_prompt = f"""
768
+ [JOB DE SCRIPTION]
769
+ {state.job_description}
770
+ [END JOB DESCRIPTION]
771
+
772
+ [CV]
773
+ {state.cv}
774
+ [END CV]
775
+ """
776
+
777
+ if len(state.messages):
778
+ user_prompt += "Other agents have already contributed to the task. Please use their contributions to improve the quality of your interview questions."
779
+ user_prompt += serialize_messages(state.messages)
780
+
781
+ print("The interview agent is generating a set of high-quality interview questions ❓")
782
+ response = call_llm(system_prompt, user_prompt).content
783
+ print("The interview agent has generated a set of interview questions. ")
784
+
785
+ agent_contribution = ("interview_agent", response)
786
+ state.messages.append(agent_contribution)
787
+
788
+ return Command(
789
+ goto="orchestrator_agent",
790
+ update=state
791
+ )
792
+
793
+
794
+ # %% [markdown]
795
+ # ### Critic agent
796
+
797
+ # %%
798
+ def critic_agent(state: ApplicationAgentState) -> Command[Literal["writer_agent","orchestrator_agent"]]:
799
+ "Provide feedback for a previously written cover letter."
800
+
801
+ agent_contribution = ("critic_agent", "The critic agent was here..")
802
+
803
+ user_prompt = f"""
804
+ [JOB DESCRIPTION]
805
+ {state.job_description}
806
+ [END JOB DESCRIPTION]
807
+
808
+ [CV]
809
+ {state.cv}
810
+ [END CV]
811
+ You are only allowed to make suggestions like quantifying experience if the required information was provided in the CV and is based on the actual experience.
812
+ """
813
+ if state.motivation != "":
814
+ user_prompt += f"\nThis is my general motivation and my desired job profiles:\n[MOTIVATION]{state.motivation}\n[END MOTIVATION]\n"
815
+
816
+ if state.examples != "":
817
+ user_prompt += f"Use the following examples of previous cover letters to adapt to my personal writing style:\n[EXAMPLES]\n{state.examples}\n[END EXAMPLES]\n"
818
+
819
+ if len(state.messages):
820
+ user_prompt += "Other agents have already contributed to the task. Please use their contributions to provide feedback to the most recent cover letter."
821
+ user_prompt += serialize_messages(state.messages)
822
+
823
+ agent_description = state.available_agents.get("critic_agent", {})
824
+ system_prompt = agent_description.get("system_prompt", "You're a critic agent that provides helpful feedback for cover letters.")
825
+
826
+ print("The critic agent revises the cover letter 🔎📝")
827
+ response = call_llm(system_prompt, user_prompt, Feedback)
828
+ print("The critic agent revised the cover letter ✅")
829
+
830
+ state.iterations += 1
831
+
832
+ if response.quality_flag == "PERFECT" or state.iterations == state.max_iterations:
833
+ next_step = "orchestrator_agent"
834
+ else:
835
+ next_step = "writer_agent"
836
+
837
+ print(f"[{state.iterations}. Iteration]\nFEEDBACK: {response.quality_flag}")
838
+
839
+ agent_contribution = ("critic_agent", f"{response.feedback}")
840
+
841
+ state.messages.append(agent_contribution)
842
+
843
+ return Command(
844
+ goto=next_step,
845
+ update=state
846
+ )
847
+
848
+ # %%
849
+ def final_answer_tool(state : ApplicationAgentState) -> Command[Literal["orchestrator_agent"]]:
850
+ "Final answer tool is invoked to formulate a final answer based on the agent message history"
851
+
852
+ system_prompt = f"""
853
+ You're a helpful application assistant and your role is to provide a concise final answer will all the relevant details to answer the user query.
854
+
855
+ Your final answer WILL HAVE to contain these parts:
856
+ ### Task outcome
857
+ ## Final answer
858
+ """
859
+
860
+ formatted_history = serialize_messages(state.messages)
861
+
862
+ user_prompt = f"""
863
+ ---
864
+ Task:
865
+ {state.user_query}
866
+ ---
867
+ Agent call history:
868
+ {formatted_history}
869
+ """
870
+
871
+ final_answer = call_llm(system_prompt, user_prompt).content
872
+
873
+ if isinstance(final_answer, str):
874
+ final_answer = strip_think_blocks(final_answer)
875
+ state.final_answer = final_answer
876
+
877
+ return Command(
878
+ goto="orchestrator_agent",
879
+ update=state
880
+ )
881
+
882
+
883
+ # %% [markdown]
884
+ # ## Build Graph
885
+
886
+ # %%
887
+ builder = StateGraph(ApplicationAgentState)
888
+
889
+ builder.add_node(orchestrator_agent)
890
+ builder.add_node(recruiter_agent)
891
+ builder.add_node(team_lead_agent)
892
+ builder.add_node(writer_agent)
893
+ builder.add_node(critic_agent)
894
+ builder.add_node(interview_agent)
895
+ builder.add_node(final_answer_tool)
896
+
897
+ builder.add_edge(START, "orchestrator_agent")
898
+
899
+ graph = builder.compile()
900
+
901
+ # %% [markdown]
902
+ # ## Gradio functions
903
+
904
+ # %% [markdown]
905
+ # ### Gradio function - Extract information
906
+
907
+ # %%
908
+ def extract_information(
909
+ state_dict,
910
+ cv_file,
911
+ job_description_file,
912
+ motivation_file,
913
+ examples_file,
914
+ max_iterations: int
915
+ ) -> tuple[str, Dict, bool]:
916
+ """
917
+ Run the extraction pipeline and return output logs + state as a dict.
918
+ """
919
+ output_text = ""
920
+
921
+ try:
922
+ cv_content = read_file_content(cv_file)
923
+ job_description_content = read_file_content(job_description_file)
924
+ motivation_content = read_file_content(motivation_file) if motivation_file else ""
925
+ examples_content = read_file_content(examples_file) if examples_file else ""
926
+ output_text += "Successfully extracted input."
927
+ except Exception as e:
928
+ output_text += f"Reading input files failed: {str(e)}"
929
+ return output_text, None, False
930
+
931
+ state = ApplicationAgentState.model_validate(state_dict)
932
+ state.cv = cv_content
933
+ state.job_description = job_description_content
934
+ state.motivation = motivation_content
935
+ state.examples = examples_content
936
+
937
+ state_dict = type_conversion(state, ApplicationAgentState)
938
+
939
+ return output_text, state_dict, True
940
+
941
+ # %%
942
+ def call_orchestrator(state_dict : Dict, user_query : str):
943
+ "Function prototype to call the orchestrator agent"
944
+ state = ApplicationAgentState.model_validate(state_dict)
945
+
946
+ state.user_query = user_query
947
+ # Capture print outputs
948
+ buffer = StringIO()
949
+ with contextlib.redirect_stdout(buffer):
950
+ # if TRACING:
951
+ # result = graph.invoke(input=state, config={"callbacks": [langfuse_handler]})
952
+ # else:
953
+ # result = graph.invoke(input=state)
954
+ result = graph.invoke(input=state)
955
+
956
+ result_dict = type_conversion(result, ApplicationAgentState)
957
+
958
+ output_text = buffer.getvalue()
959
+
960
+ return output_text, result_dict, True
961
+
962
+ # %% [markdown]
963
+ # ## Gradio Interface
964
+
965
+ # %%
966
+ with gr.Blocks() as application_agent_server:
967
+ gr.Markdown("# 🏆 Application Assistant")
968
+
969
+ with gr.Row():
970
+ with gr.Column(scale=1): # Image on the far left
971
+ gr.Image(value="application_assistant.png", container=False, show_download_button=False, show_fullscreen_button=False)
972
+
973
+ with gr.Column(scale=4): # Markdown starts next to the image
974
+ gr.Markdown("## Your happy AI assistant 🦛🤗 helps you to land your next dream job.")
975
+ gr.Markdown("Just provide some information about yourself and the oppurtunity and ask the assistant for help.")
976
+
977
+ state_dict = gr.State(value=ApplicationAgentState(available_agents=available_agents).model_dump())
978
+ extraction_successful = gr.State(value=False)
979
+
980
+ with gr.Tabs():
981
+ with gr.TabItem("🤗 Personal information"):
982
+ gr.Markdown("### 🍉 Feed the application agent with some information about yourself and the opportunity.")
983
+
984
+ with gr.Row():
985
+ cv_file = gr.File(value = "CV.md", label="Upload CV", file_types=[".pdf", ".txt", ".docx", ".md"], height=150)
986
+ job_description_file = gr.File(value= "job-description.txt", label="Upload Job Description", file_types=[".pdf", ".txt", ".docx", ".md"], height=150)
987
+
988
+ gr.Markdown("### Optional information to improve the quality of the results.")
989
+ with gr.Row():
990
+ motivation_file = gr.File(value= "motivation.txt", label="Upload General Motivation", file_types=[".pdf", ".txt", ".docx",".md"], height=150)
991
+ examples_file = gr.File(value= "examples.txt", label="Upload previous Cover Letters", file_types=[".pdf", ".txt", ".docx",".md"], height=150)
992
+
993
+ with gr.Accordion("Advanced options", open=False):
994
+ max_iterations = gr.Number(label="Number of refinement iterations", value=2, precision=0)
995
+
996
+
997
+ extract_button = gr.Button("Extract your information", variant="primary")
998
+
999
+ extract_console_output = gr.Textbox(label="Logs / Console Output")
1000
+
1001
+ @gr.render(inputs=[extraction_successful, state_dict])
1002
+ def show_contents(extraction_flag, state_dict):
1003
+ if extraction_flag:
1004
+ cv = state_dict.get("cv","")
1005
+ job_description = state_dict.get("job_description","")
1006
+ motivation = state_dict.get("motivation","")
1007
+ examples = state_dict.get("examples", "")
1008
+
1009
+ with gr.Accordion("CV", open=False):
1010
+ gr.Markdown(cv)
1011
+ with gr.Accordion("Job Description", open=False):
1012
+ gr.Markdown(job_description)
1013
+ if motivation:
1014
+ with gr.Accordion("Motivation", open=False):
1015
+ gr.Markdown(motivation)
1016
+ if examples:
1017
+ with gr.Accordion("Previous cover letters", open=False):
1018
+ gr.Markdown(examples)
1019
+
1020
+
1021
+ extract_button.click(
1022
+ fn=extract_information,
1023
+ inputs=[state_dict, cv_file, job_description_file, motivation_file, examples_file, max_iterations],
1024
+ outputs=[extract_console_output, state_dict, extraction_successful]
1025
+ )
1026
+
1027
+ with gr.TabItem("🤖 Q&A Chatbot"):
1028
+ examples = """ℹ️ **Examplary queries**
1029
+ - Write a cover letter for me.
1030
+ - I have an interview with the team lead of the hiring team. Please provide some tailored interview questions.
1031
+ - Please provide some feedback from the perspective of a recruiter on my CV and previous experience.
1032
+ """
1033
+ gr.Markdown(examples)
1034
+ user_query = gr.Textbox(label="Ask your question here", value="Generate a cover letter", interactive=True)
1035
+ button = gr.Button("Ask the application assistant 🦛🤗", variant="primary")
1036
+ qa_orchestrator_completed = gr.State(value=False)
1037
+
1038
+
1039
+ @gr.render(inputs=[qa_orchestrator_completed,state_dict])
1040
+ def show_qa_results(qa_flag, state_dict):
1041
+ if qa_flag:
1042
+ gr.Markdown(state_dict.get("final_answer", "❗️ No final answer provided: please check the logs."))
1043
+ # reset the flag after orchestor was called
1044
+ # qa_orchestrator_completed.value = False
1045
+ else:
1046
+ gr.Markdown("### Call the application assistant to get some results.")
1047
+ output_logs = gr.Textbox(label="Logs/ Console Output", lines=10)
1048
+
1049
+ # def chat_fn(message : str, history : List, state_dict : Dict):
1050
+ # # Call your orchestrator logic
1051
+ # output_logs, new_state_dict, success = call_orchestrator(state_dict, message)
1052
+ # # Extract the assistant's reply from state_dict or output_logs
1053
+ # reply = new_state_dict.get("final_answer", "Sorry, I couldn't process your request.")
1054
+ # history = history + [(message, reply)]
1055
+ # return reply, history, new_state_dict
1056
+
1057
+ # chat = gr.ChatInterface(
1058
+ # fn=chat_fn,
1059
+ # additional_inputs=[state_dict], # Pass state_dict as input
1060
+ # additional_outputs=[state_dict], # Return updated state_dict
1061
+ # # examples=[
1062
+ # # "Write a cover letter for me.",
1063
+ # # "I have an interview with the team lead of the hiring team. Please provide some tailored interview questions.",
1064
+ # # "Please provide some feedback from the perspective of a recruiter on my CV and previous experience."
1065
+ # # ],
1066
+ # title="Ask the application assistant"
1067
+ # )
1068
+ def reset_elements(qa_flag : bool, output_logs : str) -> bool:
1069
+ return False, "Generating response"
1070
+
1071
+ button.click(
1072
+ fn=reset_elements,
1073
+ inputs=[qa_orchestrator_completed, output_logs],
1074
+ outputs=[qa_orchestrator_completed, output_logs]
1075
+ ).then(
1076
+ fn=call_orchestrator,
1077
+ inputs=[state_dict, user_query],
1078
+ outputs=[output_logs, state_dict, qa_orchestrator_completed]
1079
+ )
1080
+
1081
+ with gr.TabItem("🔎 What's under the hood?"):
1082
+ gr.Markdown("## Details")
1083
+ with gr.Row():
1084
+ with gr.Column(scale=1): # Image on the far left
1085
+ gr.Image(value="mas_architecture.png", container=False, label="Architecture")
1086
+
1087
+ with gr.Column(scale=2): # Markdown starts next to the image
1088
+ gr.Markdown("There is a LangGraph-powered multi-agent system under the hood using an orchestrator approach to plan and route the requests.")
1089
+ gr.Markdown("Each agent is specialized performing application-related tasks.")
1090
+
1091
+ if __name__ == "__main__":
1092
+ application_agent_server.launch(mcp_server=True)
1093
+
1094
+
1095
+
1096
+
application_assistant.png ADDED

Git LFS Details

  • SHA256: 6f0196b3cceded9950eea6a9dfb132630afd50af088a68ac2e4db77d0e2429c5
  • Pointer size: 131 Bytes
  • Size of remote file: 166 kB
examples.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ## Example 1
2
+
gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .env
2
+ .application_assistant_tutorial.mov
interface.png ADDED

Git LFS Details

  • SHA256: 04c9af981e4d6fac05de0f45acadd5031e3384208e10a1016c9b28c1d903a268
  • Pointer size: 131 Bytes
  • Size of remote file: 292 kB
job-description.txt ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Job Title: Senior Data Scientist
2
+ Location: Hybrid – Berlin, Germany or Remote (within CET±2)
3
+ Job Type: Full-time
4
+ Experience Level: Senior
5
+
6
+ About Us:
7
+ At Nimbus 2000 Technologies, we build cutting-edge digital solutions that serve billions of users worldwide. Our platforms span cloud services, e-commerce, machine learning infrastructure, and consumer applications used by millions daily. We believe in data as the foundation of smart decision-making and innovation. Our global team thrives on curiosity, collaboration, and continuous learning.
8
+
9
+ Position Overview:
10
+ We are looking for a Senior Data Scientist to join our centralized Data Science & Analytics team. In this role, you will lead complex analytical projects from ideation to production. You’ll partner closely with product managers, engineers, and business stakeholders to drive insights, influence strategy, and build intelligent data products that scale.
11
+
12
+ Responsibilities:
13
+ • Lead end-to-end data science projects including problem scoping, data exploration, modeling, and implementation
14
+ • Develop predictive models, classification systems, and optimization algorithms using machine learning and statistical methods
15
+ • Collaborate with data engineering to structure robust data pipelines and clean, scalable datasets
16
+ • Communicate findings through compelling storytelling, dashboards, and presentations to non-technical stakeholders
17
+ • Mentor junior data scientists and contribute to internal best practices and research
18
+
19
+ Required Skills and Qualifications:
20
+ • 5+ years of hands-on experience in data science, machine learning, or related fields
21
+ • Proficiency in Python and data science libraries (e.g., pandas, NumPy, scikit-learn, TensorFlow or PyTorch)
22
+ • Strong SQL skills and experience with large-scale datasets (e.g., BigQuery, Redshift)
23
+ • Experience building and deploying ML models in production environments
24
+ • Solid understanding of statistics, experimental design (A/B testing), and data storytelling
25
+ • Experience with cloud platforms such as AWS, GCP, or Azure
26
+ • Excellent communication and cross-functional collaboration skills
27
+ • Master’s or PhD in Computer Science, Statistics, Applied Mathematics, or similar field
28
+
29
+ Nice-to-Have:
30
+ • Experience with recommendation systems, NLP, or deep learning architectures
31
+ • Familiarity with data versioning, MLOps tools (e.g., MLflow, Airflow, Kubeflow)
32
+ • Contributions to open-source or technical blogs
33
+
34
+ What We Offer:
35
+ • Competitive compensation package with annual bonus and stock options
36
+ • Flexible working hours and hybrid/remote work options
37
+ • 30 vacation days per year
38
+ • Relocation support if applicable
39
+ • Annual personal development budget (€2,000/year)
40
+ • Health & wellness stipend, gym memberships, mental health resources
41
+ • Free lunch at Berlin HQ, team events, hackathons, and a strong learning culture
42
+
43
+ How to Apply:
44
+ Please submit your resume, a short cover letter, and links to any relevant projects (e.g., GitHub, Kaggle, blog posts).
45
+
46
+
47
+
48
+ Let me know if you want a version tailored for a real company like Google, Amazon, or Microsoft.
mas_architecture.png ADDED

Git LFS Details

  • SHA256: 68e56a4cf6622e654cb3c347acc99cca8a37a93ba6c674b5a99fec5d7ad905ef
  • Pointer size: 131 Bytes
  • Size of remote file: 473 kB
motivation.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Motivation Statement – John Doe
2
+
3
+ After more than a decade of experience in software development, spanning both backend architecture and frontend engineering, I’ve come to realize that what excites me most is not just building systems, but understanding the data that flows through them and using it to drive meaningful decisions. Over the past few years, I’ve increasingly gravitated toward data-focused projects—whether it was building analytics dashboards, collaborating with data teams, or exploring trends through internal tooling. This natural progression has sparked a deeper interest in the field of data science, which I now wish to pursue full-time.
4
+
5
+ What I’m looking for in my next role is the opportunity to solve real-world problems using data—whether through statistical analysis, predictive modeling, or machine learning. I want to work on tasks where I can contribute to the design and deployment of intelligent, data-driven solutions, and continuously deepen my expertise in algorithms, experimentation, and advanced analytics.
6
+
7
+ Equally important to me is the environment I work in. I’m seeking a company with a strong culture of collaboration, continuous learning, and openness to new ideas. I thrive in teams where knowledge is shared freely, where impact matters more than hierarchy, and where personal development is encouraged alongside delivering value.
8
+
9
+ I believe I’m ready for this transition not only because of the technical foundations I’ve built—strong programming skills, system design experience, and a problem-solving mindset—but also because I’ve taken deliberate steps to reskill in data science. Through certifications, self-led projects, and collaboration with analytics teams, I’ve developed a solid grounding in the tools and methodologies of the field. Now, I’m ready to apply that knowledge in a role where data is at the core, not just an afterthought.
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ langgraph
2
+ gradio[mcp]
3
+ docling
4
+ langchain-openai
5
+ langchain-google-genai