From d1ed8b9e3f1661a865008552accf12d8ce614767 Mon Sep 17 00:00:00 2001 From: OwusuBlessing Date: Wed, 12 Feb 2025 00:12:02 +0100 Subject: [PATCH] backedn chat apis and uplaod apis integrated --- app.py | 57 +++++++++++----- src/llm.py | 174 ++++++++++++++++++------------------------------- src/prompts.py | 5 +- test.py | 27 ++++++-- 4 files changed, 126 insertions(+), 137 deletions(-) diff --git a/app.py b/app.py index 0f5617d..b6ff188 100644 --- a/app.py +++ b/app.py @@ -78,7 +78,7 @@ async def get_api_key(api_key_header: str = Security(api_key_header)) -> str: class ChatRequest(BaseModel): resume_url: Optional[str] = None query: str=None - conversation_id: str + chat_id: int theme_id: Optional[int] = 1 full_history_url: Optional[str] = None form_id:Optional[int] = None @@ -95,7 +95,7 @@ class ChatResponse(BaseModel): class GeneratePDFRequest(BaseModel): resume_url: Optional[str] = None - conversation_id: str + chat_id: int theme_id: Optional[int] = 1 full_history_url: Optional[str] = None form_id:Optional[int] = None @@ -215,7 +215,7 @@ async def chat_endpoint( query = "Let's get started" response = ai_chat( query=query, - conversation_id=request.conversation_id, + conversation_id=request.chat_id, theme_id=request.theme_id, resume=resume_docs, full_history=full_history_docs, @@ -304,36 +304,57 @@ async def generate_pdf_endpoint( # Here you would fetch the conversation data using the conversation_id # This is a placeholder - replace with your actual conversation data fetching logic # Get AI-generated theme content + # Get AI-generated theme content response = ai_chat( query="NOW GENERATE THE STARTPOP FRAMEWORK", - conversation_id=request.conversation_id, + conversation_id=request.chat_id, theme_id=request.theme_id, resume=resume_docs, full_history=full_history_docs, form_response=form_response_docs, generate_theme="YES" ) - print(f"AI Response for theme: {response}") - + # Ensure AI response is valid if not isinstance(response, str): raise HTTPException(status_code=500, detail="Invalid AI response format") # Generate PDF - response = json.loads(response) - pdf_content = create_pdf(response) + response_data = json.loads(response) + pdf_content = create_pdf(response_data) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"theme_{timestamp}.pdf" - - print(f"Returning PDF with filename: {filename}") - - return Response( - content=pdf_content, - media_type="application/pdf", - headers={"Content-Disposition": f'attachment; filename="{filename}"'} - ) - + file_path = f"theme_{timestamp}.pdf" + + # Save the PDF locally temporarily + with open(file_path, "wb") as file: + file.write(pdf_content) + + # Upload the PDF to S3 using the API + upload_url = f"{os.getenv('BACKEND_BASE_URL')}/v3/api/custom/theme/doc-upload?x-project={x_api_key}" + with open(file_path, 'rb') as file: + files = {'file': file} + upload_response = requests.post(upload_url, files=files) + + # Check if the upload was successful + if upload_response.status_code != 200: + raise HTTPException(status_code=upload_response.status_code, detail="File upload to S3 failed: " + upload_response.text) + + upload_data = upload_response.json() # Get the response in JSON format + + # Extract the uploaded file URL + theme_url = upload_data.get("url") # Adjust this key based on the actual API response structure + if not theme_url: + raise HTTPException(status_code=500, detail="Failed to retrieve theme URL from upload response") + + # Clean up the temporary file + os.remove(file_path) + + # Return JSON response with theme URL and text + return { + "theme_url": theme_url, + "theme_text": response_data + } except Exception as e: print(f"Error generating theme: {str(e)}") raise HTTPException(status_code=500, detail=f"Error: {str(e)}") diff --git a/src/llm.py b/src/llm.py index 9110b93..736b66b 100644 --- a/src/llm.py +++ b/src/llm.py @@ -7,16 +7,28 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import HumanMessage, AIMessage, BaseMessage from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import START, MessagesState, StateGraph -from utils.utils import format_questions_text from src.prompts import chat_prompt from langchain_openai import ChatOpenAI from src.models import Phase2Generation,Phase1Generation from config import TEMPERATURE +import os +import requests +import json +from dotenv import load_dotenv +from typing import List, Dict, Optional +from dataclasses import dataclass +from datetime import datetime +from langchain_core.messages import HumanMessage, AIMessage + +# Load environment variables +load_dotenv() + + @dataclass class Message: role: str # 'human' or 'ai' content: str - timestamp: str + timestamp: str=None QUESTIONS_PATH = "./data/config_files/questions.json" with open(QUESTIONS_PATH, "r") as f: @@ -46,63 +58,31 @@ def setup_prompt_template(theme: int, resume: str,full_history=None,form_respons ("system", chat_prompt(theme, resume,full_history,form_response,generate_theme)), MessagesPlaceholder(variable_name="messages") ]) - -def parse_ai_response(content: str) -> Dict: - """Parse AI response content into expected format""" +def fetch_conversation_history(conversation_id: str) -> List[Message]: + """ + Fetch conversation history from the API using the conversation ID. + """ + x_api_key = os.getenv("BACKEND_XAPI_KEY") + base_url = os.getenv("BACKEND_BASE_URL") + url = f"{base_url}/v3/api/custom/jordan/ai-chat/get-messages/{conversation_id}?x-project={x_api_key}" + try: - response = json.loads(content) - return { - "message": response.get("message", ""), - "end": response.get("end", "no") == "yes" - } - except json.JSONDecodeError: - return { - "message": content, - "end": False - } + response = requests.get(url) + response.raise_for_status() # Raise an error for bad responses + data = response.json()["data"] # First JSON parse + data = json.loads(json.loads(data)) + # Parse the API response into Message objects + messages = [] + for item in data: + role = item.get("role", "unknown") + content = item.get("content", "") + timestamp = datetime.now().isoformat() # Use current timestamp if not provided + messages.append(Message(role=role, content=content)) + return messages + except requests.RequestException as e: + print(f"Error fetching conversation history: {e}") + return [] -def add_message(storage_path: Path, conversation_id: str, role: str, content: str) -> None: - """Add a message to the conversation history""" - message_data = { - "role": role, - "content": content, - "timestamp": datetime.now().isoformat() - } - - conversations = load_conversations(storage_path) - if conversation_id not in conversations: - conversations[conversation_id] = {"messages": []} - conversations[conversation_id]["messages"].append(message_data) - save_conversations(storage_path, conversations) - - -def get_conversation_history(conversation_id: str, storage_path: Path) -> List[Message]: - """Get the conversation history""" - conversations = load_conversations(storage_path) - if conversation_id not in conversations: - return None - - return [ - Message( - role=msg["role"], - content=msg["content"], - timestamp=msg["timestamp"] - ) - for msg in conversations[conversation_id]["messages"] - ] - -def load_conversations(storage_path: Path) -> Dict: - """Load conversations from storage file""" - try: - with open(storage_path, 'r') as f: - return json.load(f) - except FileNotFoundError: - return {} - -def save_conversations(storage_path: Path, conversations: Dict) -> None: - """Save conversations to storage file""" - with open(storage_path, 'w') as f: - json.dump(conversations, f, indent=2) def convert_to_langchain_messages(messages: List[Message]) -> List[HumanMessage | AIMessage]: """Convert our Message objects to LangChain message objects""" @@ -114,106 +94,76 @@ def convert_to_langchain_messages(messages: List[Message]) -> List[HumanMessage converted_messages.append(AIMessage(content=msg.content)) return converted_messages - -def ai_chat(query: str, conversation_id: str, theme_id: int, resume: str,full_history=None,form_response=None,generate_theme="NO") -> str: +def ai_chat(query: str, conversation_id: str, theme_id: int, resume: str, full_history=None, form_response=None, generate_theme="NO") -> str: """Main chat function that processes queries and manages conversation""" - storage_path = Path("conversations.json") - class State(TypedDict): - messages: Annotated[Sequence[BaseMessage], "The messages in the conversation"] + messages: List[HumanMessage | AIMessage] language: str + # Initialize model and workflow - model = ChatOpenAI(model=MODEL,temperature=TEMPERATURE) + model = ChatOpenAI(model=MODEL, temperature=TEMPERATURE) if generate_theme == "YES": model = model.with_structured_output(Phase2Generation) else: model = model.with_structured_output(Phase1Generation) + workflow = StateGraph(state_schema=State) def call_model(state: State): - prompt_template = setup_prompt_template(theme_id, resume,full_history,form_response,generate_theme) + prompt_template = setup_prompt_template(theme_id, resume, full_history, form_response, generate_theme) prompt = prompt_template.invoke({ "messages": state["messages"], "language": state["language"] }) response = model.invoke(prompt) return {"messages": [response]} - + workflow.add_edge(START, "model") workflow.add_node("model", call_model) - memory = MemorySaver() app = workflow.compile(checkpointer=memory) - - # Get conversation history - history = get_conversation_history(conversation_id, storage_path) + + # Fetch conversation history from the API + history = fetch_conversation_history(conversation_id) config = {"configurable": {"thread_id": conversation_id}} language = "English" - + if not history: # New conversation input_messages = [HumanMessage(content=query)] if query else [HumanMessage(content="Let's get started")] - output = app.invoke( - {"messages": input_messages, "language": language}, - config - ) else: # Existing conversation history = convert_to_langchain_messages(history) input_messages = history + [HumanMessage(content=query)] if query else history - output = app.invoke( - {"messages": input_messages, "language": language}, - config - ) + output = app.invoke( + {"messages": input_messages, "language": language}, + config + ) - if generate_theme == "YES": structured_message = output["messages"][0] output = structured_message.json(by_alias=True) # This returns a JSON string. - print(f"Output: {output}") - if query: - add_message(storage_path, conversation_id, "human", query) - add_message(storage_path, conversation_id, "ai", output) else: - structured_message = output["messages"][0] output = structured_message.json() # This returns a JSON string. output = json.loads(output) - print(output) + message = output.get("message") print(output) - if query: - add_message(storage_path, conversation_id, "human", query) - add_message(storage_path, conversation_id, "ai", message) - print(output) + return output + # Example usage: if __name__ == "__main__": - # Sample resume - sample_resume = """ - John Doe - EMT-B Certified - 5 years experience as volunteer firefighter - Bachelor's in Fire Science - """ - - # Sample conversation - conversation_id = "12345" - theme_id = 1 # Customer Service theme - - # Start conversation - - - # Continue conversation - follow_up = ai_chat( - query="What was my last questions?", - conversation_id=conversation_id, - theme_id=theme_id, - resume=sample_resume - ) - print("AI:", follow_up) + #conversation_id = "12345" # Replace with the actual conversation ID + query = "Hello let us continue" + theme_id = 1 + resume = "Emergency Response Specialist" + conversation_id = 1 + response = ai_chat(query, conversation_id, theme_id, resume) + print(response) \ No newline at end of file diff --git a/src/prompts.py b/src/prompts.py index 062f9ca..7cffb22 100644 --- a/src/prompts.py +++ b/src/prompts.py @@ -329,7 +329,7 @@ def chat_prompt(theme,resume,full_history=None, form_response=None,generate_them NOTE: DO NOT KEEP THE CONVERSATION excessively long , CAREFULL ANALYZE USER RESUME AND THE PROVIDED EXAMPLES QUESTIONS AND ALL CONTEXT , ASK RELEVANT QUESTION BASED ON THE THEME AND THAT IS ALL - Follow these instructions exactly: + FOLLOW THESE INSTRUCTIONS STTRICTLY: You may receive chat history that includes a previously generated STARTPOP framework, and the user may provide feedback on it. Use this feedback to engage with the user and ask clarifying questions as needed. @@ -346,6 +346,9 @@ def chat_prompt(theme,resume,full_history=None, form_response=None,generate_them If the user says, "I just want to make the actions a little bit clearer," a good response is: "Okay noted, would you like to generate your theme now?" (with the appropriate "end" and "pop_theme_generation" values). If the user responds "yes," a good response is: "Go ahead and click on the theme generation button, thanks." + + "NEVER RETURN THE STARTPOP FRAME WORK FORMAT PLEASE" , SEE EXAMPLE RESPONSES I GAVE + WHENE THERE IS THERE IS NEED TO GENERATE THE STARTPOP THEME FRAME , JUST TELL THEM TO GO AHEAD AND CLICK ON button Strictly adhere to these guidelines """ return prompt \ No newline at end of file diff --git a/test.py b/test.py index 8ebc426..a7a6951 100644 --- a/test.py +++ b/test.py @@ -1,13 +1,28 @@ import os import requests import json +from typing import List from dotenv import load_dotenv load_dotenv() -doc_id = 2 -x_api_key = os.getenv("BACKEND_XAPI_KEY") -url = f"{os.getenv('BACKEND_BASE_URL')}/v3/api/custom/theme-document/answer/{doc_id}?x-project={x_api_key}" -result = requests.get(url) -response_json = result.json() # Return response in JSON format -print(response_json) \ No newline at end of file +from src.llm import ai_chat + + +#conversation_id = "12345" # Replace with the actual conversation ID +query = "Hello let us continue" +theme_id = 1 +resume = "Emergency Response Specialist" +conversation_id = 1 +response = ai_chat(query, conversation_id, theme_id, resume) +print(response) +""" +with open(file_path, 'rb') as file: + files = {'file': file} + response = requests.post(upload_url, files=files) + response.raise_for_status() # Ensure we raise an error for bad responses + response_data = response.json() # Get the response in JSON format + print(response_data) + +""" +