Skip to content

Commit 2b2453e

Browse files
authored
Merge pull request #1240 from AnishSarkar22/feat/resume-builder
feat: resume builder
2 parents 2270b4c + 0fa32c3 commit 2b2453e

24 files changed

Lines changed: 1916 additions & 59 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""126_add_report_content_type
2+
3+
Revision ID: 126
4+
Revises: 125
5+
Create Date: 2026-04-15
6+
7+
Adds content_type column to reports table to distinguish between
8+
Markdown reports and Typst-based resumes.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
from collections.abc import Sequence
14+
15+
import sqlalchemy as sa
16+
17+
from alembic import op
18+
19+
revision: str = "126"
20+
down_revision: str | None = "125"
21+
branch_labels: str | Sequence[str] | None = None
22+
depends_on: str | Sequence[str] | None = None
23+
24+
25+
def upgrade() -> None:
26+
conn = op.get_bind()
27+
columns = [c["name"] for c in sa.inspect(conn).get_columns("reports")]
28+
if "content_type" in columns:
29+
return
30+
op.add_column(
31+
"reports",
32+
sa.Column(
33+
"content_type",
34+
sa.String(20),
35+
nullable=False,
36+
server_default="markdown",
37+
),
38+
)
39+
40+
41+
def downgrade() -> None:
42+
op.drop_column("reports", "content_type")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""127_seed_build_resume_prompt
2+
3+
Revision ID: 127
4+
Revises: 126
5+
Create Date: 2026-04-15
6+
7+
Seeds the 'Build Resume' default prompt for all existing users.
8+
New users get it automatically via SYSTEM_PROMPT_DEFAULTS on signup.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
from collections.abc import Sequence
14+
15+
import sqlalchemy as sa
16+
17+
from alembic import op
18+
19+
revision: str = "127"
20+
down_revision: str | None = "126"
21+
branch_labels: str | Sequence[str] | None = None
22+
depends_on: str | Sequence[str] | None = None
23+
24+
25+
def upgrade() -> None:
26+
conn = op.get_bind()
27+
conn.execute(
28+
sa.text(
29+
"""
30+
INSERT INTO prompts
31+
(user_id, default_prompt_slug, name, prompt, mode, version, is_public, created_at)
32+
SELECT u.id, 'build-resume', 'Build Resume',
33+
E'Build me a professional resume. Here is my information:\\n\\n{selection}',
34+
'explore'::prompt_mode, 1, false, now()
35+
FROM "user" u
36+
ON CONFLICT (user_id, default_prompt_slug) DO NOTHING
37+
"""
38+
)
39+
)
40+
41+
42+
def downgrade() -> None:
43+
op.execute("DELETE FROM prompts WHERE default_prompt_slug = 'build-resume'")

surfsense_backend/app/agents/new_chat/system_prompt.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,13 +443,60 @@ def _get_system_instructions(
443443
- Call: `web_search(query="weather New York today")`
444444
"""
445445

446+
_TOOL_INSTRUCTIONS["generate_resume"] = """
447+
- generate_resume: Generate or revise a professional resume as a Typst document.
448+
- WHEN TO CALL: The user asks to create, build, generate, write, or draft a resume or CV.
449+
Also when they ask to modify, update, or revise an existing resume from this conversation.
450+
- WHEN NOT TO CALL: General career advice, resume tips, cover letters, or reviewing
451+
a resume without making changes. For cover letters, use generate_report instead.
452+
- The tool produces Typst source code that is compiled to a PDF preview automatically.
453+
- Args:
454+
- user_info: The user's resume content — work experience, education, skills, contact
455+
info, etc. Can be structured or unstructured text.
456+
CRITICAL: user_info must be COMPREHENSIVE. Do NOT just pass the user's raw message.
457+
You MUST gather and consolidate ALL available information:
458+
* Content from referenced/mentioned documents (e.g., uploaded resumes, CVs, LinkedIn profiles)
459+
that appear in the conversation context — extract and include their FULL content.
460+
* Information the user shared across multiple messages in the conversation.
461+
* Any relevant details from knowledge base search results in the context.
462+
The more complete the user_info, the better the resume. Include names, contact info,
463+
work experience with dates, education, skills, projects, certifications — everything available.
464+
- user_instructions: Optional style or content preferences (e.g. "emphasize leadership",
465+
"keep it to one page"). For revisions, describe what to change.
466+
- parent_report_id: Set this when the user wants to MODIFY an existing resume from
467+
this conversation. Use the report_id from a previous generate_resume result.
468+
- Returns: Dict with status, report_id, title, and content_type.
469+
- After calling: Give a brief confirmation. Do NOT paste resume content in chat. Do NOT mention report_id or any internal IDs — the resume card is shown automatically.
470+
- VERSIONING: Same rules as generate_report — set parent_report_id for modifications
471+
of an existing resume, leave as None for new resumes.
472+
"""
473+
474+
_TOOL_EXAMPLES["generate_resume"] = """
475+
- User: "Build me a resume. I'm John Doe, engineer at Acme Corp..."
476+
- Call: `generate_resume(user_info="John Doe, engineer at Acme Corp...")`
477+
- WHY: Has creation verb "build" + resume → call the tool.
478+
- User: "Create my CV with this info: [experience, education, skills]"
479+
- Call: `generate_resume(user_info="[experience, education, skills]")`
480+
- User: "Build me a resume" (and there is a resume/CV document in the conversation context)
481+
- Extract the FULL content from the document in context, then call:
482+
`generate_resume(user_info="Name: John Doe\\nEmail: john@example.com\\n\\nExperience:\\n- Senior Engineer at Acme Corp (2020-2024)\\n Led team of 5...\\n\\nEducation:\\n- BS Computer Science, MIT (2016-2020)\\n\\nSkills: Python, TypeScript, AWS...")`
483+
- WHY: Document content is available in context — extract ALL of it into user_info. Do NOT ignore referenced documents.
484+
- User: (after resume generated) "Change my title to Senior Engineer"
485+
- Call: `generate_resume(user_info="", user_instructions="Change the job title to Senior Engineer", parent_report_id=<previous_report_id>)`
486+
- WHY: Modification verb "change" + refers to existing resume → set parent_report_id.
487+
- User: "How should I structure my resume?"
488+
- Do NOT call generate_resume. Answer in chat with advice.
489+
- WHY: No creation/modification verb.
490+
"""
491+
446492
# All tool names that have prompt instructions (order matters for prompt readability)
447493
_ALL_TOOL_NAMES_ORDERED = [
448494
"search_surfsense_docs",
449495
"web_search",
450496
"generate_podcast",
451497
"generate_video_presentation",
452498
"generate_report",
499+
"generate_resume",
453500
"generate_image",
454501
"scrape_webpage",
455502
"update_memory",

surfsense_backend/app/agents/new_chat/tools/registry.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ async def my_tool(param: str) -> dict:
9292
)
9393
from .podcast import create_generate_podcast_tool
9494
from .report import create_generate_report_tool
95+
from .resume import create_generate_resume_tool
9596
from .scrape_webpage import create_scrape_webpage_tool
9697
from .search_surfsense_docs import create_search_surfsense_docs_tool
9798
from .update_memory import create_update_memory_tool, create_update_team_memory_tool
@@ -171,6 +172,16 @@ class ToolDefinition:
171172
# are optional — when missing, source_strategy="kb_search" degrades
172173
# gracefully to "provided"
173174
),
175+
# Resume generation tool (Typst-based, uses rendercv package)
176+
ToolDefinition(
177+
name="generate_resume",
178+
description="Generate a professional resume as a Typst document",
179+
factory=lambda deps: create_generate_resume_tool(
180+
search_space_id=deps["search_space_id"],
181+
thread_id=deps["thread_id"],
182+
),
183+
requires=["search_space_id", "thread_id"],
184+
),
174185
# Generate image tool - creates images using AI models (DALL-E, GPT Image, etc.)
175186
ToolDefinition(
176187
name="generate_image",

0 commit comments

Comments
 (0)