Spaces:
Sleeping
Sleeping
| """ | |
| LLM Tool Call Demo App | |
| ---------------------- | |
| - Sends mobile notifications via Pushover | |
| - Records user details and unknown questions | |
| - Reads profile/summary from local files | |
| - Simulates tool call dispatching (e.g., from OpenAI function calling) | |
| - Exposes a Gradio Chat Interface for conversation | |
| """ | |
| import os | |
| import json | |
| import requests | |
| from pathlib import Path | |
| from dotenv import load_dotenv | |
| from types import SimpleNamespace | |
| from pypdf import PdfReader | |
| from openai import OpenAI | |
| import gradio as gr | |
| # --- Load Environment Variables --- | |
| load_dotenv(override=True) | |
| # --- Mobile Notification Setup --- | |
| PUSH_NOTIFICATION_URI = "https://api.pushover.net/1/messages.json" | |
| pushover_user = os.getenv("PUSHOVER_USER") | |
| pushover_token = os.getenv("PUSHOVER_TOKEN") | |
| def push_notification(message: str): | |
| data = { | |
| "token": pushover_token, | |
| "user": pushover_user, | |
| "message": message | |
| } | |
| response = requests.post(PUSH_NOTIFICATION_URI, data) | |
| if response.status_code == 200: | |
| return "Notification sent!" | |
| return f"Failed to send: {response.text}" | |
| # --- Tool Functions --- | |
| def record_user_details(email, name="Name not provided", notes="not provided"): | |
| push_notification(f"[User Interest] {name} ({email}) | Notes: {notes}") | |
| return {"recorded": "ok"} | |
| def record_unknown_question(question): | |
| push_notification(f"[Unknown Question] {question}") | |
| return {"recorded": "ok"} | |
| # --- Tool Schemas --- | |
| record_user_details_json = { | |
| "name": "record_user_details", | |
| "description": "Record a user's interest using their email and optional details.", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "email": {"type": "string", "description": "User's email address"}, | |
| "name": {"type": "string", "description": "User's name"}, | |
| "notes": {"type": "string", "description": "Additional context or comments"} | |
| }, | |
| "required": ["email"], | |
| "additionalProperties": False | |
| } | |
| } | |
| record_unknown_question_json = { | |
| "name": "record_unknown_question", | |
| "description": "Log a question that the assistant couldn't answer.", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "question": {"type": "string", "description": "The unanswerable question"} | |
| }, | |
| "required": ["question"], | |
| "additionalProperties": False | |
| } | |
| } | |
| # --- Tool Dispatcher --- | |
| TOOL_FUNCTIONS = { | |
| "record_user_details": record_user_details, | |
| "record_unknown_question": record_unknown_question, | |
| } | |
| def dispatch_tool_calls(tool_calls): | |
| results = [] | |
| for call in tool_calls: | |
| name = call.function.name | |
| args = json.loads(call.function.arguments) | |
| print(f"Tool called: {name}") | |
| func = TOOL_FUNCTIONS.get(name) | |
| if func: | |
| try: | |
| result = func(**args) | |
| except Exception as e: | |
| result = {"error": f"Execution failed: {str(e)}"} | |
| else: | |
| result = {"error": f"Unknown tool: {name}"} | |
| results.append({ | |
| "role": "tool", | |
| "content": json.dumps(result), | |
| "name": name, | |
| "tool_call_id": call.id | |
| }) | |
| return results | |
| # --- Load Profile and Summary Data --- | |
| project_root = Path.cwd().parent | |
| profile_path = "Profile-1.pdf" | |
| summary_path = "Summary.txt" | |
| prof_summary = "".join( | |
| page.extract_text() or "" for page in PdfReader(profile_path).pages | |
| ) | |
| with open(summary_path, "r", encoding="utf-8") as f: | |
| summary = f.read() | |
| # --- System Prompt --- | |
| name = "Dhanush Saravanan" | |
| system_prompt = ( | |
| f"You are acting as {name}, representing {name} on their website. " | |
| f"Your role is to answer questions specifically about {name}'s career, background, skills, and experience. " | |
| f"You must faithfully and accurately portray {name} in all interactions. " | |
| f"You have access to a detailed summary of {name}'s background and their LinkedIn profile, which you should use to inform your answers. " | |
| f"Maintain a professional, engaging, and approachable tone. " | |
| f"Always record any unanswered questions using the record_unknown_question tool. " | |
| f"If the user continues chatting, encourage them to share their email address, then use the record_user_details tool." | |
| f"\n\n## Summary:\n{summary}\n\n## LinkedIn Profile:\n{prof_summary}\n" | |
| ) | |
| # --- OpenAI Clients --- | |
| gemini_api_key = os.getenv('GEMINKEY_API_KEY') | |
| gemini_base_url = "https://generativelanguage.googleapis.com/v1beta/openai/" | |
| gemini_client = OpenAI(api_key=gemini_api_key, base_url=gemini_base_url) | |
| openai_api_key = os.getenv('API_TOKEN') | |
| deepseek_base_url = "https://api.deepseek.com" | |
| openai_client = OpenAI(api_key=openai_api_key, base_url=deepseek_base_url) | |
| # --- Conversation Handler --- | |
| tools = [ | |
| {"type": "function", "function": record_user_details_json}, | |
| {"type": "function", "function": record_unknown_question_json} | |
| ] | |
| def run_conversation(message, history): | |
| messages = [{"role": "system", "content": system_prompt}] + history + [{"role": "user", "content": message}] | |
| finishLoop = False | |
| message_obj = None | |
| while not finishLoop: | |
| response = openai_client.chat.completions.create( | |
| model="deepseek-chat", | |
| messages=messages, | |
| tools=tools, | |
| tool_choice="auto" | |
| ) | |
| message_obj = response.choices[0].message.content | |
| finish_reason = response.choices[0].finish_reason | |
| print(f"Finish Reason : {finish_reason}") | |
| if finish_reason == "tool_calls": | |
| tool_calls = response.choices[0].message.tool_calls | |
| messages.append(message_obj) | |
| tool_result = dispatch_tool_calls(tool_calls) | |
| messages.extend(tool_result) | |
| finishLoop = True | |
| else: | |
| finishLoop = True | |
| return message_obj | |
| # --- Gradio UI --- | |
| gr.ChatInterface(run_conversation).launch(share=True) |