Spaces:
Sleeping
Sleeping
File size: 7,607 Bytes
c5fe829 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.tools import tool
from github import Github
from github import Auth
load_dotenv()
# Constants / Config
COMPANY_NAME = "TechFlow Solutions"
COMPANY_CONTACT = "support@techflow.com | +1-555-0199"
DB_PATH = "vector_db"
def get_vector_store():
embeddings = OpenAIEmbeddings()
vector_store = FAISS.load_local(
DB_PATH,
embeddings,
allow_dangerous_deserialization=True
)
return vector_store
@tool
def search_knowledge_base(query: str) -> str:
"""
Search the company knowledge base for answers to user questions.
Returns relevance of text and citations (source files and page numbers).
"""
try:
vector_store = get_vector_store()
# Use relevance scores (0 to 1, where 1 is best match)
results = vector_store.similarity_search_with_relevance_scores(query, k=5)
response = ""
relevant_count = 0
# Threshold for relevance (0.7 is a reasonable baseline for OpenAI embeddings)
THRESHOLD = 0.7
print(f"\n--- Search Query: '{query}' ---")
for i, (doc, score) in enumerate(results):
print(f"Result {i+1}: Score {score:.4f} | Content: {doc.page_content[:50]}...")
if score < THRESHOLD:
print(f" -> FILTERED (Below {THRESHOLD})")
continue
relevant_count += 1
print(f" -> ACCEPTED")
if score < THRESHOLD:
continue
relevant_count += 1
source = doc.metadata.get("source", "Unknown")
page = doc.metadata.get("page", "Unknown")
# Extract just the filename from the path
filename = os.path.basename(source)
response += f"--- Result {relevant_count} (Score: {score:.2f}) ---\n"
response += f"Content: {doc.page_content}\n"
response += f"Source: {filename}, Page: {page}\n\n"
return response if response else "No relevant information found in the knowledge base (all results below threshold)."
except Exception as e:
return f"Error searching knowledge base: {str(e)}"
class TicketSystem:
def __init__(self, token, repo_name):
auth = Auth.Token(token)
self.g = Github(auth=auth)
self.repo = self.g.get_repo(repo_name)
def create_ticket(self, title, body, project_folder):
"""
project_folder: Project folder name (e.g. 'Capstone2-1.2Antigravity')
"""
# 1. Check or create label
label_name = project_folder.lower().replace("/", "-").replace(" ", "-")
try:
self.repo.get_label(label_name)
except:
# Create new label (blue)
self.repo.create_label(name=label_name, color="0075ca")
# 2. Decorate title
full_title = f"[{project_folder}] {title}"
# 3. Add details to body
full_body = f"**Project:** {project_folder}\n\n**Description:**\n{body}"
# 4. Create Issue
new_issue = self.repo.create_issue(
title=full_title,
body=full_body,
labels=[label_name, "customer-support"]
)
return new_issue
def create_github_issue(summary: str, description: str, user_email: str, user_name: str) -> str:
token = os.getenv("GITHUB_TOKEN")
repo_name = os.getenv("REPO_NAME")
project_folder = os.getenv("PROJECT_FOLDER", "Capstone Project")
if not token or not repo_name:
return "Error: GitHub credentials not configured. Cannot create ticket."
try:
ticket_system = TicketSystem(token, repo_name)
# Combine user details into the body description
full_description = f"**User Name:** {user_name}\n**User Email:** {user_email}\n\n{description}"
issue = ticket_system.create_ticket(
title=summary,
body=full_description,
project_folder=project_folder
)
return f"Ticket created successfully! Ticket ID: #{issue.number}. Link: {issue.html_url}"
except Exception as e:
return f"Error creating ticket: {str(e)}"
@tool
def create_support_ticket(summary: str, description: str, user_email: str, user_name: str) -> str:
"""
Create a support ticket (GitHub Issue) for the user.
Use this when the knowledge base doesn't have the answer or the user explicitly asks to raise a ticket.
Include all details: user name, email, issue summary and full description.
"""
return create_github_issue(summary, description, user_email, user_name)
def create_agent():
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [search_knowledge_base, create_support_ticket]
system_prompt = f"""You are a helpful and professional customer support agent for {COMPANY_NAME}.
Company Contact Info: {COMPANY_CONTACT}
Your goal is to assist users with their questions using the available tools.
GUIDELINES:
1. **ALWAYS SEARCH**: You MUST use the `search_knowledge_base` tool for **EVERY** user message, even if it looks like a typo, gibberish, or nonsense.
- **Reason**: The search tool has internal logic to handle/reject irrelevant queries. You must let it run.
- **Do not** simply reply "It seems like a typo" without calling the tool first.
2. **Intent**: If you can infer a valid term (e.g. "solutiun" -> "Solution"), search for the corrected term. If it is total gibberish, search for the gibberish exactly.
2. **Comprehensive Synthesis**: Use the provided search results to answer the user's question.
- **Summarize ALL chunks**: You must synthesize information from ALL relevant chunks provided by the search tool.
- **Proactive Answering**: If exact matches aren't found, define related concepts (e.g., Software Architecture for Solution Architecture) found in the text.
- **NEVER** refuse to answer if there is ANY retrieved text that is even remotely technical or relevant.
3. **MANDATORY CITATIONS**: You MUST list **ALL** source citations found in the search results at the end of your response.
- Even if you summarize multiple chunks, list every unique source/page used.
- Format: `**Source 1:** [filename] (Page [number])`
4. **IF ANSWER NOT FOUND**: Only if the search results are completely empty or nonsensical string matches, state: "I could not find the answer in the knowledge base."
5. **Ticket Creation**: If you truly cannot help, or if the user explicitly asks, create a support ticket using `create_support_ticket`.
6. Required details for a ticket: Title (Summary), Description, User Name, User Email.
7. Be polite and concise.
"""
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
return agent_executor
|