claridon commited on
Commit
d1b7767
Β·
verified Β·
1 Parent(s): 5db9875

Upload folder using huggingface_hub

Browse files
Files changed (11) hide show
  1. .gitignore +126 -0
  2. LICENSE +21 -0
  3. README.md +106 -7
  4. app.py +134 -0
  5. env-example.txt +8 -0
  6. me/cedric-linkedin.pdf +0 -0
  7. me/summary.txt +2 -0
  8. pyproject.toml +20 -0
  9. requirements.txt +5 -0
  10. setup_check.py +102 -0
  11. uv.lock +0 -0
.gitignore ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables
2
+ .env
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+ .Python
10
+ build/
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ share/python-wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # PyInstaller
29
+ *.manifest
30
+ *.spec
31
+
32
+ # Installer logs
33
+ pip-log.txt
34
+ pip-delete-this-directory.txt
35
+
36
+ # Unit test / coverage reports
37
+ htmlcov/
38
+ .tox/
39
+ .nox/
40
+ .coverage
41
+ .coverage.*
42
+ .cache
43
+ nosetests.xml
44
+ coverage.xml
45
+ *.cover
46
+ *.py,cover
47
+ .hypothesis/
48
+ .pytest_cache/
49
+ cover/
50
+
51
+ # Jupyter Notebook
52
+ .ipynb_checkpoints
53
+
54
+ # IPython
55
+ profile_default/
56
+ ipython_config.py
57
+
58
+ # pyenv
59
+ .python-version
60
+
61
+ # pipenv
62
+ Pipfile.lock
63
+
64
+ # poetry
65
+ poetry.lock
66
+
67
+ # pdm
68
+ .pdm.toml
69
+
70
+ # PEP 582
71
+ __pypackages__/
72
+
73
+ # Celery stuff
74
+ celerybeat-schedule
75
+ celerybeat.pid
76
+
77
+ # SageMath parsed files
78
+ *.sage.py
79
+
80
+ # Environments
81
+ .venv
82
+ env/
83
+ venv/
84
+ ENV/
85
+ env.bak/
86
+ venv.bak/
87
+
88
+ # Spyder project settings
89
+ .spyderproject
90
+ .spyproject
91
+
92
+ # Rope project settings
93
+ .ropeproject
94
+
95
+ # mkdocs documentation
96
+ /site
97
+
98
+ # mypy
99
+ .mypy_cache/
100
+ .dmypy.json
101
+ dmypy.json
102
+
103
+ # Pyre type checker
104
+ .pyre/
105
+
106
+ # pytype static type analyzer
107
+ .pytype/
108
+
109
+ # Cython debug symbols
110
+ cython_debug/
111
+
112
+ # IDE
113
+ .vscode/
114
+ .idea/
115
+ *.swp
116
+ *.swo
117
+ *~
118
+
119
+ # OS
120
+ .DS_Store
121
+ .DS_Store?
122
+ ._*
123
+ .Spotlight-V100
124
+ .Trashes
125
+ ehthumbs.db
126
+ Thumbs.db
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cedric Laridon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,111 @@
1
  ---
2
- title: Cedric Agent
3
- emoji: πŸ’»
4
- colorFrom: indigo
5
- colorTo: yellow
6
  sdk: gradio
7
  sdk_version: 5.39.0
8
- app_file: app.py
9
- pinned: false
10
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: cedric-agent
3
+ app_file: app.py
 
 
4
  sdk: gradio
5
  sdk_version: 5.39.0
 
 
6
  ---
7
+ # Cedric Agent - Personal AI Chatbot
8
+
9
+ A personal AI chatbot that represents Cedric Laridon on his website, answering questions about his career, background, skills, and experience.
10
+
11
+ ## Features
12
+
13
+ - **Personal AI Assistant**: Acts as Cedric Laridon, answering questions about his professional background
14
+ - **LinkedIn Integration**: Uses Cedric's LinkedIn profile data to provide accurate responses
15
+ - **Contact Recording**: Captures visitor contact information and unknown questions via Pushover notifications
16
+ - **Web Interface**: Clean Gradio interface for easy interaction
17
+ - **Professional Engagement**: Designed to engage potential clients and employers
18
+
19
+ ## Setup
20
+
21
+ ### Prerequisites
22
+
23
+ - Python 3.12 or higher
24
+ - [uv](https://docs.astral.sh/uv/) (recommended) or pip
25
+ - OpenAI API key
26
+ - Pushover account (optional, for notifications)
27
+
28
+ ### Installation
29
+
30
+ 1. Clone the repository:
31
+ ```bash
32
+ git clone https://github.com/cedriclaridon/cedric-agent.git
33
+ cd cedric-agent
34
+ ```
35
+
36
+ 2. Install dependencies using uv (recommended):
37
+ ```bash
38
+ uv sync
39
+ ```
40
+
41
+ Or using pip:
42
+ ```bash
43
+ pip install -r requirements.txt
44
+ ```
45
+
46
+ 3. Create a `.env` file in the root directory with your API keys:
47
+ ```env
48
+ OPENAI_API_KEY=your_openai_api_key_here
49
+ PUSHOVER_TOKEN=your_pushover_token_here
50
+ PUSHOVER_USER=your_pushover_user_key_here
51
+ ```
52
+
53
+ ### Running the Application
54
+
55
+ Using uv (recommended):
56
+ ```bash
57
+ uv run python app.py
58
+ ```
59
+
60
+ Or using python directly:
61
+ ```bash
62
+ python app.py
63
+ ```
64
+
65
+ The application will launch a Gradio interface, typically accessible at `http://localhost:7860`.
66
+
67
+ ## Configuration
68
+
69
+ ### Environment Variables
70
+
71
+ - `OPENAI_API_KEY`: Required. Your OpenAI API key for GPT-4o-mini
72
+ - `PUSHOVER_TOKEN`: Optional. Pushover application token for notifications
73
+ - `PUSHOVER_USER`: Optional. Pushover user key for notifications
74
+
75
+ ### Personal Data
76
+
77
+ The chatbot uses two main sources of personal information:
78
+
79
+ 1. **`me/summary.txt`**: A brief personal summary
80
+ 2. **`me/cedric-linkedin.pdf`**: LinkedIn profile export in PDF format
81
+
82
+ Update these files with your own information to customize the chatbot for your use.
83
+
84
+ ## How It Works
85
+
86
+ 1. **System Prompt**: The chatbot is given a comprehensive system prompt that includes personal summary and LinkedIn data
87
+ 2. **Function Calling**: Uses OpenAI's function calling to record user details and unknown questions
88
+ 3. **Notifications**: Sends real-time notifications via Pushover when users provide contact info or ask unknown questions
89
+ 4. **Conversation Flow**: Designed to engage users and encourage them to provide contact information
90
+
91
+ ## Customization
92
+
93
+ To adapt this chatbot for your own use:
94
+
95
+ 1. Replace `me/summary.txt` with your personal summary
96
+ 2. Replace `me/cedric-linkedin.pdf` with your LinkedIn profile PDF
97
+ 3. Update the name in `app.py` (line 80: `self.name = "Your Name"`)
98
+ 4. Modify the system prompt in the `system_prompt()` method if needed
99
+ 5. Update the contact information in this README
100
+
101
+ ## License
102
+
103
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
104
+
105
+ ## Contributing
106
+
107
+ This is a personal project, but feel free to fork it and adapt it for your own use. If you have suggestions for improvements, please open an issue.
108
+
109
+ ## Contact
110
 
111
+ For questions about this project, please contact Cedric Laridon through the chatbot interface or via the contact methods provided on his website.
app.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ from openai import OpenAI
3
+ import json
4
+ import os
5
+ import requests
6
+ from pypdf import PdfReader
7
+ import gradio as gr
8
+
9
+
10
+ load_dotenv(override=True)
11
+
12
+ def push(text):
13
+ requests.post(
14
+ "https://api.pushover.net/1/messages.json",
15
+ data={
16
+ "token": os.getenv("PUSHOVER_TOKEN"),
17
+ "user": os.getenv("PUSHOVER_USER"),
18
+ "message": text,
19
+ }
20
+ )
21
+
22
+
23
+ def record_user_details(email, name="Name not provided", notes="not provided"):
24
+ push(f"Recording {name} with email {email} and notes {notes}")
25
+ return {"recorded": "ok"}
26
+
27
+ def record_unknown_question(question):
28
+ push(f"Recording {question}")
29
+ return {"recorded": "ok"}
30
+
31
+ record_user_details_json = {
32
+ "name": "record_user_details",
33
+ "description": "Use this tool to record that a user is interested in being in touch and provided an email address",
34
+ "parameters": {
35
+ "type": "object",
36
+ "properties": {
37
+ "email": {
38
+ "type": "string",
39
+ "description": "The email address of this user"
40
+ },
41
+ "name": {
42
+ "type": "string",
43
+ "description": "The user's name, if they provided it"
44
+ }
45
+ ,
46
+ "notes": {
47
+ "type": "string",
48
+ "description": "Any additional information about the conversation that's worth recording to give context"
49
+ }
50
+ },
51
+ "required": ["email"],
52
+ "additionalProperties": False
53
+ }
54
+ }
55
+
56
+ record_unknown_question_json = {
57
+ "name": "record_unknown_question",
58
+ "description": "Always use this tool to record any question that couldn't be answered as you didn't know the answer",
59
+ "parameters": {
60
+ "type": "object",
61
+ "properties": {
62
+ "question": {
63
+ "type": "string",
64
+ "description": "The question that couldn't be answered"
65
+ },
66
+ },
67
+ "required": ["question"],
68
+ "additionalProperties": False
69
+ }
70
+ }
71
+
72
+ tools = [{"type": "function", "function": record_user_details_json},
73
+ {"type": "function", "function": record_unknown_question_json}]
74
+
75
+
76
+ class Me:
77
+
78
+ def __init__(self):
79
+ self.openai = OpenAI()
80
+ self.name = "Cedric Laridon"
81
+ reader = PdfReader("me/cedric-linkedin.pdf")
82
+ self.linkedin = ""
83
+ for page in reader.pages:
84
+ text = page.extract_text()
85
+ if text:
86
+ self.linkedin += text
87
+ with open("me/summary.txt", "r", encoding="utf-8") as f:
88
+ self.summary = f.read()
89
+
90
+
91
+ def handle_tool_call(self, tool_calls):
92
+ results = []
93
+ for tool_call in tool_calls:
94
+ tool_name = tool_call.function.name
95
+ arguments = json.loads(tool_call.function.arguments)
96
+ print(f"Tool called: {tool_name}", flush=True)
97
+ tool = globals().get(tool_name)
98
+ result = tool(**arguments) if tool else {}
99
+ results.append({"role": "tool","content": json.dumps(result),"tool_call_id": tool_call.id})
100
+ return results
101
+
102
+ def system_prompt(self):
103
+ system_prompt = f"You are acting as {self.name}. You are answering questions on {self.name}'s website, \
104
+ particularly questions related to {self.name}'s career, background, skills and experience. \
105
+ Your responsibility is to represent {self.name} for interactions on the website as faithfully as possible. \
106
+ You are given a summary of {self.name}'s background and LinkedIn profile which you can use to answer questions. \
107
+ Be professional and engaging, as if talking to a potential client or future employer who came across the website. \
108
+ If you don't know the answer to any question, use your record_unknown_question tool to record the question that you couldn't answer, even if it's about something trivial or unrelated to career. \
109
+ If the user is engaging in discussion, try to steer them towards getting in touch via email; ask for their email and record it using your record_user_details tool. "
110
+
111
+ system_prompt += f"\n\n## Summary:\n{self.summary}\n\n## LinkedIn Profile:\n{self.linkedin}\n\n"
112
+ system_prompt += f"With this context, please chat with the user, always staying in character as {self.name}."
113
+ return system_prompt
114
+
115
+ def chat(self, message, history):
116
+ messages = [{"role": "system", "content": self.system_prompt()}] + history + [{"role": "user", "content": message}]
117
+ done = False
118
+ while not done:
119
+ response = self.openai.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=tools)
120
+ if response.choices[0].finish_reason=="tool_calls":
121
+ message = response.choices[0].message
122
+ tool_calls = message.tool_calls
123
+ results = self.handle_tool_call(tool_calls)
124
+ messages.append(message)
125
+ messages.extend(results)
126
+ else:
127
+ done = True
128
+ return response.choices[0].message.content
129
+
130
+
131
+ if __name__ == "__main__":
132
+ me = Me()
133
+ gr.ChatInterface(me.chat, type="messages").launch()
134
+
env-example.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Copy this file to .env and fill in your actual values
2
+
3
+ # OpenAI API Configuration (Required)
4
+ OPENAI_API_KEY=your_openai_api_key_here
5
+
6
+ # Pushover Notifications (Optional)
7
+ PUSHOVER_TOKEN=your_pushover_token_here
8
+ PUSHOVER_USER=your_pushover_user_key_here
me/cedric-linkedin.pdf ADDED
Binary file (59.5 kB). View file
 
me/summary.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ My name is Cedric Laridon. I'm a Belgian wealth-tech executive.
2
+ I’m a passionate pianist and composer, often immersing myself in Chopin, Bach, and my own classical and cinematic pieces. I have a deep love for culture, fine wine, and authentic dining experiences, and I enjoy traveling with my family to discover vibrant cities, music, and culinary scenes. I live by the idea of savoring lifeβ€”whether that’s through music, food, or exploring new placesβ€”always seeking meaningful, authentic experiences over the mainstream.
pyproject.toml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "cedric-agent"
3
+ version = "1.0.0"
4
+ description = "Personal AI chatbot for Cedric Laridon's website"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ authors = [
8
+ {name = "Cedric Laridon", email = "cedric@example.com"}
9
+ ]
10
+ license = {text = "MIT"}
11
+ dependencies = [
12
+ "openai>=1.68.2",
13
+ "gradio>=5.22.0",
14
+ "python-dotenv>=1.0.1",
15
+ "pypdf>=5.4.0",
16
+ "requests>=2.32.3",
17
+ ]
18
+
19
+ [tool.uv]
20
+ dev-dependencies = []
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ openai>=1.68.2
2
+ gradio>=5.22.0
3
+ python-dotenv>=1.0.1
4
+ pypdf>=5.4.0
5
+ requests>=2.32.3
setup_check.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Setup verification script for Cedric Agent
4
+ Checks if all dependencies are available and files are in place
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import importlib
10
+
11
+ def check_file_exists(filepath, description):
12
+ """Check if a file exists and print status"""
13
+ if os.path.exists(filepath):
14
+ print(f"βœ… {description}: {filepath}")
15
+ return True
16
+ else:
17
+ print(f"❌ {description}: {filepath} - MISSING")
18
+ return False
19
+
20
+ def check_module(module_name, description):
21
+ """Check if a Python module can be imported"""
22
+ try:
23
+ importlib.import_module(module_name)
24
+ print(f"βœ… {description}: {module_name}")
25
+ return True
26
+ except ImportError:
27
+ print(f"❌ {description}: {module_name} - NOT INSTALLED")
28
+ return False
29
+
30
+ def main():
31
+ print("πŸ” Cedric Agent Setup Verification")
32
+ print("=" * 40)
33
+
34
+ all_good = True
35
+
36
+ # Check required files
37
+ print("\nπŸ“ Checking files...")
38
+ files_to_check = [
39
+ ("app.py", "Main application"),
40
+ ("requirements.txt", "Dependencies file"),
41
+ ("pyproject.toml", "Project configuration"),
42
+ ("README.md", "Documentation"),
43
+ ("LICENSE", "License file"),
44
+ (".gitignore", "Git ignore file"),
45
+ ("me/summary.txt", "Personal summary"),
46
+ ("me/cedric-linkedin.pdf", "LinkedIn profile"),
47
+ ]
48
+
49
+ for filepath, description in files_to_check:
50
+ if not check_file_exists(filepath, description):
51
+ all_good = False
52
+
53
+ # Check Python modules
54
+ print("\nπŸ“¦ Checking Python dependencies...")
55
+ modules_to_check = [
56
+ ("openai", "OpenAI library"),
57
+ ("gradio", "Gradio web interface"),
58
+ ("dotenv", "Environment variables"),
59
+ ("pypdf", "PDF processing"),
60
+ ("requests", "HTTP requests"),
61
+ ]
62
+
63
+ for module_name, description in modules_to_check:
64
+ if not check_module(module_name, description):
65
+ all_good = False
66
+
67
+ # Check environment variables
68
+ print("\nπŸ”‘ Checking environment variables...")
69
+ env_vars_to_check = [
70
+ ("OPENAI_API_KEY", "OpenAI API key", True),
71
+ ("PUSHOVER_TOKEN", "Pushover token", False),
72
+ ("PUSHOVER_USER", "Pushover user key", False),
73
+ ]
74
+
75
+ for var_name, description, required in env_vars_to_check:
76
+ if os.getenv(var_name):
77
+ print(f"βœ… {description}: Set")
78
+ else:
79
+ if required:
80
+ print(f"❌ {description}: NOT SET (Required)")
81
+ all_good = False
82
+ else:
83
+ print(f"⚠️ {description}: NOT SET (Optional)")
84
+
85
+ # Final status
86
+ print("\n" + "=" * 40)
87
+ if all_good:
88
+ print("πŸŽ‰ Setup verification completed successfully!")
89
+ print("You can now run: python app.py")
90
+ else:
91
+ print("❌ Setup verification failed!")
92
+ print("Please fix the issues above before running the application.")
93
+
94
+ print("\nπŸ’‘ Quick fixes:")
95
+ print("- Install missing dependencies: uv sync (or pip install -r requirements.txt)")
96
+ print("- Create .env file with your OpenAI API key")
97
+ print("- Make sure all files are in the correct location")
98
+
99
+ return 0 if all_good else 1
100
+
101
+ if __name__ == "__main__":
102
+ sys.exit(main())
uv.lock ADDED
The diff for this file is too large to render. See raw diff