Skip to content

Commit df1ee1c

Browse files
update use case
1 parent 594bfc5 commit df1ee1c

File tree

1 file changed

+170
-67
lines changed

1 file changed

+170
-67
lines changed

community_usecase/Profile_generation/run_profile_generation.py

Lines changed: 170 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Optional
99

1010
from owl.utils import run_society
11-
from pydantic import BaseModel
11+
from pydantic import BaseModel, Field
1212
from dotenv import load_dotenv
1313
from camel.agents import ChatAgent
1414
from camel.logger import get_logger, set_log_level
@@ -120,12 +120,13 @@ def analyze_chat_history(chat_history: list[dict]) -> Optional[str]:
120120
isinstance(assistant_content, str)
121121
and "FINAL SUMMARY REPORT" in assistant_content
122122
):
123-
final_summary=assistant_content
123+
final_summary = assistant_content
124124
if final_summary:
125125
logger.info("\033[94m===== RAW SUMMARY FROM AGENTS =====\033[0m")
126126
logger.info(final_summary)
127127
else:
128-
logger.info("\033[91mNo final summary report found in chat history.\033[0m")
128+
logger.info(
129+
"\033[91mNo final summary report found in chat history.\033[0m")
129130

130131
queried_urls: list[str] = []
131132
for turn in chat_history:
@@ -147,31 +148,63 @@ def analyze_chat_history(chat_history: list[dict]) -> Optional[str]:
147148

148149
return final_summary
149150

151+
150152
# ------------------ Pydantic Schemas ------------------
151153

152154
class PersonalInformation(BaseModel):
153-
name: str
154-
positions: list[str]
155-
contact_information: str | None = None
156-
social_media: list[str] | None = None
157-
research_keywords: list[str]
155+
name: str = Field(description="Full name of the scholar")
156+
positions: list[str] = Field(
157+
description="List of current academic or professional positions")
158+
contact_information: Optional[str] = Field(
159+
description="Primary contact information such as email address or "
160+
"phone number"
161+
)
162+
social_media: Optional[list[str]] = Field(
163+
description="List of social media or personal profile URLs (e.g., "
164+
"LinkedIn, Twitter)"
165+
)
166+
research_keywords: list[str] = Field(
167+
description="Keywords summarizing key research areas or topics")
168+
short_introduction: str = Field(
169+
description="A brief one-paragraph summary of the scholar's profile")
158170

159171

160172
class Biography(BaseModel):
161-
career_history: str
162-
research_focus: str
173+
biography: str = Field(
174+
description="Narrative biography including career trajectory and "
175+
"research journey")
163176

164177

165178
class ResearchInterests(BaseModel):
166-
areas: list[str]
179+
areas: list[str] = Field(
180+
description="List of active or long-term research topics")
167181

168182

169183
class AwardsAndDistinctions(BaseModel):
170-
honors: list[str]
184+
honors: list[str] = Field(
185+
description="List of awards, honors, and professional distinctions "
186+
"received")
171187

172188

173189
class Education(BaseModel):
174-
degrees: list[str]
190+
degrees: list[str] = Field(
191+
description="Academic degrees in reverse chronological order, "
192+
"including institution and year")
193+
194+
195+
class Links(BaseModel):
196+
scholarly_identity_links: list[str] = Field(
197+
description="Links to unique scholarly identity profiles (e.g., "
198+
"ORCID, IEEE Xplore, DBLP)"
199+
)
200+
related_sites: list[str] = Field(
201+
description="Links to institutional or departmental homepages (e.g., "
202+
"KAUST ECE department)"
203+
)
204+
related_links: list[str] = Field(
205+
description="Links to academic and professional presence (e.g., "
206+
"Google Scholar, ResearchGate, LinkedIn)"
207+
)
175208

176209

177210
class ScholarProfile(BaseModel):
@@ -180,21 +213,16 @@ class ScholarProfile(BaseModel):
180213
research_interests: ResearchInterests
181214
awards_and_distinctions: AwardsAndDistinctions
182215
education: Education
216+
links: Links
183217

184218

185-
def generate_html_profile(input_text: str,
186-
output_file: str = "profile.html") -> None:
187-
"""Pipeline: extract structured profile from a free‑form biography and
188-
turn it into a polished HTML page.
189-
190-
Parameters
191-
----------
192-
input_text : str
193-
Raw biography or resume text.
194-
output_file : str, optional
195-
Path where the final HTML file will be saved, by default
196-
"profile.html".
197-
"""
219+
def generate_html_profile(input_text,
220+
template_path: str = "template.html",
221+
output_file: str = "profile.html",
222+
base_url: str = "https://cemse.kaust.edu.sa") -> \
223+
None:
224+
"""Fill an HTML template with profile data, some with agent rewriting,
225+
and write to file."""
198226
extraction_prompt = (
199227
"You are a scholarly‑profile extraction assistant. "
200228
"Return ONLY JSON that matches the provided schema."
@@ -205,43 +233,112 @@ def generate_html_profile(input_text: str,
205233
input_message=input_text,
206234
response_format=ScholarProfile,
207235
)
208-
profile: ScholarProfile = extraction_response.msgs[0].parsed
209-
210-
html_system_prompt = "You are an expert HTML profile page generator."
211-
html_agent = ChatAgent(system_message=html_system_prompt)
212-
213-
html_user_prompt = f"""
214-
Using the following JSON, build a complete HTML5 profile page.
215-
216-
• Sections: Personal Information, Biography, Research Interests, Awards
217-
and Distinctions, Education.
218-
Generate a professional and natural-looking personal profile HTML page
219-
based
220-
on the provided information, following these detailed requirements:
221-
Using standard HTML5 + simple inline CSS, beautiful but not complicated.
222-
Layout should be clearly organized using <h1> for the main title, <h2> for
223-
section headings, and <p> for paragraph text.
224-
Use proper <a href> hyperlinks for any external links (e.g., LinkedIn,
225-
ResearchGate, Google Scholar).
226-
Write in a natural, flowing narrative style suitable for introducing the
227-
person to a broad audience.
228-
229-
Please write ALL the input content to the webpage.
230-
JSON:
231-
{profile.model_dump_json(indent=2)}
236+
profile = extraction_response.msgs[0].parsed
237+
238+
def to_html_list(items: list[str]) -> str:
239+
return "<br>".join(items)
240+
241+
def format_awards_with_agent(awards: list[str]) -> str:
242+
formatted_awards = []
243+
agent = ChatAgent(
244+
system_message="""Reformat awards as HTML list items using the
245+
structure, Like:
246+
<li class="field__item"><strong>The
247+
NSF CAREER grant in low-power computing and
248+
communication systems</strong>, 2024</li>.""")
249+
for award in awards:
250+
response = agent.step(f"Reformat this: {award}")
251+
formatted_awards.append(response.msgs[0].content.strip())
252+
return "\n".join(formatted_awards)
253+
254+
def format_education_with_agent(degrees: list[str]) -> str:
255+
agent = ChatAgent(
256+
system_message="""Reformat education degrees into <dl> format.
257+
Like:
258+
<dl class="field__item"><dt>Doctor of Philosophy (Ph.D.)</dt>
259+
<dd class="text-break">Integrated Circuits and Systems,
260+
<em>University of California</em>, United States, 2003</dd></dl>
261+
""")
262+
263+
items = []
264+
for degree in degrees:
265+
response = agent.step(f"Reformat this: {degree}")
266+
items.append(response.msgs[0].content.strip())
267+
return "\n".join(items)
268+
269+
def format_links(links: list[str], html_template: str) -> str:
270+
agent = ChatAgent(
271+
system_message="Reformat link into this html structure: %s ." %
272+
html_template)
273+
items = []
274+
for link in links:
275+
response = agent.step(f"Reformat this: {link}")
276+
items.append(response.msgs[0].content.strip())
277+
return "\n".join(items)
278+
279+
engage_link_template = """
280+
<li class="field__item"><a class="text-decoration-none"
281+
href="https://orcid.org/0000-0003-1849-083X" title="Follow Ahmed Eltawil
282+
on ORCID at 0000-0003-1849-083X"><button class="btn btn-orcid px-1 py-0
283+
text-bg-light bg-opacity-10 text-break text-wrap" type="button"
284+
aria-label="ORCID"><i class="bi bi-orcid mx-1"></i><span class="mx-1
285+
text-start">ORCID</span></button></a></li>-->
232286
"""
287+
related_link_template = """
288+
<li class="field__item"><a
289+
href="https://scholar.google.com/citations?user=XzW-KWoAAAAJ&amp;hl=en"
290+
class="text-break text-underline-hover">Publications on Google
291+
Scholar</a></li>
292+
"""
293+
related_site_template = """
294+
<li class="list-group-item px-0 field__item"><a class="text-break
295+
text-underline-hover" href="https://ece.kaust.edu.sa/" title="Electrical
296+
and Computer Engineering (ECE) Academic Program">Electrical and Computer
297+
Engineering (ECE)</a></li>
298+
"""
299+
with open(template_path, "r", encoding="utf-8") as f:
300+
template = f.read()
301+
302+
name = profile.personal_information.name
303+
slug = name.replace(" ", '-').lower()
304+
share_url = f"{base_url}/profiles/{slug}"
305+
306+
filled = (
307+
template
308+
.replace("{{ name }}", name)
309+
.replace("{{ personal information }}",
310+
to_html_list(profile.personal_information.positions))
311+
.replace("{{ email }}",
312+
profile.personal_information.contact_information)
313+
.replace("{{ introduction }}",
314+
profile.personal_information.short_introduction)
315+
.replace("{{ biography }}", profile.biography.biography)
316+
.replace("{{ research interests }}",
317+
to_html_list(profile.research_interests.areas))
318+
.replace("{{ awards and distinctions }}", format_awards_with_agent(
319+
profile.awards_and_distinctions.honors))
320+
.replace("{{ education }}",
321+
format_education_with_agent(profile.education.degrees))
322+
.replace("{{ engage }}",
323+
format_links(profile.links.scholarly_identity_links,
324+
html_template=engage_link_template))
325+
.replace("{{ related sites }}",
326+
format_links(profile.links.related_sites,
327+
html_template=related_site_template))
328+
.replace("{{ related links }}",
329+
format_links(profile.links.related_links,
330+
html_template=related_link_template))
331+
.replace("{{ slug }}", slug)
332+
.replace("{{ base_url }}", base_url)
333+
.replace("{{ share_url }}", share_url)
334+
.replace("{{ summary }}",
335+
profile.biography.career_history.replace('\n', '%0A'))
336+
)
233337

234-
html_response = html_agent.step(html_user_prompt)
235-
html_code = html_response.msgs[0].content
236-
html_code_block = re.search(r"```html(.*?)```", html_code, re.DOTALL)
338+
with open(output_file, "w", encoding="utf-8") as f:
339+
f.write(filled)
237340

238-
if html_code_block:
239-
extracted_html = html_code_block.group(1).strip()
240-
with open(output_file, "w", encoding="utf-8") as fp:
241-
fp.write(extracted_html)
242-
logger.info(f"HTML profile page saved to: {output_file}")
243-
else:
244-
logger.warning("No HTML code block found in the Agent response.")
341+
logger.info(f"HTML profile generated at: {output_file}")
245342

246343

247344
def run_profile_generation(task: str | None = None,
@@ -250,18 +347,24 @@ def run_profile_generation(task: str | None = None,
250347
the browsing history, and render the final HTML profile.
251348
"""
252349
default_task = (
253-
"find Bernard‑Ghanem's information based on these websites:\n"
254-
"https://www.linkedin.com/in/bernardghanem/\n"
255-
"https://scholar.google.com/citations?hl=en&user=rVsGTeEAAAAJ"
256-
"&view_op=list_works\n"
257-
"https://x.com/bernardsghanem\n"
258-
"https://www.researchgate.net/profile/Bernard-Ghanem\n"
350+
"""find Bernard‑Ghanem's information based on these websites:
351+
https://www.linkedin.com/in/bernardghanem/
352+
https://scholar.google.com/citations?hl=en&user=rVsGTeEAAAAJ&view_op
353+
=list_works
354+
https://x.com/bernardsghanem
355+
https://www.bernardghanem.com/
356+
https://www.researchgate.net/profile/Bernard-Ghanem
357+
https://www.bernardghanem.com/curriculum-vitae
358+
https://www.bernardghanem.com/home
359+
"""
259360

260361
)
261362
section_plan = """
262363
Information summary focus on sections: "
263364
"Personal Information, Biography, Research Interests, Awards and "
264-
"Distinctions, Education.
365+
"Distinctions, Education, related links, related sites, Scholarly
366+
Identity Links
367+
265368
Each section need to be as detailed as possible.
266369
267370
Final summary report please note "FINAL SUMMARY REPORT" at the beginning.

0 commit comments

Comments
 (0)