Spaces:
Runtime error
Runtime error
Julian Vanecek commited on
Commit ·
9967c6c
1
Parent(s): a35d3a6
Add document search functionality
Browse files- Create minimal vector search implementation
- Add embedding-based document retrieval
- Integrate search results into chat context
- Use simple Blocks interface instead of ChatInterface
- app.py +61 -7
- requirements.txt +3 -1
- simple_vector_search.py +65 -0
app.py
CHANGED
|
@@ -1,9 +1,38 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import os
|
| 3 |
from openai import OpenAI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
def chat_function(message, history):
|
| 6 |
-
"""Chat function with OpenAI integration"""
|
| 7 |
# Check if API key is set
|
| 8 |
api_key = os.environ.get("OPENAI_API_KEY", "")
|
| 9 |
|
|
@@ -14,8 +43,16 @@ def chat_function(message, history):
|
|
| 14 |
# Initialize OpenAI client
|
| 15 |
client = OpenAI(api_key=api_key)
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# Convert history to OpenAI format
|
| 18 |
-
messages = [{"role": "system", "content":
|
| 19 |
|
| 20 |
for user_msg, assistant_msg in history:
|
| 21 |
messages.append({"role": "user", "content": user_msg})
|
|
@@ -40,10 +77,27 @@ def chat_function(message, history):
|
|
| 40 |
return history
|
| 41 |
|
| 42 |
# Create chat interface
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
demo.launch()
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import os
|
| 3 |
from openai import OpenAI
|
| 4 |
+
from simple_vector_search import SimpleVectorSearch
|
| 5 |
+
|
| 6 |
+
# Initialize vector search
|
| 7 |
+
vector_search = SimpleVectorSearch()
|
| 8 |
+
|
| 9 |
+
def get_relevant_context(query: str, client: OpenAI) -> str:
|
| 10 |
+
"""Get relevant context from documents"""
|
| 11 |
+
try:
|
| 12 |
+
# Get query embedding
|
| 13 |
+
response = client.embeddings.create(
|
| 14 |
+
model="text-embedding-ada-002",
|
| 15 |
+
input=query
|
| 16 |
+
)
|
| 17 |
+
query_embedding = response.data[0].embedding
|
| 18 |
+
|
| 19 |
+
# Search for relevant documents
|
| 20 |
+
results = vector_search.search(query_embedding, k=3)
|
| 21 |
+
|
| 22 |
+
# Format context
|
| 23 |
+
if results:
|
| 24 |
+
context = "Relevant information from documentation:\n\n"
|
| 25 |
+
for i, result in enumerate(results, 1):
|
| 26 |
+
context += f"{i}. {result['content'][:500]}...\n\n"
|
| 27 |
+
return context
|
| 28 |
+
else:
|
| 29 |
+
return ""
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"Error in vector search: {e}")
|
| 32 |
+
return ""
|
| 33 |
|
| 34 |
def chat_function(message, history):
|
| 35 |
+
"""Chat function with OpenAI integration and document search"""
|
| 36 |
# Check if API key is set
|
| 37 |
api_key = os.environ.get("OPENAI_API_KEY", "")
|
| 38 |
|
|
|
|
| 43 |
# Initialize OpenAI client
|
| 44 |
client = OpenAI(api_key=api_key)
|
| 45 |
|
| 46 |
+
# Get relevant context
|
| 47 |
+
context = get_relevant_context(message, client)
|
| 48 |
+
|
| 49 |
+
# Build system message
|
| 50 |
+
system_content = "You are a helpful AI assistant with access to technical documentation."
|
| 51 |
+
if context:
|
| 52 |
+
system_content += f"\n\n{context}"
|
| 53 |
+
|
| 54 |
# Convert history to OpenAI format
|
| 55 |
+
messages = [{"role": "system", "content": system_content}]
|
| 56 |
|
| 57 |
for user_msg, assistant_msg in history:
|
| 58 |
messages.append({"role": "user", "content": user_msg})
|
|
|
|
| 77 |
return history
|
| 78 |
|
| 79 |
# Create chat interface
|
| 80 |
+
with gr.Blocks(title="AI Assistant") as demo:
|
| 81 |
+
gr.Markdown("# 🤖 AI Assistant Multi-Agent System")
|
| 82 |
+
gr.Markdown("Chat with an AI assistant that has access to technical documentation.")
|
| 83 |
+
|
| 84 |
+
chatbot = gr.Chatbot()
|
| 85 |
+
msg = gr.Textbox(
|
| 86 |
+
label="Your Message",
|
| 87 |
+
placeholder="Ask about Harmony or Chorus documentation...",
|
| 88 |
+
lines=2
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
with gr.Row():
|
| 92 |
+
submit = gr.Button("Send", variant="primary")
|
| 93 |
+
clear = gr.Button("Clear")
|
| 94 |
+
|
| 95 |
+
# Event handlers
|
| 96 |
+
def respond(message, history):
|
| 97 |
+
return chat_function(message, history), ""
|
| 98 |
+
|
| 99 |
+
msg.submit(respond, [msg, chatbot], [chatbot, msg])
|
| 100 |
+
submit.click(respond, [msg, chatbot], [chatbot, msg])
|
| 101 |
+
clear.click(lambda: ([], ""), outputs=[chatbot, msg])
|
| 102 |
|
| 103 |
demo.launch()
|
requirements.txt
CHANGED
|
@@ -1,2 +1,4 @@
|
|
| 1 |
gradio>=4.0.0
|
| 2 |
-
openai>=1.0.0
|
|
|
|
|
|
|
|
|
| 1 |
gradio>=4.0.0
|
| 2 |
+
openai>=1.0.0
|
| 3 |
+
numpy>=1.24.0
|
| 4 |
+
tiktoken>=0.5.0
|
simple_vector_search.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Minimal vector search implementation for HuggingFace deployment
|
| 3 |
+
"""
|
| 4 |
+
import json
|
| 5 |
+
import numpy as np
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import List, Dict, Tuple
|
| 8 |
+
|
| 9 |
+
class SimpleVectorSearch:
|
| 10 |
+
"""Simple in-memory vector search"""
|
| 11 |
+
|
| 12 |
+
def __init__(self, data_dir: str = "py/backend/data/embeddings"):
|
| 13 |
+
self.data_dir = Path(data_dir)
|
| 14 |
+
self.documents = []
|
| 15 |
+
self.embeddings = None
|
| 16 |
+
self._load_embeddings()
|
| 17 |
+
|
| 18 |
+
def _load_embeddings(self):
|
| 19 |
+
"""Load all embedding files"""
|
| 20 |
+
all_docs = []
|
| 21 |
+
all_embeddings = []
|
| 22 |
+
|
| 23 |
+
# Load all JSON files
|
| 24 |
+
for json_file in self.data_dir.glob("*.json"):
|
| 25 |
+
try:
|
| 26 |
+
with open(json_file, 'r') as f:
|
| 27 |
+
data = json.load(f)
|
| 28 |
+
for item in data:
|
| 29 |
+
all_docs.append({
|
| 30 |
+
'content': item['content'],
|
| 31 |
+
'metadata': item.get('metadata', {})
|
| 32 |
+
})
|
| 33 |
+
all_embeddings.append(item['embedding'])
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"Error loading {json_file}: {e}")
|
| 36 |
+
|
| 37 |
+
self.documents = all_docs
|
| 38 |
+
self.embeddings = np.array(all_embeddings) if all_embeddings else None
|
| 39 |
+
|
| 40 |
+
def search(self, query_embedding: List[float], k: int = 3) -> List[Dict]:
|
| 41 |
+
"""Search for similar documents"""
|
| 42 |
+
if self.embeddings is None or len(self.embeddings) == 0:
|
| 43 |
+
return []
|
| 44 |
+
|
| 45 |
+
# Convert query to numpy array
|
| 46 |
+
query_vec = np.array(query_embedding)
|
| 47 |
+
|
| 48 |
+
# Compute cosine similarity
|
| 49 |
+
query_norm = query_vec / (np.linalg.norm(query_vec) + 1e-10)
|
| 50 |
+
embeddings_norm = self.embeddings / (np.linalg.norm(self.embeddings, axis=1, keepdims=True) + 1e-10)
|
| 51 |
+
similarities = np.dot(embeddings_norm, query_norm)
|
| 52 |
+
|
| 53 |
+
# Get top k indices
|
| 54 |
+
top_indices = np.argsort(similarities)[-k:][::-1]
|
| 55 |
+
|
| 56 |
+
# Return documents with scores
|
| 57 |
+
results = []
|
| 58 |
+
for idx in top_indices:
|
| 59 |
+
results.append({
|
| 60 |
+
'content': self.documents[idx]['content'],
|
| 61 |
+
'metadata': self.documents[idx]['metadata'],
|
| 62 |
+
'score': float(similarities[idx])
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
return results
|