Diomedes Git commited on
Commit
e857958
·
1 Parent(s): 458e81a

memory integration, corvus first, also some fuzzy logic for paper:query weighting, etc

Browse files
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
- title: Cluas
3
  emoji: 💬
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 5.42.0
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
 
1
  ---
2
+ title: cluas_huginn
3
  emoji: 💬
4
  colorFrom: yellow
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 6
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
src/characters/corvus.py CHANGED
@@ -15,12 +15,11 @@ logger = logging.getLogger(__name__)
15
 
16
  class Corvus:
17
 
18
-
19
-
20
  def __init__(self, use_groq=True, location="Glasgow, Scotland"):
21
  self.name = "Corvus"
22
  self.use_groq = use_groq
23
  self.memory = AgentMemory()
 
24
 
25
  if use_groq:
26
  api_key = os.getenv("GROQ_API_KEY")
@@ -71,6 +70,28 @@ class Corvus:
71
 
72
  return corvus_base_prompt + memory_context
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  async def respond(self,
75
  message: str,
76
  conversation_history: Optional[List[Dict]] = None) -> str:
@@ -85,8 +106,8 @@ class Corvus:
85
 
86
  if "paper" in message.lower() and len(message.split()) < 10: # maybe add oyther keywords? "or study"? "or article"?
87
  recalled = self.recall_paper(message)
88
- if recalled:
89
- return f"Oh, I remember that one! {recalled['title']}. {recalled.get('snippet', '')} Want me to search for more details?"
90
 
91
 
92
  messages = [{"role": "system", "content": self.get_system_prompt()}]
@@ -161,7 +182,7 @@ class Corvus:
161
  "content": tool_result
162
  })
163
 
164
- # Second LLM call with search results
165
  final_response = self.client.chat.completions.create(
166
  model=self.model,
167
  messages=messages,
@@ -171,17 +192,10 @@ class Corvus:
171
 
172
  return final_response.choices[0].message.content.strip()
173
 
174
- # No tool use, return direct response
175
  return choice.message.content.strip()
176
 
177
- def recall_paper(self, query: str) -> Optional[Dict]:
178
- """Try to recall a paper from memory before searching"""
179
- matches = self.memory.search_title(query)
180
- if matches:
181
- return matches[0] # return most relevant
182
- return None
183
-
184
-
185
 
186
  def _format_search_for_llm(self, results: dict) -> str:
187
  """Format search results into text for the LLM to read."""
 
15
 
16
  class Corvus:
17
 
 
 
18
  def __init__(self, use_groq=True, location="Glasgow, Scotland"):
19
  self.name = "Corvus"
20
  self.use_groq = use_groq
21
  self.memory = AgentMemory()
22
+
23
 
24
  if use_groq:
25
  api_key = os.getenv("GROQ_API_KEY")
 
70
 
71
  return corvus_base_prompt + memory_context
72
 
73
+ # little bit of fuzzy for the recall:
74
+
75
+ def recall_paper(self, query: str) -> Optional[Dict]:
76
+ """Try to recall a paper from memory before searching"""
77
+ matches = self.memory.search_title(query)
78
+
79
+ if matches:
80
+ best = matches[0]
81
+ logger.debug(f"Recalled: {best['title']} (score: {best['relevance_score']:.2f})")
82
+ return best # return most relevant
83
+
84
+ return None
85
+
86
+ def clear_memory(self):
87
+ """clears the memory (testing/fresh install purposes)"""
88
+
89
+ self.memory.memory = {}
90
+ self.memory._write_memory({})
91
+ logger.info(f"{self.name}'s memory cleared.")
92
+
93
+
94
+
95
  async def respond(self,
96
  message: str,
97
  conversation_history: Optional[List[Dict]] = None) -> str:
 
106
 
107
  if "paper" in message.lower() and len(message.split()) < 10: # maybe add oyther keywords? "or study"? "or article"?
108
  recalled = self.recall_paper(message)
109
+ if recalled:
110
+ return f"Oh, I remember that one! {recalled['title']}. {recalled.get('snippet', '')} Want me to search for more details?"
111
 
112
 
113
  messages = [{"role": "system", "content": self.get_system_prompt()}]
 
182
  "content": tool_result
183
  })
184
 
185
+ # second LLM call with search results
186
  final_response = self.client.chat.completions.create(
187
  model=self.model,
188
  messages=messages,
 
192
 
193
  return final_response.choices[0].message.content.strip()
194
 
195
+ # no tool use, return direct response
196
  return choice.message.content.strip()
197
 
198
+
 
 
 
 
 
 
 
199
 
200
  def _format_search_for_llm(self, results: dict) -> str:
201
  """Format search results into text for the LLM to read."""
src/cluas_mcp/common/memory.py CHANGED
@@ -2,6 +2,8 @@ import json
2
  from pathlib import Path
3
  from datetime import datetime, timedelta
4
  from typing import List, Dict, Optional
 
 
5
 
6
  class AgentMemory:
7
  """
@@ -87,6 +89,33 @@ class AgentMemory:
87
  if keys_to_delete:
88
  self._write_memory(self.memory)
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
 
92
  # poss usage example:
 
2
  from pathlib import Path
3
  from datetime import datetime, timedelta
4
  from typing import List, Dict, Optional
5
+ from difflib import SequenceMatcher
6
+
7
 
8
  class AgentMemory:
9
  """
 
89
  if keys_to_delete:
90
  self._write_memory(self.memory)
91
 
92
+ def search_titl_scored(self, query: str) -> List[Dict]:
93
+ """Return items with relevance scores"""
94
+ query_lower = query.lower()
95
+ results = []
96
+
97
+ for item in self.memory.values():
98
+ title_lower = item["title"].lower()
99
+
100
+ similarity = SequenceMatcher(None, query_lower, title_lower).ratio()
101
+
102
+ query_words = set(query_lower.split())
103
+ title_words = set(title_lower.split())
104
+ word_overlap = len(query_words & title_words) / len(query_words) if query_words else 0
105
+
106
+ score = (similarity * 0.7) + (word_overlap * 0.3)
107
+
108
+ if score > 0.2:
109
+ result = item.copy()
110
+ result['relevance_score'] = score
111
+ results.append(result)
112
+
113
+
114
+ results.sort(key=lambda x: x['relevance_socre'], reverse=True)
115
+ return results
116
+
117
+
118
+
119
 
120
 
121
  # poss usage example:
src/cluas_mcp/news/news_search_entrypoint.py CHANGED
@@ -96,3 +96,4 @@ def verify_claim(claim: str) -> dict:
96
 
97
 
98
 
 
 
96
 
97
 
98
 
99
+
src/cluas_mcp/observation/observation_entrypoint.py CHANGED
@@ -93,3 +93,4 @@
93
 
94
 
95
 
 
 
93
 
94
 
95
 
96
+
src/cluas_mcp/web/web_search_entrypoint.py CHANGED
@@ -102,3 +102,4 @@ def get_quick_facts(topic: str) -> dict:
102
 
103
 
104
 
 
 
102
 
103
 
104
 
105
+
tests/test_memory.py ADDED
File without changes