Oscarli commited on
Commit
3a232c1
·
verified ·
1 Parent(s): e7aaeda

Upload 8 files

Browse files
Files changed (1) hide show
  1. app.py +172 -44
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import httpx
 
3
  from fastapi import FastAPI, Request, HTTPException
4
  from fastapi.responses import HTMLResponse, JSONResponse
5
  from fastapi.staticfiles import StaticFiles
@@ -69,13 +70,18 @@ async def call_deepseek_api(messages: list, model: str = "deepseek-chat", temper
69
  detail=error_msg
70
  )
71
 
72
- # --- New AI Agent Endpoint ---
73
  @app.post("/generate")
74
  async def generate_content(request: Request):
75
  """
76
- This endpoint uses a two-step LLM process:
77
- 1. Generate a high-quality prompt based on user data.
78
- 2. Use that prompt to generate the final content.
 
 
 
 
 
79
  """
80
  try:
81
  body = await request.json()
@@ -85,41 +91,70 @@ async def generate_content(request: Request):
85
  if not task or not data:
86
  raise HTTPException(status_code=400, detail="Missing 'task' or 'data' in request body")
87
 
88
- # Step 1: Generate a high-quality prompt for the main task
89
- meta_system_prompt = "You are an expert prompt engineer. Your task is to create a detailed and effective 'user' prompt for another AI model, which is an expert career consultant. The generated prompt must guide the second AI to produce a comprehensive, high-quality response in the required format based on the user's raw data and task."
90
-
91
- meta_user_prompt = f"""
92
- I have the following task and user data. Create the perfect prompt for a career consultant AI to handle this.
93
-
94
- **Task:** '{task}'
95
-
96
- **User's Raw Data:**
97
- ```json
98
- {data}
99
- ```
100
-
101
- **Instructions for the prompt you will generate:**
102
- - The prompt must be self-contained and include all necessary user data.
103
- - It must clearly state the desired output format.
104
- - For the 'resume' task, the format MUST be a single JSON object with two keys: "resume" and "analysis", both containing well-formed HTML.
105
- - For all other tasks ('interview', 'learning_path', 'cover_letter', 'linkedin', 'salary'), the format MUST be well-formed HTML content directly.
106
- - The tone of the prompt should be as if a user is asking an expert for help.
107
- - Incorporate all the details from the user's data into the prompt naturally.
108
- """
109
-
110
- print(f"[INFO] Generating prompt for task: {task}")
111
- generated_prompt = await call_deepseek_api(
112
- messages=[
113
- {"role": "system", "content": meta_system_prompt},
114
- {"role": "user", "content": meta_user_prompt}
115
- ],
116
- temperature=0.3 # Lower temperature for more deterministic prompt generation
117
- )
118
- print(f"[DEBUG] Generated Prompt for 2nd LLM call:\n{generated_prompt}")
119
 
120
- # Step 2: Use the generated prompt to get the final content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  final_system_prompts = {
122
- "resume": "You are a professional career consultant and resume expert. Please strictly follow the JSON format for the response, ensuring the HTML is well-formed and professional.",
123
  "interview": "You are an experienced interviewer and career mentor. Provide practical, professional interview preparation materials in well-formed HTML.",
124
  "learning_path": "You are an experienced career mentor and learning planner. Create a personalized, actionable learning path in well-formed HTML.",
125
  "cover_letter": "You are an expert cover letter writer. Write a professional, persuasive, and personalized cover letter in well-formed HTML.",
@@ -128,12 +163,106 @@ async def generate_content(request: Request):
128
  }
129
  final_system_prompt = final_system_prompts.get(task, "You are a helpful AI career assistant.")
130
 
131
- print(f"[INFO] Generating final content for task: {task}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  final_content = await call_deepseek_api(
133
  messages=[
134
  {"role": "system", "content": final_system_prompt},
135
- {"role": "user", "content": generated_prompt}
136
- ]
 
137
  )
138
 
139
  return JSONResponse(content={"content": final_content})
@@ -152,9 +281,8 @@ async def generate_content(request: Request):
152
  @app.post("/call-deepseek")
153
  async def proxy_deepseek(request: Request):
154
  """
155
- This endpoint receives the request from our frontend (index.html),
156
- adds the secret API key, and forwards it to the DeepSeek API.
157
- This is the original proxy endpoint and will be replaced by the /generate endpoint logic.
158
  """
159
  if not DEEPSEEK_API_KEY:
160
  print("[ERROR] DEEPSEEK_API_KEY is not set!")
@@ -211,4 +339,4 @@ async def read_root():
211
  return HTMLResponse(
212
  content="<h1>Error: index.html not found</h1><p>Ensure index.html is in a 'static' folder.",
213
  status_code=404
214
- )
 
1
  import os
2
  import httpx
3
+ import json # <-- 新增导入
4
  from fastapi import FastAPI, Request, HTTPException
5
  from fastapi.responses import HTMLResponse, JSONResponse
6
  from fastapi.staticfiles import StaticFiles
 
70
  detail=error_msg
71
  )
72
 
73
+ # --- NEW: AI Agent Endpoint (Extractor + Expert Model) ---
74
  @app.post("/generate")
75
  async def generate_content(request: Request):
76
  """
77
+ This endpoint uses a robust "Extractor + Template + Expert" pattern.
78
+
79
+ 1. (Optional) LLM 1 (Extractor): For tasks with messy user input (like 'resume'),
80
+ this step cleans and structures the data into a reliable JSON.
81
+ 2. (Required) Human Template: We use a precise, human-written f-string template
82
+ to build the perfect prompt for the expert.
83
+ 3. (Required) LLM 2 (Expert): This model receives the clean prompt and generates
84
+ the final, high-quality content.
85
  """
86
  try:
87
  body = await request.json()
 
91
  if not task or not data:
92
  raise HTTPException(status_code=400, detail="Missing 'task' or 'data' in request body")
93
 
94
+ structured_data = data # Default to original data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ # --- Step 1: (Optional) LLM 1 (Extractor) ---
97
+ # We only run this for tasks where user input might be "messy"
98
+ if task == "resume":
99
+ print(f"[INFO] Task '{task}' requires data extraction. Running LLM 1 (Extractor)...")
100
+
101
+ extractor_system_prompt = """
102
+ You are an expert data analyst. Your job is to extract and structure key information
103
+ from a user's raw data for a resume. Pay close attention to the 'skills' field,
104
+ which might be a messy, comma-separated list or natural language.
105
+ Your output MUST be a valid JSON object.
106
+
107
+ Keep all fields from the original data, but add a new key 'skills_list'
108
+ containing a clean Python-style list of skills extracted from the 'skills' field.
109
+
110
+ Example Input Data:
111
+ { "name": "Alex", "skills": "i use react, js, and a bit of python. also project management", ... }
112
+
113
+ Example Output JSON:
114
+ { "name": "Alex", "skills": "i use react, js, and a bit of python. also project management", "skills_list": ["React", "JavaScript", "Python (Beginner)", "Project Management"], ... }
115
+ """
116
+
117
+ extractor_user_prompt = f"""
118
+ Please process the following raw user data and return ONLY a valid JSON object.
119
+
120
+ Raw Data:
121
+ ```json
122
+ {json.dumps(data)}
123
+ ```
124
+ """
125
+
126
+ try:
127
+ # --- LLM 1 Call ---
128
+ json_string_output = await call_deepseek_api(
129
+ messages=[
130
+ {"role": "system", "content": extractor_system_prompt},
131
+ {"role": "user", "content": extractor_user_prompt}
132
+ ],
133
+ model="deepseek-chat", # Use a fast model
134
+ temperature=0.1 # Low temp for high accuracy
135
+ )
136
+
137
+ # Clean up potential markdown ```json ... ```
138
+ if "```json" in json_string_output:
139
+ json_string_output = json_string_output.split("```json\n", 1)[1].split("```")[0]
140
+
141
+ structured_data = json.loads(json_string_output)
142
+ print(f"[DEBUG] LLM 1 (Extractor) output: {structured_data}")
143
+
144
+ except Exception as e:
145
+ print(f"[ERROR] LLM 1 (Extractor) failed: {e}. Falling back to raw data.")
146
+ # Fallback: If extraction fails, use the original data and do a simple split
147
+ structured_data = data
148
+ structured_data['skills_list'] = [skill.strip() for skill in data.get('skills', '').split(',')]
149
+
150
+ else:
151
+ print(f"[INFO] Task '{task}' does not require extraction. Using raw data.")
152
+ structured_data = data
153
+
154
+ # --- Step 2: Human-Written Templates ---
155
+
156
  final_system_prompts = {
157
+ "resume": "You are a professional career consultant and resume expert. Your task is to generate a JSON object with two keys: 'resume' (HTML content) and 'analysis' (HTML content). Please strictly follow the JSON format, ensuring all HTML is well-formed and professional.",
158
  "interview": "You are an experienced interviewer and career mentor. Provide practical, professional interview preparation materials in well-formed HTML.",
159
  "learning_path": "You are an experienced career mentor and learning planner. Create a personalized, actionable learning path in well-formed HTML.",
160
  "cover_letter": "You are an expert cover letter writer. Write a professional, persuasive, and personalized cover letter in well-formed HTML.",
 
163
  }
164
  final_system_prompt = final_system_prompts.get(task, "You are a helpful AI career assistant.")
165
 
166
+ # --- Build Final User Prompt from Template ---
167
+ final_user_prompt = ""
168
+
169
+ if task == "resume":
170
+ final_user_prompt = f"""
171
+ Please act as a resume expert. Create an optimized resume and a matching analysis based on the following structured data.
172
+
173
+ **User Profile:**
174
+ - Name: {structured_data.get('name', 'N/A')}
175
+ - Current Role: {structured_data.get('currentRole', 'N/A')}
176
+ - Years of Experience: {structured_data.get('experience', 'N/A')}
177
+ - Cleaned Skills List: {structured_data.get('skills_list', 'N/A')}
178
+
179
+ **Target Opportunity:**
180
+ - Job Title: {structured_data.get('jobTitle', 'N/A')}
181
+ - Company: {structured_data.get('company', 'N/A')}
182
+ - Job Description:
183
+ ```
184
+ {structured_data.get('jobDescription', 'N/A')}
185
+ ```
186
+
187
+ **Required Output Format:**
188
+ You MUST return a single, valid JSON object with two keys: "resume" and "analysis".
189
+ Both keys must contain well-formed HTML content.
190
+ """
191
+
192
+ elif task == "interview":
193
+ final_user_prompt = f"""
194
+ Please act as an interview coach. Generate interview questions based on this data.
195
+
196
+ - Role: {structured_data.get('role', 'N/A')}
197
+ - Level: {structured_data.get('level', 'N/A')}
198
+ - Key Skills: {structured_data.get('skills', 'N/A')}
199
+
200
+ **Required Output Format:**
201
+ A single block of well-formed HTML content.
202
+ """
203
+
204
+ elif task == "learning_path":
205
+ final_user_prompt = f"""
206
+ Please act as a learning planner. Create a personalized learning path.
207
+
208
+ - Current Skills: {structured_data.get('currentSkills', 'N/A')}
209
+ - Target Role: {structured_data.get('targetRole', 'N/A')}
210
+ - Timeline: {structured_data.get('timeline', 'N/A')} months
211
+
212
+ **Required Output Format:**
213
+ A single block of well-formed HTML content, detailing a roadmap.
214
+ """
215
+
216
+ elif task == "cover_letter":
217
+ final_user_prompt = f"""
218
+ Please act as a cover letter writer. Write a letter based on these details.
219
+
220
+ - Company: {structured_data.get('company', 'N/A')}
221
+ - Role: {structured_data.get('role', 'N/A')}
222
+ - Key Achievement: {structured_data.get('achievement', 'N/A')}
223
+ - Tone: {structured_data.get('tone', 'N/A')}
224
+
225
+ **Required Output Format:**
226
+ A single block of well-formed HTML content, formatted as a letter.
227
+ """
228
+
229
+ elif task == "linkedin":
230
+ final_user_prompt = f"""
231
+ Please act as a LinkedIn expert. Optimize a profile based on this data.
232
+
233
+ - Current Headline: {structured_data.get('headline', 'N/A')}
234
+ - Current About: {structured_data.get('about', 'N/A')}
235
+ - Target Industry/Roles: {structured_data.get('target', 'N/A')}
236
+
237
+ **Required Output Format:**
238
+ A single block of well-formed HTML content with sections for "New Headline" and "New About Section".
239
+ """
240
+
241
+ elif task == "salary":
242
+ final_user_prompt = f"""
243
+ Please act as a salary analyst. Provide insights for the following role.
244
+
245
+ - Role: {structured_data.get('role', 'N/A')}
246
+ - Location: {structured_data.get('location', 'N/A')}
247
+ - Experience: {structured_data.get('experience', 'N/A')} years
248
+ - Company Size: {structured_data.get('companySize', 'N/A')}
249
+
250
+ **Required Output Format:**
251
+ A single block of well-formed HTML content, including an estimated range and negotiation tips.
252
+ """
253
+ else:
254
+ final_user_prompt = f"Please perform the task '{task}' with the data: {json.dumps(structured_data)}"
255
+
256
+ print(f"[DEBUG] Final User Prompt for LLM 2:\n{final_user_prompt[:500]}...") # Log first 500 chars
257
+
258
+ # --- Step 3: LLM 2 (Expert) Call ---
259
+ print(f"[INFO] Generating final content for task: {task} using LLM 2 (Expert)...")
260
  final_content = await call_deepseek_api(
261
  messages=[
262
  {"role": "system", "content": final_system_prompt},
263
+ {"role": "user", "content": final_user_prompt}
264
+ ],
265
+ temperature=0.7 # Standard temp for creative/expert output
266
  )
267
 
268
  return JSONResponse(content={"content": final_content})
 
281
  @app.post("/call-deepseek")
282
  async def proxy_deepseek(request: Request):
283
  """
284
+ This endpoint is kept for legacy purposes but is not used by the
285
+ new /generate logic.
 
286
  """
287
  if not DEEPSEEK_API_KEY:
288
  print("[ERROR] DEEPSEEK_API_KEY is not set!")
 
339
  return HTMLResponse(
340
  content="<h1>Error: index.html not found</h1><p>Ensure index.html is in a 'static' folder.",
341
  status_code=404
342
+ )