Skip to content

Commit 09cf2ef

Browse files
authored
Merge pull request #27 from ProspektStudio/david
Touch-ups
2 parents 988e526 + 0cee9ac commit 09cf2ef

9 files changed

Lines changed: 164 additions & 172 deletions

File tree

api/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
BASE_PROMPT = """You are an expert in the topic of satellites and space stations.
1414
You are here to answer any questions you have regarding the topic.
15-
Give a short one-paragraph answer with the most relevant information."""
15+
Give a short one-paragraph answer with the most recent information."""
1616

1717
def generate_prompt(group: str, name: str):
1818
prompt = f"Give me information about {name}"

api/rag_agent.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,18 @@
1414

1515
logger = uvicorn_logging.getLogger("uvicorn")
1616

17+
# Model and file configuration
1718
EMBEDDINGS_MODEL = "text-embedding-004"
1819
WEB_PAGES_FILE = "webpages.txt"
1920

20-
SYSTEM_PROMPT_FOR_COMBINED_TOOL = """You have access to the following tools:
21+
# RAG configuration
22+
CHUNK_SIZE = 1000
23+
CHUNK_OVERLAP = 200
24+
SIMILARITY_SEARCH_K = 10
25+
LLM_TEMPERATURE = 0.0
26+
27+
SYSTEM_PROMPT_FOR_COMBINED_TOOL = """
28+
You have access to the following tools:
2129
1. **`combined_search`**: Use this tool to provide a comprehensive response that combines both document-specific information and general knowledge. This tool will:
2230
* First search the document collection for relevant information
2331
* Then supplement the response with additional general knowledge
@@ -48,18 +56,33 @@ def __init__(
4856

4957
# Load and process documents if provided
5058
web_pages = load_webpages(WEB_PAGES_FILE)
59+
logger.info(f"Using {len(web_pages)} webpages from {WEB_PAGES_FILE}")
60+
5161
loader = WebBaseLoader(web_paths=web_pages)
5262
docs = loader.load()
53-
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
54-
all_splits = text_splitter.split_documents(docs)
63+
logger.info(f"Loaded {len(docs)} documents")
64+
65+
# Preprocess documents
66+
cleaned_docs = []
67+
for doc in docs:
68+
# Clean the content
69+
cleaned_content = self._preprocess_content(doc.page_content)
70+
if cleaned_content: # Only keep documents with content after cleaning
71+
doc.page_content = cleaned_content
72+
cleaned_docs.append(doc)
73+
logger.info(f"Cleaned {len(cleaned_docs)} documents")
74+
75+
# Split and store cleaned documents
76+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP)
77+
all_splits = text_splitter.split_documents(cleaned_docs)
5578
_ = self.vector_store.add_documents(documents=all_splits)
56-
logger.info(f"Loaded {len(all_splits)} document chunks into vector store")
79+
logger.info(f"Split into {len(all_splits)} chunks and loaded into vector store")
5780

5881
# 2. Set up language model
5982
self.llm = init_chat_model(
6083
llm_model,
6184
model_provider=llm_model_provider,
62-
temperature=0.0
85+
temperature=LLM_TEMPERATURE
6386
)
6487

6588
# 3. Set up memory
@@ -76,6 +99,24 @@ def __init__(
7699

77100
logger.info(f"RAG Agent initialized with system prompt: \n\n{system_prompt}")
78101

102+
def _preprocess_content(self, content: str) -> str:
103+
"""
104+
Simple preprocessing: replace separators with spaces and clean whitespace.
105+
"""
106+
if not content:
107+
return ""
108+
109+
# Replace common separators with spaces
110+
separators = ['\n', '\r', '\t', '|', '•', '→', '←', '↑', '↓', '↔', '↕', '↖', '↗', '↘', '↙']
111+
for sep in separators:
112+
content = content.replace(sep, ' ')
113+
114+
# Clean up whitespace: replace multiple spaces with single space
115+
import re
116+
content = re.sub(r'\s+', ' ', content)
117+
118+
return content.strip()
119+
79120
def generate_combined_tool(self):
80121
@tool(response_format="content_and_artifact")
81122
def combined_search(query: str) -> tuple[str, list]:
@@ -84,10 +125,9 @@ def combined_search(query: str) -> tuple[str, list]:
84125
and general knowledge. This tool will first search the document collection and then
85126
supplement with general knowledge if needed.
86127
"""
87-
logger.info(f"Performing combined search for query: {query}")
88128

89129
# First, try to retrieve from documents
90-
retrieved_docs = self.vector_store.similarity_search(query, k=3)
130+
retrieved_docs = self.vector_store.similarity_search(query, k=SIMILARITY_SEARCH_K)
91131
doc_content = ""
92132

93133
if retrieved_docs:
@@ -98,12 +138,13 @@ def combined_search(query: str) -> tuple[str, list]:
98138
)
99139

100140
# Then, get general knowledge response
101-
message = HumanMessage(content=f"User query: {query}\nPlease provide additional general knowledge that complements the following document information:\n{doc_content}")
141+
message = HumanMessage(content=query)
102142
try:
103143
general_knowledge = self.llm.invoke([message]).content
104144

105145
# Combine both responses
106146
combined_response = f"Document Information:\n{doc_content}\n\nAdditional General Knowledge:\n{general_knowledge}"
147+
logger.info(f"Completed combined search for query")
107148
return combined_response, retrieved_docs
108149
except Exception as e:
109150
logger.error(f"Error in combined search: {e}")
@@ -115,8 +156,7 @@ async def ask(self, prompt: str):
115156
"""
116157
Sends a prompt to the agent and streams the final LLM response.
117158
"""
118-
print(f"\nUser Prompt: {prompt}")
119-
print("Agent Response (streaming):")
159+
logger.info(f"\nUser Prompt: {prompt}")
120160

121161
# Track if we're in a tool call
122162
in_tool_call = False
@@ -141,6 +181,5 @@ async def ask(self, prompt: str):
141181
if isinstance(chunk, AIMessageChunk):
142182
# Only yield content if we're not in a tool call
143183
if chunk.content:
144-
print(chunk.content, end="", flush=True)
145184
final_response_buffer.append(chunk.content)
146185
yield chunk.content

api/webpages.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ https://www.spacex.com/vehicles/dragon/
44
https://en.wikipedia.org/wiki/SpaceX_Dragon
55
https://en.wikipedia.org/wiki/SpaceX_Dragon_2
66
https://en.wikipedia.org/wiki/SpaceX_Crew-10
7-
https://en.wikipedia.org/wiki/Zarya_(ISS_module
7+
https://en.wikipedia.org/wiki/Zarya_(ISS_module)
88
https://en.wikipedia.org/wiki/Tianhe_core_module
9-
https://en.wikipedia.org/wiki/Nauka_(ISS_module
9+
https://en.wikipedia.org/wiki/Nauka_(ISS_module)
1010
https://isstracker.pl/en/satellites/46260
1111
https://en.wikipedia.org/wiki/Wentian_module
1212
https://en.wikipedia.org/wiki/Mengtian_module
1313
https://en.wikipedia.org/wiki/Tianzhou_8
1414
https://en.wikipedia.org/wiki/Progress_MS-29
1515
https://nextspaceflight.com/launches/details/7637
16-
https://en.wikipedia.org/wiki/SpaceX_Crew-10
1716
https://nextspaceflight.com/launches/details/7624
1817
https://en.wikipedia.org/wiki/Shenzhou_20
1918
https://www.globalstar.com/

components/AiInfo.tsx

Lines changed: 82 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -27,91 +27,33 @@ const agentInfo: Record<string, { name: string; description: string }> = {
2727
},
2828
}
2929

30-
const Tooltip = ({ agent, text }: { agent: Agent, text: string }) => {
31-
const [showTooltip, setShowTooltip] = useState(false);
32-
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
33-
const [mounted, setMounted] = useState(false);
34-
35-
useEffect(() => {
36-
setMounted(true);
37-
return () => setMounted(false);
38-
}, []);
39-
40-
const handleMouseEnter = (e: React.MouseEvent) => {
41-
const rect = e.currentTarget.getBoundingClientRect();
42-
setTooltipPosition({
43-
x: rect.left + rect.width / 2,
44-
y: rect.top
45-
});
46-
setShowTooltip(true);
47-
};
48-
49-
const handleMouseLeave = () => {
50-
setShowTooltip(false);
51-
};
52-
53-
return (
54-
<>
55-
<span
56-
style={{
57-
cursor: 'help',
58-
position: 'relative',
59-
display: 'inline-block'
60-
}}
61-
onMouseEnter={handleMouseEnter}
62-
onMouseLeave={handleMouseLeave}
63-
>
64-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
65-
<circle cx="12" cy="12" r="10"></circle>
66-
<path d="M12 16v-4"></path>
67-
<path d="M12 8h.01"></path>
68-
</svg>
69-
</span>
70-
{mounted && showTooltip && createPortal(
71-
<div
72-
style={{
73-
position: 'fixed',
74-
left: tooltipPosition.x,
75-
top: tooltipPosition.y,
76-
transform: `translate(${agent === Agent.LLM ? '-30%' : '-80%'}, -100%)`,
77-
padding: '8px',
78-
background: 'rgba(0, 0, 0, 0.8)',
79-
color: 'white',
80-
borderRadius: '4px',
81-
fontSize: '12px',
82-
zIndex: 1000,
83-
pointerEvents: 'none',
84-
width: '300px',
85-
marginTop: '-8px'
86-
}}
87-
>
88-
{text}
89-
</div>,
90-
document.body
91-
)}
92-
</>
93-
);
94-
};
95-
9630
const AiInfo: React.FC<AiInfoProps> = ({ selectedSatellite }) => {
9731

9832
if (!selectedSatellite) return null;
9933

100-
const [agent, setAgent] = useState<keyof typeof agentInfo>('llm');
101-
const [isHovered, setIsHovered] = useState(false);
34+
const [agent, setAgent] = useState<keyof typeof agentInfo | null>(null);
10235
const [isLoading, setIsLoading] = useState<boolean>(false);
10336
const [llmSatelliteInfo, setLlmSatelliteInfo] = useState<string>('');
10437
const [ragSatelliteInfo, setRagSatelliteInfo] = useState<string>('');
105-
106-
const satelliteInfo = agent === 'llm' ? llmSatelliteInfo : ragSatelliteInfo;
107-
const setSatelliteInfo = agent === 'llm' ? setLlmSatelliteInfo : setRagSatelliteInfo;
38+
const satelliteInfo = agent === null ? '' : (agent === Agent.LLM ? llmSatelliteInfo : ragSatelliteInfo);
10839

10940
useEffect(() => {
11041
setLlmSatelliteInfo('');
11142
setRagSatelliteInfo('');
11243
}, [selectedSatellite]);
11344

114-
const fetchSatelliteInfoInChuncks = async (): Promise<void> => {
45+
const onAgentSelect = async (agent: keyof typeof agentInfo) => {
46+
47+
// Check if we already have info for this agent
48+
const currentInfo = agent === Agent.LLM ? llmSatelliteInfo : ragSatelliteInfo;
49+
if (currentInfo) {
50+
setAgent(agent);
51+
return;
52+
}
53+
54+
setAgent(agent);
55+
const setSatelliteInfo = agent === Agent.LLM ? setLlmSatelliteInfo : setRagSatelliteInfo;
56+
11557
setIsLoading(true);
11658
if (!selectedSatellite) return;
11759
try {
@@ -129,43 +71,17 @@ const AiInfo: React.FC<AiInfoProps> = ({ selectedSatellite }) => {
12971
return (
13072
<>
13173
<div className="toggle-buttons">
132-
{Object.entries(agentInfo).map(([key, value]) => (
74+
{Object.keys(agentInfo).map((key: keyof typeof agentInfo) => (
13375
<button
13476
key={key}
13577
className={`toggle-button w-full flex items-center justify-center gap-2 ${agent === key ? 'active' : ''}`}
136-
onClick={() => setAgent(key)}
78+
onClick={() => onAgentSelect(key)}
13779
>
13880
<span className="pt-1">{key.toUpperCase()}</span>
13981
<Tooltip agent={key as Agent} text={agentInfo[key].description} />
14082
</button>
14183
))}
14284
</div>
143-
{!satelliteInfo && !isLoading && (
144-
<p className="mt-4">
145-
<button
146-
onClick={() => fetchSatelliteInfoInChuncks()}
147-
onMouseEnter={() => setIsHovered(true)}
148-
onMouseLeave={() => setIsHovered(false)}
149-
style={{
150-
width: '100%',
151-
marginBottom: '16px',
152-
padding: '8px 16px',
153-
background: isHovered ? 'rgba(59, 130, 246, 0.9)' : 'rgba(59, 130, 246, 0.8)',
154-
border: '1px solid rgba(147, 197, 253, 0.3)',
155-
borderRadius: '4px',
156-
color: 'white',
157-
fontSize: '14px',
158-
fontFamily: 'Shentox',
159-
fontWeight: 500,
160-
cursor: 'pointer',
161-
transition: 'all 0.2s ease-in-out',
162-
transform: isHovered ? 'translateY(-1px)' : 'none'
163-
}}
164-
>
165-
Get AI-Powered Satellite Info
166-
</button>
167-
</p>
168-
)}
16985

17086
<div className="satellite-info">
17187
{isLoading ? (
@@ -241,4 +157,70 @@ const AiInfo: React.FC<AiInfoProps> = ({ selectedSatellite }) => {
241157
);
242158
};
243159

160+
const Tooltip = ({ agent, text }: { agent: Agent, text: string }) => {
161+
const [showTooltip, setShowTooltip] = useState(false);
162+
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
163+
const [mounted, setMounted] = useState(false);
164+
165+
useEffect(() => {
166+
setMounted(true);
167+
return () => setMounted(false);
168+
}, []);
169+
170+
const handleMouseEnter = (e: React.MouseEvent) => {
171+
const rect = e.currentTarget.getBoundingClientRect();
172+
setTooltipPosition({
173+
x: rect.left + rect.width / 2,
174+
y: rect.top
175+
});
176+
setShowTooltip(true);
177+
};
178+
179+
const handleMouseLeave = () => {
180+
setShowTooltip(false);
181+
};
182+
183+
return (
184+
<>
185+
<span
186+
style={{
187+
cursor: 'help',
188+
position: 'relative',
189+
display: 'inline-block'
190+
}}
191+
onMouseEnter={handleMouseEnter}
192+
onMouseLeave={handleMouseLeave}
193+
>
194+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
195+
<circle cx="12" cy="12" r="10"></circle>
196+
<path d="M12 16v-4"></path>
197+
<path d="M12 8h.01"></path>
198+
</svg>
199+
</span>
200+
{mounted && showTooltip && createPortal(
201+
<div
202+
style={{
203+
position: 'fixed',
204+
left: tooltipPosition.x,
205+
top: tooltipPosition.y,
206+
transform: `translate(${agent === Agent.LLM ? '-30%' : '-80%'}, -100%)`,
207+
padding: '8px',
208+
background: 'rgba(0, 0, 0, 0.8)',
209+
color: 'white',
210+
borderRadius: '4px',
211+
fontSize: '12px',
212+
zIndex: 1000,
213+
pointerEvents: 'none',
214+
width: '300px',
215+
marginTop: '-8px'
216+
}}
217+
>
218+
{text}
219+
</div>,
220+
document.body
221+
)}
222+
</>
223+
);
224+
};
225+
244226
export default AiInfo;

components/CustomDropdown.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ const CustomDropdown: React.FC<CustomDropdownProps> = ({
130130
}
131131
132132
.dropdown-item.selected {
133-
background: transparent;
133+
background: rgba(128, 128, 128, 0.3);
134+
cursor: default;
134135
}
135136
136137
/* Scrollbar styling */

0 commit comments

Comments
 (0)