igiuseppe commited on
Commit
757671f
·
1 Parent(s): 55f5cff

divided code in files and working api

Browse files
Files changed (4) hide show
  1. app.py +54 -209
  2. core.py +115 -0
  3. schemas.py +36 -0
  4. utils.py +21 -0
app.py CHANGED
@@ -1,233 +1,78 @@
1
- import random
2
- from typing import List, Dict
3
- import openai # or any LLM wrapper like llama_index, langchain, etc.
4
- import json
5
  from pydantic import BaseModel
6
  from dotenv import load_dotenv
7
- from fastapi import FastAPI,Request, HTTPException, Header
8
  import uvicorn
9
- import os
 
 
10
 
11
  load_dotenv()
12
 
13
  API_KEY = os.getenv("MY_API_KEY")
14
- latest_fleet_data = {"fleet": [], "scope": ""}
15
-
16
- ### 1. Configuration ###
17
- LLM_MODEL = "gpt-4o-mini"
18
-
19
- ### 2. Utilities ###
20
- def call_llm(prompt: str,response_format=None) -> str:
21
- client = openai.OpenAI()
22
- if response_format:
23
- response = client.beta.chat.completions.parse(
24
- model=LLM_MODEL,
25
- messages=[{"role": "user", "content": prompt}],
26
- response_format=response_format,
27
- temperature=0
28
- )
29
- else:
30
- response = client.chat.completions.create(
31
- model=LLM_MODEL,
32
- messages=[{"role": "user", "content": prompt}],
33
- temperature=0
34
- )
35
- return response.choices[0].message.content
36
-
37
- ### 3. Generate User Parameters ###
38
- def generate_user_parameters(audience: str, scope: str) -> List[str]:
39
- # Combine standard parameters with additional parameters
40
- standard_parameters = ["Name", "Age", "Location", "Profession"]
41
- prompt = f"""
42
- You are an expert customer researcher.
43
-
44
- Your task is to help define user personas for a specific audience and a specific research scope.
45
 
46
- This is the desired audience: {audience}
47
- This is the research scope: {scope}
48
-
49
- Start from the following 4 standard demographic parameters: {standard_parameters}
50
-
51
- Your goal is to suggest 4 additional parameters that are especially relevant for capturing behaviors, attitudes, or characteristics important for this audience and scope.
52
-
53
- Only suggest parameters that will meaningfully help differentiate users in this specific context.
54
- """
55
 
56
- class Response(BaseModel):
57
- additional_parameters: list[str]
58
-
59
- response = call_llm(prompt=prompt,response_format=Response)
60
- additional_parameters = json.loads(response)["additional_parameters"]
61
-
62
- print(f"prompt: {prompt}\n response: {response}")
63
-
64
 
65
- return standard_parameters + additional_parameters
66
-
67
- ### 4. Synthetic User Creation ###
68
- def generate_synthetic_personas(parameters: List[str], num_personas: int,audience:str) -> List[Dict]:
69
- # Create a prompt for the LLM to generate diversified personas
70
- if num_personas > 1:
71
- prompt = f"""
72
- You are an expert in user persona creation.
73
-
74
- Generate {num_personas} diversified user personas based on the following parameters:
75
- {parameters}
76
-
77
- Each persona should be unique and realistic. Provide the output as a list. Take into account that the desired audience is the follwoing: {audience}.
78
- """
79
- else:
80
- prompt = f"""
81
- You are an expert in user persona creation.
82
-
83
- Generate a user persona based on the following parameters:
84
- {parameters}
85
-
86
- The persona should be unique and realistic. Provide the output as a list. Take into account that the desired audience is the follwoing: {audience}.
87
- """
88
-
89
- class Parameter(BaseModel):
90
- parameter_name: str
91
- value: str
92
-
93
- class User(BaseModel):
94
- user_persona: list[Parameter]
95
 
96
- class Response(BaseModel):
97
- users_personas: list[User]
 
 
 
 
98
 
 
99
 
100
- # Call the LLM to generate the personas
101
- response = call_llm(prompt=prompt,response_format=Response)
102
- print("Generated Personas:", response)
103
- # Parse the response into a list of dictionaries
104
- response=json.loads(response)
105
 
106
- transformed_data = {
107
- "users_personas": [
108
- {item["parameter_name"]: item["value"] for item in user["user_persona"]}
109
- for user in response["users_personas"]
110
- ]
111
- }
112
 
113
- return transformed_data
114
 
115
-
116
- ### 5. Q&A with Persona ###
117
- from concurrent.futures import ThreadPoolExecutor
118
-
119
- def ask_questions_to_persona(persona: dict, questions: List[str]) -> List[str]:
120
- """
121
- Ask questions to a persona in parallel using a maximum of 10 workers.
122
-
123
- Args:
124
- persona (dict): The user persona as a dictionary.
125
- questions (List[str]): A list of questions to ask the persona.
126
-
127
- Returns:
128
- List[str]: A list of answers corresponding to the questions.
129
- """
130
- def ask_single_question(question: str) -> str:
131
- # Function to ask a single question
132
- prompt = f"""Act as if you are this user persona:\n{persona}\n\nAnswer the following question as if you were them:\n{question}"""
133
- return call_llm(prompt, False)
134
-
135
- # Use ThreadPoolExecutor to ask questions in parallel
136
- max_workers = min(8, len(questions)) # Limit the number of workers to 8 or the number of questions
137
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
138
- answers = list(executor.map(ask_single_question, questions))
139
-
140
- return answers
141
-
142
- ### 6. Fleet Generator ###
143
- def generate_fleet(n: int, parameters: Dict, questions: List[str],audience:str) -> List[Dict]:
144
- fleet = []
145
- users_personas = generate_synthetic_personas(parameters=parameters,num_personas=n,audience=audience)["users_personas"]
146
- i=0
147
- for persona_dict in users_personas:
148
- i+=1
149
- print("index:", i)
150
- print("Persona Dictionary:", persona_dict)
151
- answers = ask_questions_to_persona(persona_dict, questions)
152
- print("Answers:", answers)
153
- persona_dict["answers"] = answers
154
- fleet.append(persona_dict)
155
- print("Fleet:", fleet)
156
- return fleet
157
-
158
- def generate_report() -> str:
159
- fleet = latest_fleet_data["fleet"]
160
- scope = latest_fleet_data["scope"]
161
-
162
- content = f"Scope of Research:\n{scope}\n\n"
163
- for i, user in enumerate(fleet, 1):
164
- content += f"### User {i} ###\n"
165
- # Iterate over the dictionary values to display all attributes
166
- for key, value in user.items():
167
- if key != "answers": # Skip the "answers" key for now
168
- content += f"{key}: {value}\n"
169
- content += "\n"
170
- # Add answers to the output
171
- for j, answer in enumerate(user.get("answers", []), 1):
172
- content += f"Q{j}: {answer}\n\n"
173
- content += "\n---\n\n"
174
-
175
- prompt = f"Write a research report based on the following scope and interviews:\n\nScope:\n{scope}\n\nQ&A Content:\n{content}\n\nInclude a section where you extract all the important insights from the interviews following the scope.\n\n"
176
-
177
- report_text = call_llm(prompt, False)
178
 
179
- return report_text
180
-
181
- def main(audience: str, scope: str, n: int, questions: List[str]) -> tuple[str, str]:
182
- # Generate user parameters based on audience and scope
183
- user_parameters = generate_user_parameters(audience, scope)
184
-
185
- # Generate fleet
186
- fleet = generate_fleet(n, user_parameters, questions, audience)
187
-
188
- # Format interviews
189
- interviews_output = ""
190
- for i, user in enumerate(fleet, 1):
191
- interviews_output += f"### User {i} ###\n"
192
- for key, value in user.items():
193
- if key != "answers":
194
- interviews_output += f"{key}: {value}\n"
195
- interviews_output += "\n"
196
- for j, answer in enumerate(user.get("answers", []), 1):
197
- interviews_output += f"Q{j}: {answer}\n\n"
198
- interviews_output += "\n---\n\n"
199
 
200
- # Generate report using the modified generate_report function
201
- latest_fleet_data["fleet"] = fleet
202
- latest_fleet_data["scope"] = scope
203
- report = generate_report()
204
-
205
- return interviews_output, report
206
 
207
- app = FastAPI()
208
 
209
- class InterviewRequest(BaseModel):
210
- audience: str
211
- scope: str
212
- n: int
213
- questions: list[str]
214
 
215
- class InterviewResponse(BaseModel):
216
- interviews_output: str
217
- report: str
218
 
 
 
 
219
 
220
- @app.post("/generate", response_model=InterviewResponse)
221
- def generate_interviews(request: InterviewRequest, x_api_key: str = Header(...)):
222
- if x_api_key != API_KEY:
223
- raise HTTPException(status_code=403, detail="Invalid API Key")
224
-
225
- interviews_output, report = main(
226
- audience=request.audience,
227
- scope=request.scope,
228
- n=request.n,
229
- questions=request.questions
230
- )
231
- return InterviewResponse(interviews_output=interviews_output, report=report)
232
 
233
- #if __name__ == "__main__": uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=False)
 
1
+ import os
2
+ from fastapi import FastAPI, HTTPException, Header
 
 
3
  from pydantic import BaseModel
4
  from dotenv import load_dotenv
5
+ from schemas import GenerateInterviewsRequest, GenerateInterviewsResponse, GenerateReportRequest, GenerateReportResponse, GenerateUsersRequest, GenerateUsersResponse, GenerateRequest, GenerateResponse
6
  import uvicorn
7
+ import logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
 
11
  load_dotenv()
12
 
13
  API_KEY = os.getenv("MY_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ app = FastAPI()
16
+ """
17
+ @app.post("/generate-users", response_model=GenerateUsersResponse)
18
+ def generate_users(request: GenerateUsersRequest, x_api_key: str = Header(...)):
19
+ if x_api_key != API_KEY:
20
+ raise HTTPException(status_code=403, detail="Invalid API Key")
 
 
 
21
 
22
+ from core import generate_user_parameters, generate_synthetic_personas
 
 
 
 
 
 
 
23
 
24
+ user_parameters = generate_user_parameters(request.audience, request.scope)
25
+ personas = generate_synthetic_personas(parameters=user_parameters, num_personas=request.n, audience=request.audience)
26
+
27
+ return GenerateUsersResponse(users_personas=personas)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ @app.post("/generate-interviews", response_model=GenerateInterviewsResponse)
30
+ def generate_interviews(request: GenerateInterviewsRequest, x_api_key: str = Header(...)):
31
+ if x_api_key != API_KEY:
32
+ raise HTTPException(status_code=403, detail="Invalid API Key")
33
+
34
+ from core import generate_fleet
35
 
36
+ fleet = generate_fleet(n=request.n, parameters=request.users_personas, questions=request.questions, audience=request.scope)
37
 
38
+ return GenerateInterviewsResponse(fleet=fleet)
 
 
 
 
39
 
40
+ @app.post("/generate-report", response_model=GenerateReportResponse)
41
+ def generate_report(request: GenerateReportRequest, x_api_key: str = Header(...)):
42
+ if x_api_key != API_KEY:
43
+ raise HTTPException(status_code=403, detail="Invalid API Key")
 
 
44
 
45
+ from core import generate_report
46
 
47
+ report = generate_report(fleet=request.fleet, scope=request.scope)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ return GenerateReportResponse(report=report) """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ @app.post("/generate", response_model=GenerateResponse)
52
+ def generate_all(request: GenerateRequest, x_api_key: str = Header(...)):
53
+ if x_api_key != API_KEY:
54
+ logger.warning("Unauthorized access attempt.")
55
+ raise HTTPException(status_code=403, detail="Invalid API Key")
 
56
 
57
+ from core import generate_user_parameters, generate_synthetic_personas, generate_fleet, generate_report
58
 
59
+ logger.info(f"Generating user personas for audience: {request.audience}, scope: {request.scope}, n: {request.n}")
60
+ user_parameters = generate_user_parameters(request.audience, request.scope)
61
+ logger.info(f"User parameters: {user_parameters}")
62
+ users = generate_synthetic_personas(parameters=user_parameters, num_personas=request.n, audience=request.audience)["users_personas"]
63
+ logger.info(f"Generated personas:\n{users}")
64
 
65
+ logger.info("Generating interviews for each persona.")
66
+ fleet = generate_fleet(n=request.n, parameters=user_parameters, questions=request.questions, audience=request.audience)
67
+ logger.info(f"Generated fleet:\n{fleet}")
68
 
69
+ logger.info("Generating report based on all interviews.")
70
+ report_text = generate_report(fleet=fleet, scope=request.scope)
71
+ logger.info("Report generation completed.")
72
 
73
+ return {
74
+ "users": fleet,
75
+ "report": report_text
76
+ }
 
 
 
 
 
 
 
 
77
 
78
+ if __name__ == "__main__": uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=False)
core.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import List, Dict
3
+ from pydantic import BaseModel
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from utils import call_llm
6
+
7
+ LLM_MODEL = "gpt-4o-mini"
8
+
9
+ def generate_user_parameters(audience: str, scope: str) -> List[str]:
10
+ standard_parameters = ["Name", "Age", "Location", "Profession"]
11
+ prompt = f"""
12
+ You are an expert customer researcher.
13
+
14
+ Your task is to help define user personas for a specific audience and a specific research scope.
15
+
16
+ This is the desired audience: {audience}
17
+ This is the research scope: {scope}
18
+
19
+ Start from the following 4 standard demographic parameters: {standard_parameters}
20
+
21
+ Your goal is to suggest 4 additional parameters that are especially relevant for capturing behaviors, attitudes, or characteristics important for this audience and scope.
22
+
23
+ Only suggest parameters that will meaningfully help differentiate users in this specific context. The parameters should be tailored for the audience and scope but also not too specific.
24
+ """
25
+
26
+ class Response(BaseModel):
27
+ additional_parameters: list[str]
28
+
29
+ response = call_llm(prompt=prompt, response_format=Response)
30
+ additional_parameters = json.loads(response)["additional_parameters"]
31
+
32
+ return standard_parameters + additional_parameters
33
+
34
+
35
+ def generate_synthetic_personas(parameters: List[str], num_personas: int, audience: str) -> Dict:
36
+ prompt = f"""
37
+ You are an expert in user persona creation.
38
+
39
+ Generate {num_personas} diversified user personas based on the following parameters:
40
+ {parameters}
41
+
42
+ Each persona should be unique and realistic, ensure variability. Provide the output as a list. Take into account that the desired audience is the following: {audience}.
43
+ Ensure that the list contains exactly {num_personas} personas.
44
+ """
45
+
46
+ class Parameter(BaseModel):
47
+ parameter_name: str
48
+ value: str
49
+
50
+ class User(BaseModel):
51
+ user_persona: list[Parameter]
52
+
53
+ class Response(BaseModel):
54
+ users_personas: list[User]
55
+
56
+ response = call_llm(prompt=prompt, response_format=Response)
57
+ response = json.loads(response)
58
+
59
+ transformed_data = {
60
+ "users_personas": [
61
+ {item["parameter_name"]: item["value"] for item in user["user_persona"]}
62
+ for user in response["users_personas"]
63
+ ]
64
+ }
65
+
66
+ return transformed_data
67
+
68
+
69
+ def ask_questions_to_persona(persona: dict, questions: List[str]) -> List[str]:
70
+ def ask_single_question(question: str) -> str:
71
+ prompt = f"""
72
+ Act as if you were this user persona:
73
+ {persona}
74
+ You need to impersonate this user persona and answer the following question as if you were that user:
75
+ {question}
76
+ Never sart the sentences in the following way: As "name of the person"...
77
+ Try to sound natural and authentic, as if you were the user persona.
78
+ Make sure to answer the question in a way that is relevant to the user persona.
79
+ """
80
+ return call_llm(prompt, False)
81
+
82
+ max_workers = min(5, len(questions))
83
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
84
+ answers = list(executor.map(ask_single_question, questions))
85
+
86
+ return answers
87
+
88
+
89
+ def generate_fleet(n: int, parameters: Dict, questions: List[str], audience: str) -> List[Dict]:
90
+ fleet = []
91
+ users_personas = generate_synthetic_personas(parameters=parameters, num_personas=n, audience=audience)["users_personas"]
92
+ for persona_dict in users_personas:
93
+ answers = ask_questions_to_persona(persona_dict, questions)
94
+ persona_dict["answers"] = answers
95
+ fleet.append(persona_dict)
96
+ return fleet
97
+
98
+
99
+ def generate_report(fleet,scope) -> str:
100
+
101
+ content = f"Scope of Research:\n{scope}\n\n"
102
+ for i, user in enumerate(fleet, 1):
103
+ content += f"### User {i} ###\n"
104
+ for key, value in user.items():
105
+ if key != "answers":
106
+ content += f"{key}: {value}\n"
107
+ content += "\n"
108
+ for j, answer in enumerate(user.get("answers", []), 1):
109
+ content += f"Q{j}: {answer}\n\n"
110
+ content += "\n---\n\n"
111
+
112
+ prompt = f"Write a research report based on the following scope and interviews:\n\nScope:\n{scope}\n\nQ&A Content:\n{content}\n\nInclude a section where you extract all the important insights from the interviews following the scope.\n\n"
113
+ report_text = call_llm(prompt, False)
114
+
115
+ return report_text
schemas.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Dict
3
+
4
+ class GenerateUsersRequest(BaseModel):
5
+ audience: str
6
+ scope: str
7
+ n: int
8
+
9
+ class GenerateUsersResponse(BaseModel):
10
+ users_personas: List[Dict[str, str]]
11
+
12
+ class GenerateInterviewsRequest(BaseModel):
13
+ users_personas: List[Dict[str, str]]
14
+ questions: List[str]
15
+ scope: str
16
+
17
+ class GenerateInterviewsResponse(BaseModel):
18
+ fleet: List[Dict[str, str]]
19
+
20
+ class GenerateReportRequest(BaseModel):
21
+ fleet: List[Dict[str, str]]
22
+ scope: str
23
+
24
+ class GenerateReportResponse(BaseModel):
25
+ report: str
26
+
27
+ class GenerateRequest(BaseModel):
28
+ audience: str
29
+ scope: str
30
+ n: int
31
+ questions: List[str]
32
+
33
+ class GenerateResponse(BaseModel):
34
+ users_personas: List[dict]
35
+ interviews: List[dict]
36
+ report: str
utils.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import openai
2
+ import os
3
+
4
+ LLM_MODEL = "gpt-4o-mini"
5
+
6
+ def call_llm(prompt: str, response_format=None) -> str:
7
+ client = openai.OpenAI()
8
+ if response_format:
9
+ response = client.beta.chat.completions.parse(
10
+ model=LLM_MODEL,
11
+ messages=[{"role": "user", "content": prompt}],
12
+ response_format=response_format,
13
+ temperature=0
14
+ )
15
+ else:
16
+ response = client.chat.completions.create(
17
+ model=LLM_MODEL,
18
+ messages=[{"role": "user", "content": prompt}],
19
+ temperature=0
20
+ )
21
+ return response.choices[0].message.content