OpenAI Assistants API提供了对线程管理的内置支持,以及对检索、代码解释和函数调用等功能的支持,使开发人员能够以更少的成本创建更复杂的人工智能助手。因此,当新的更新推出时,用它来构建人工智能应用程序将更具优势。
它还是个新产品,缺乏广泛的教程或文档,本文提供了动手制作一个小原型的教程及相应代码。
应用构造目标
在各种想法中,我最终决定创建一个助手,专注于优化用户的LinkedIn个人资料。这个想法源于与一位好友的谈话,他最近一直在找工作,并向我询问LinkedIn个人资料的使用技巧。我自己并不是专家,但我想,为什么不打造一个专家呢?同时,尝试使用Assistants API并从中获得一些乐趣呢。就这样,一个计划形成了,而我所需要做的就是将这一切整合在一起。
即使只是一个原型,其结果也相当令人印象深刻--助手出色地为用户提供了建议,告诉他们应该在哪些方面下功夫、做出改变、加以改进,甚至还为整个个人档案打分。
定义工作方式
工作流
用户首先输入自己的LinkedIn个人资料URL,然后工作就正式开始了。在后台,主要表现为三步流程:
- 抓取LinkedIn简介:从用户资料中提取相关信息,包括profile图像。
- 通过GPT-4 Vision进行图像分析:对个人资料图像进行分析,以评估其对LinkedIn专业形象的适合性和有效性。
- 综合个人资料审查:Assistant会将文本数据和图像分析综合成一份详细的报告,并提出可行的改进建议。
得到初步分析结果后,用户可以继续与助手对话,以获得进一步的意见。
拆分代码任务
这个应用由3个脚本代码构成:
- main.py:创建带有预定义知识库的助手,并增加分析个人资料图片的功能。
- linkedin_scraper.py:抓取LinkedIn资料,提取详细信息和资料图片URL以供分析。
- app.py:使用Streamlit构建的用户界面,允许提交个人资料并显示AI生成的建议。
Part 1:创建助手——main.py
第一步是创建助手,这可以通过OpenAI平台完成,也可以直接在本地代码中执行。Assistant API支持三种工具:代码解释器、检索和函数调用。本文将通过数据检索和函数调用启用助手:
- 数据检索:通过上传6个PDF文件集,其中包含有关LinkedIn个人资料改进的专家建议,助理可以利用这些知识库进行分析。
- 图像分析函数调用:虽然助手API缺乏内置视觉功能,但我们可以利用GPT-4 Vision。我们需要使用一个在需要时自动调用的自定义函数将其连接到助手。这样,我们的助手就能对用户的个人资料图片进行视觉分析并提供反馈。
main.py中集成助手应用程序接口的工作流
- 创建助手:定义助手的指令并选择一个模型(本文选择gpt-4-turbo-preview)。启用检索和函数调用。
- 启动对话线程:每当用户开始与助手对话时,就启动一个新的线程。
- 将用户查询添加到Message:在用户提问时,用Message填充线程。
- 激活线程上的助手:助手会根据线程内容生成回复,并根据需要自动调用相关工具。
create_linkedin_profile_analyzer函数包含初始化助手的逻辑。这包括上传PDF文件以形成我们的知识库。这些文件被上传到OpenAI,其ID用于启用助手的检索功能,使其能够在分析用户配置文件时利用该知识库。用于图像分析的自定义函数analyze_profile_picture,可在提供图像URL时由助手自动调用,以便根据专业标准对个人资料图片进行评估。代码如下:
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
api_key = os.environ.get("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)
# Define the function for creating the Assistant
def create_linkedin_profile_analyzer():
# List of PDF files
pdf_files = [
"file1.pdf",
"file2.pdf",
"file3.pdf",
"file4.pdf",
"file5.pdf",
"file6.pdf"
]
# Path to the directory containing the PDFs
pdf_directory = "knowledge" # Adjust the path as necessary
# Upload documents for Retrieval
file_ids = []
for pdf_file in pdf_files:
file_path = os.path.join(pdf_directory, pdf_file)
with open(file_path, "rb") as file_data:
file = client.files.create(file=file_data, purpose="assistants")
file_ids.append(file.id)
# Create the Assistant with necessary tools
assistant = client.beta.assistants.create(
name="LinkedIn Profile Analyzer",
instructions = (
"You are an expert in LinkedIn profile optimization, tasked with providing a comprehensive analysis "
"of a user's LinkedIn profile, analyze it thoroughly. Be helpful, "
"and maintain a casual, approachable yet professional tone. Remember to address the user directly and use the first person.\n"
"- analyze_profile_picture, when the image url of the profile is given."
),
model="gpt-4-turbo-preview",
tools=[
{"type": "retrieval", "file_ids": file_ids},
{"type": "function", "function": function_json}
]
)
return assistant
# Define the function to create thread and run
def create_thread_and_run(assistant_id, user_message):
thread = client.beta.threads.create()
client.beta.threads.messages.create(
thread_id=thread.id, role="user", content=user_message
)
return client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id,
)
# Define the custom function for image analysis
function_json = {
"name": "analyze_profile_picture",
"description": "Analyze this LinkedIn profile picture provided through the image URL. Examine its appropriateness for a professional LinkedIn profile by focusing on the presentation, expression and body language, composition and setting (including background), and the quality of the image. Ensure your analysis determines whether these aspects meet professional standards and offer specific recommendations for any needed improvements to enhance the profile's professional image. Overall, describe the image in detail.",
"parameters": {
"type": "object",
"properties": {
"image_url": {"type": "string", "description": "URL of the profile picture"}
},
"required": ["image_url"]
}
}
def handle_custom_function(run):
if run.status == 'requires_action' and run.required_action.type == 'submit_tool_outputs':
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
if tool_call.function.name == "analyze_profile_picture":
image_url = json.loads(tool_call.function.arguments)["image_url"]
# Call GPT-4 Vision API to analyze the image
vision_response = client.chat.completions.create(
model='gpt-4-vision-preview',
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": (
"Analyze this LinkedIn profile picture. Provide an analysis focusing on its "
"appropriateness and effectiveness for a LinkedIn profile. Consider the following aspects:\n\n"
"1. Presentation: Evaluate the subject's attire and grooming. Does it align with professional standards suitable for "
"their industry or field?\n"
"2. Expression and Body Language: Assess the subject's facial expression and body language. Does it project confidence, "
"approachability, and professionalism?\n"
"3. Composition and Setting: Comment on the composition of the photograph, including the background. Is it distraction-free "
"and does it enhance the subject's professional image?\n"
"4. Quality and Lighting: Evaluate the quality of the photograph, including lighting and clarity. Does the image quality "
"uphold professional standards?\n\n"
"Provide recommendations for improvement if necessary, highlighting aspects that could enhance the subject's professional "
"portrayal on LinkedIn."
)
},
{
"type": "image_url",
"image_url": {
"url": image_url,
},
},
],
}
],
max_tokens=400
)
# Submit the output back to the Assistant
client.beta.threads.runs.submit_tool_outputs(
thread_id=run.thread_id,
run_id=run.id,
tool_outputs=[
{
'tool_call_id': tool_call.id,
'output': vision_response.choices[0].message.content
}
]
)
# Main function to create the assistant
def main():
assistant = create_linkedin_profile_analyzer()
if __name__ == "__main__":
main()Part 2. Scraping the profiles — linkedin_scraper.py
Part 2:抓取资料——linkedin_scraper.py
为了抓取Linkedin资料,该脚本使用了RapidAPI的第三方API,该API提供50个免费请求额度。由于LinkedIn的保护措施,直接搜索可能具有挑战性。虽然创建一个自定义的搜索器可以对检索的数据进行更多控制,但本文更倾向于使用已建立的API来快速创建原型。
linkedin_scraper.py首先从提供的LinkedIn资料URL中提取用户名。有了用户名,我们就会向RapidAPI端点发出请求,检索个人资料的数据,包括个人信息、职业经历、教育、技能、语言和认证。然后,这些数据会被格式化为适合GPT分析的结构化格式。这就是我使用的 API。下面是代码:
import os
import requests
import argparse
import logging
from dotenv import load_dotenv
# Set up basic configuration for logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
load_dotenv()
rapidapi_key = os.getenv('RAPIDAPI_KEY')
def main(profile_url):
formatted_text, profile_image_url = scrape_linkedin_profile(profile_url)
if formatted_text: # Just check if formatted_text is not None or empty
logging.info("Profile Data Scraped Successfully:")
logging.info(formatted_text)
if profile_image_url:
logging.info(f"Profile Image URL: {profile_image_url}")
else:
logging.info("No Profile Image URL found.")
else:
logging.error("Failed to scrape profile data or profile is incomplete/private.")
def scrape_linkedin_profile(profile_url):
username = extract_username(profile_url)
if not rapidapi_key:
logging.error("RapidAPI key not found. Please set the RAPIDAPI_KEY environment variable.")
return None, None
url = "https://linkedin-api8.p.rapidapi.com/"
querystring = {"username": username}
headers = {
"X-RapidAPI-Key": rapidapi_key,
"X-RapidAPI-Host": "linkedin-api8.p.rapidapi.com"
}
try:
response = requests.get(url, headers=headers, params=querystring)
if response.status_code == 200:
profile_data = response.json()
logging.debug(f"API Response: {profile_data}")
formatted_text, profile_image_url = format_data_for_gpt(profile_data)
# Additional logging to confirm data formatting
logging.debug(f"Formatted Text: {formatted_text[:500]}")
logging.debug(f"Profile Image URL: {profile_image_url}")
return formatted_text, profile_image_url
else:
logging.error(f"Failed to fetch profile data. Status Code: {response.status_code}")
return None, None
except Exception as e:
logging.exception("An error occurred while fetching the profile data.")
return None, None
def extract_username(linkedin_url):
username = linkedin_url.split('/')[-1] if linkedin_url.split('/')[-1] else linkedin_url.split('/')[-2]
return username.strip()
import logging
def safe_get_list(data, key):
"""
Safely get a list value from a dictionary. Returns an empty list if the key is not found or the value is None.
"""
value = data.get(key)
return [] if value is None else value
def safe_get_value(data, key, default=''):
"""Safely get a single value from a dictionary. Returns a default value if the key is not found or the value is None."""
return data.get(key) if data.get(key) is not None else default
def format_data_for_gpt(profile_data):
try:
# Extract the profile image URL safely
profile_image_url = safe_get_value(profile_data, 'profilePicture', '')
# Personal Information with safe retrieval
firstName = safe_get_value(profile_data, 'firstName', 'No first name')
lastName = safe_get_value(profile_data, 'lastName', 'No last name')
fullName = f"{firstName} {lastName}"
headline = safe_get_value(profile_data, 'headline', 'No headline provided')
summary = safe_get_value(profile_data, 'summary', 'No summary provided')
location = safe_get_value(profile_data.get('geo', {}), 'full', 'No location provided')
formatted_text = f"Name: {fullName}\nHeadline: {headline}\nLocation: {location}\nSummary: {summary}\n"
# Professional Experience
formatted_text += "Experience:\n"
for position in safe_get_list(profile_data, 'position'):
company = position.get('companyName', 'No company name')
title = position.get('title', 'No title')
jobLocation = position.get('location', 'No location')
jobDescription = position.get('description', 'No description').replace('\n', ' ')
formatted_text += f"- {title} at {company}, {jobLocation}. {jobDescription}\n"
# Education
formatted_text += "Education:\n"
for education in safe_get_list(profile_data, 'educations'):
school = education.get('schoolName', 'No school name')
degree = education.get('degree', 'No degree')
field = education.get('fieldOfStudy', 'No field of study')
grade = education.get('grade', 'No grade')
eduDescription = education.get('description', 'No description').replace('\n', ' ')
formatted_text += f"- {degree} in {field} from {school}, Grade: {grade}. {eduDescription}\n"
# Skills
formatted_text += "Skills:\n"
for skill in safe_get_list(profile_data, 'skills'):
skillName = skill.get('name', 'No skill name')
formatted_text += f"- {skillName}\n"
# Languages
formatted_text += "Languages:\n"
languages = safe_get_list(profile_data, 'languages')
if languages:
for language in languages:
langName = language.get('name', 'No language name')
proficiency = language.get('proficiency', 'No proficiency level')
formatted_text += f"- {langName} ({proficiency})\n"
else:
formatted_text += "- No languages provided\n"
# Certifications
formatted_text += "Certifications:\n"
for certification in safe_get_list(profile_data, 'certifications'):
certName = certification.get('name', 'No certification name')
formatted_text += f"- {certName}\n"
return formatted_text, profile_image_url
except Exception as e:
logging.exception("An error occurred during data formatting.")
return None, None
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Scrape LinkedIn profile data.")
parser.add_argument("profile_url", help="LinkedIn profile URL to scrape.")
args = parser.parse_args()
main(args.profile_url)
Part 3:整体展示——app.py
最后一块拼图:用户界面。本文选择Streamlit是因为它能简单高效地将脚本转化为可共享的网络应用--纯粹的python,没有前端的繁琐。
工作流
脚本首先加载必要的环境变量,包括ASSISTANT_ID,这对于与我们创建的OpenAI 助手进行交互至关重要。这可以从OpenAI平台上的 "Assistants"页面获取。
Streamlit的会话状态用于管理应用程序的状态,跟踪聊天是否已开始、请求的分析以及是否已完成。
构建用户界面
侧边栏是主要的交互点,用户可在此输入OpenAI API密钥和LinkedIn个人资料URL。用户还可以选择输入工作偏好,以便进行更个性化的分析。
启动分析后,会通过一个按钮与助理创建对话线程。
处理LinkedIn资料
一旦触发了分析按钮,脚本就会使用前面提到的scrape_linkedin_profile函数从LinkedIn资料中提取数据并进行格式化,包括资料图片。然后将这些数据转发给助手进行处理。
处理自定义函数调用
工作流程的核心部分是handle_custom_function。当助手需要分析个人资料图片时,就会调用该函数,调用GPT-4 Vision API、提交图片URL并处理返回的分析结果。这一步说明了助理功能的扩展,使其能够"看到"图像和视觉元素并提供反馈。
展示分析结果
分析结果显示在主应用界面中。用户还可以提出更多问题并继续对话。
代码如下:
import json
import os
import time
from dotenv import load_dotenv
import openai
import streamlit as st
from linkedin_scraper import scrape_linkedin_profile
# Load environment variables from .env file
load_dotenv()
assistant_id = os.getenv("ASSISTANT_ID")
# Initialize Streamlit session state variables
if "start_chat" not in st.session_state:
st.session_state.start_chat = False
if "thread_id" not in st.session_state:
st.session_state.thread_id = None
if "openai_api_key" not in st.session_state:
st.session_state.openai_api_key = ""
# Add a new session state variable for analysis completion tracking
if "analysis_completed" not in st.session_state:
st.session_state.analysis_completed = False
# Configure the Streamlit page
st.set_page_config(page_title="ReviewIn", page_icon=":computer:")
with st.sidebar:
st.markdown("<h1 style='display: flex; align-items: center;'>ReviewIn <img src='https://www.pagetraffic.com/blog/wp-content/uploads/2022/09/linkedin-blue-logo-icon.png' alt='LinkedIn Logo' width='40' height='40'></h1>", unsafe_allow_html=True)
#bullet points
with st.expander("🚀 Features and Tips"):
st.markdown("""
- 🧐 **LinkedIn Profile Review:** Personalized, actionable advice for enhancing your profile.
- 📚 **Specialized Knowledge:** Draws on a custom knowledge base tailored for LinkedIn profile improvements.
- 💡 **Powered by OpenAI:** Leverages the Assistant's API with GPT-4 Turbo for analysis and conversation.
- 🖼 **Vision Insights:** GPT-4 Vision for detailed feedback on profile pictures.
- 🔥 **Tip:** For better analysis, set your profile to public. The more complete and public your profile, the better our insights.
- ⚠️ **Note:** Assistants API is in beta and doesn't yet support streaming - full response is generated before being sent. Your patience is appreciated! 🙂
""", unsafe_allow_html=True)
st.session_state['openai_api_key'] = st.text_input("🔑 OpenAI API Key:", type="password")
with st.expander("🎯 Job Preferences & Context (optional)"):
job_preferences = st.text_area("Got any specific job-seeking goals? Let us know here!",
placeholder="e.g., 'Software Engineer, entry-level, Tech industry and interested in AI.'")
profile_url = st.text_input("🌐 Enter LinkedIn Profile URL:")
# Check if both OpenAI API Key and LinkedIn Profile URL are provided
if st.session_state['openai_api_key'] and profile_url:
if st.button("Analyze"):
st.session_state.start_chat = True
openai.api_key = st.session_state.openai_api_key
thread = openai.beta.threads.create()
st.session_state.thread_id = thread.id
st.session_state.analysis_requested = True
else:
# Optionally, display a message prompting the user to fill in all required fields
st.warning("Friendly reminder - add your OpenAI API Key and LinkedIn Profile URL to kick things off!😎")
# Initialize or reset session state on page load
if 'init' not in st.session_state:
st.session_state['init'] = True
st.session_state.start_chat = False
st.session_state.messages = []
st.session_state.thread_id = None
st.session_state.analysis_requested = False
def handle_custom_function(run, job_preferences=""):
if run.status == 'requires_action' and run.required_action.type == 'submit_tool_outputs':
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
if tool_call.function.name == "analyze_profile_picture":
image_url = json.loads(tool_call.function.arguments)["image_url"]
print(f"Analyzing image at URL: {image_url}")
additional_context = ""
if job_preferences:
additional_context += f"\n\n**ADDITIONAL** - If relevant for your analysis, please consider the following context about the user's job preferences: {job_preferences}"
# Call GPT-4 Vision API to analyze the image
vision_response = openai.chat.completions.create(
model='gpt-4-vision-preview',
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": (
"Analyze this LinkedIn profile picture. Provide an analysis focusing on its "
"appropriateness and effectiveness for a LinkedIn profile. Consider the following aspects:\n\n"
"1. Presentation: Evaluate the subject's attire and grooming. Does it align with professional standards suitable for "
"their industry or field?\n"
"2. Expression and Body Language: Assess the subject's facial expression and body language. Does it project confidence, "
"approachability, and professionalism?\n"
"3. Composition and Setting: Comment on the composition of the photograph, including the background. Is it distraction-free "
"and does it enhance the subject's professional image?\n"
"4. Quality and Lighting: Evaluate the quality of the photograph, including lighting and clarity. Does the image quality "
"uphold professional standards?\n\n"
"Provide recommendations for improvement if necessary, highlighting aspects that could enhance the subject's professional "
"portrayal on LinkedIn. {additional_context}"
)
},
{
"type": "image_url",
"image_url": {
"url": image_url,
},
},
],
}
],
max_tokens=400
)
vision_content = vision_response.choices[0].message.content
print(f"Vision API response: {vision_content}")
# Submit the output back to the Assistant
openai.beta.threads.runs.submit_tool_outputs(
thread_id=run.thread_id,
run_id=run.id,
tool_outputs=[
{
'tool_call_id': tool_call.id,
'output': vision_response.choices[0].message.content
}
]
)
print(f"Submitted vision analysis back to the assistant: {vision_content}")
# Main interaction logic
if st.session_state.start_chat:
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if st.session_state.analysis_requested:
with st.spinner('⏳🔍 Crunching the numbers - going to take a sec chief!😊'):
# Process the LinkedIn profile URL
formatted_text, image_url = scrape_linkedin_profile(profile_url)
time.sleep(3)
if formatted_text:
# Instructions for analysis
instructions = """
Provide an analysis/report of my LinkedIn profile below. Approach this task with professionalism and friendliness, ensure your recommendations is are both helpful and actionable. Provide detailed feedback for improvement. You can follow this structure:
1. **Profile Picture**: Begin with the profile picture. Assess its alignment. Suggest specific changes to enhance the first impression it makes, if needed.
2. **Headline and Summary**: Evaluate the clarity and impact of the headline and summary. How well do they communicate the individual's professional narrative and unique value proposition? Provide actionable advice to refine these elements, enhancing their appeal and coherence.
3. **Work Experience and Skills**: Delve into the work experience and skills sections. Identify the strengths and pinpoint areas that can benefit from greater detail or stronger examples of achievements. Recommend strategies to showcase expertise/skills more effectively.
4. **Educational Background and Volunteer Experience**: Analyze the education section and provide recommendations, if needed. Do the same for the Volunteer experience, if present. Advise on optimizing these areas to support the professional identity and narrative.
Each section should receive an assessment that contributes to an overall profile rating. Conclude with:
5. **Overall Quality Evaluation and Potential**: Rate the profile's current state out of 100, based on the coherence, presentation, and effectiveness of all sections combined - be as objective as you can, refrain from giving overly high ratings, unless the profile really is amazing, you can be critical, but still friendly. Then, estimate the potential score increase achievable by implementing your recommendations. Highlight the transformative impact of suggested changes, not just incrementally but in terms of elevating the profile's professional stature and networking potential.
Remember, your analysis should be comprehensive and nuanced, leveraging your expertise and any relevant external information from the files, where relevant. Address me directly and use the first person for a personal touch Let's evaluate this LinkedIn profile:
"""
# Prepare the analysis request content
analysis_request = f"{instructions}\n\n**HERE IS THE CONTENT FOR ANALYSIS**:\n- **Profile Text**: {formatted_text}\n"
if image_url: # Conditionally include image URL if available
analysis_request += f"- **Profile Image URL**: {image_url}"
# Add job preferences to the analysis request if any
if job_preferences:
analysis_request += f"\n\n**ADDITIONAL** - If relevant, please incorporate the following context about the job preferences of the user to tailor the recommendations: {job_preferences}"
print(analysis_request)
openai.beta.threads.messages.create(
thread_id=st.session_state.thread_id,
role="user",
content=analysis_request
)
# Wait for the analysis to complete
run = openai.beta.threads.runs.create(
thread_id=st.session_state.thread_id,
assistant_id=assistant_id,
instructions="Address me directly and use first person for a personal touch. Be helpful and approachable."
)
while run.status != 'completed':
time.sleep(1)
run = openai.beta.threads.runs.retrieve(thread_id=st.session_state.thread_id, run_id=run.id)
if run.status == 'requires_action':
print("Function Calling")
handle_custom_function(run)
# Fetch and display the analysis results
messages = openai.beta.threads.messages.list(
thread_id=st.session_state.thread_id
)
# Filter and display messages for the current run
for message in messages.data:
if message.run_id == run.id and message.role == "assistant":
st.session_state.messages.append({"role": "assistant", "content": message.content[0].text.value})
with st.chat_message("assistant"):
st.markdown(message.content[0].text.value)
# Mark the analysis as completed and ready for user follow-up
st.session_state.analysis_requested = False
st.session_state.analysis_completed = True
# After completing the initial analysis, enable follow-up conversations
if st.session_state.analysis_completed:
user_input = st.chat_input("Ask me anything about improving your LinkedIn profile!")
if user_input: # If there's user input, process it
# Append user input to messages for display
st.session_state.messages.append({"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)
# Send the user input to OpenAI API as a new message in the thread
openai.beta.threads.messages.create(
thread_id=st.session_state.thread_id,
role="user",
content=user_input
)
# Wait for response
run = openai.beta.threads.runs.create(
thread_id=st.session_state.thread_id,
assistant_id=assistant_id,
instructions="Be helpful and approachable."
)
while run.status != 'completed':
time.sleep(1)
run = openai.beta.threads.runs.retrieve(thread_id=st.session_state.thread_id, run_id=run.id)
# Fetch and display the analysis results
messages = openai.beta.threads.messages.list(
thread_id=st.session_state.thread_id
)
# Filter and display messages for the current run
for message in messages.data:
if message.run_id == run.id and message.role == "assistant":
st.session_state.messages.append({"role": "assistant", "content": message.content[0].text.value})
with st.chat_message("assistant"):
st.markdown(message.content[0].text.value)
# Display if chat not yet started
if not st.session_state.start_chat:
st.markdown(
"""
<h2 style='text-align: center;'>
🚀 Ready to enhance your LinkedIn profile? <br>
Drop your <img src='https://upcdn.io/FW25bBB/image/content/app_logos/485244ee-9158-4685-8f1e-d349e97b35e1.png?f=webp&w=1920&q=85&fit=shrink-cover' alt='LinkedIn Logo' width='45' height='45' style='border-radius: 15%'> API key and <img src='https://www.pagetraffic.com/blog/wp-content/uploads/2022/09/linkedin-blue-logo-icon.png' alt='LinkedIn Logo' width='40' height='40'> profile URL in the sidebar and let's dive in!
</h2>
""",
unsafe_allow_html=True
)
st.image("dalle.png", use_column_width=True)
这就是最终效果:
扩展阅读
- LangGPT 社区:www.langgpt.ai/
- 数据摸鱼wx订阅号