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