RemanenetSpy commited on
Commit
7fc8c13
·
1 Parent(s): d8952c8

Refactor: Implement LiteLLM Mixture of Agents Orchestration

Browse files
.env.example CHANGED
@@ -5,6 +5,10 @@
5
  # Get your free key at: https://console.groq.com
6
  GROQ_API_KEY=gsk_your_key_here
7
 
 
 
 
 
8
  # Razorpay (for premium tier billing)
9
  RAZORPAY_KEY_ID=rzp_test_...
10
  RAZORPAY_KEY_SECRET=secret_...
 
5
  # Get your free key at: https://console.groq.com
6
  GROQ_API_KEY=gsk_your_key_here
7
 
8
+ # Cerebras Inference API (free 1M tokens/day)
9
+ # Get your free key at: https://inference.cerebras.ai
10
+ CEREBRAS_API_KEY=cbr_your_key_here
11
+
12
  # Razorpay (for premium tier billing)
13
  RAZORPAY_KEY_ID=rzp_test_...
14
  RAZORPAY_KEY_SECRET=secret_...
agent/nodes.py CHANGED
@@ -114,16 +114,11 @@ async def call_model_node(state: dict) -> dict:
114
  if not isinstance(msg, SystemMessage):
115
  augmented.append(msg)
116
 
117
- # Get the LLM (Groq — free tier, blazing fast)
118
  try:
119
- from langchain_groq import ChatGroq
120
- import os
121
-
122
- llm = ChatGroq(
123
- model="llama-3.3-70b-versatile",
124
- api_key=os.getenv("GROQ_API_KEY", ""),
125
- temperature=0.7,
126
- )
127
 
128
  # Bind tools so the agent can interact with SaaS connectors
129
  from agent.tools import CHRONOS_TOOLS
 
114
  if not isinstance(msg, SystemMessage):
115
  augmented.append(msg)
116
 
117
+ # Get the LLM from the Mixture of Agents Router (Heavy Pipeline)
118
  try:
119
+ from chronos_core.llm_router import get_heavy_pipeline
120
+
121
+ llm = get_heavy_pipeline()
 
 
 
 
 
122
 
123
  # Bind tools so the agent can interact with SaaS connectors
124
  from agent.tools import CHRONOS_TOOLS
chronos_core/llm_router.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chronos OS — Multi-Model Mixture of Agents Router
3
+ ===================================================
4
+ Intelligently routes tasks to different LLMs to aggregate free-tier API limits.
5
+ Falls back safely if rate limits (429) or downtimes occur.
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from typing import Any
11
+
12
+ from langchain_community.chat_models import ChatLiteLLM
13
+ from langchain_core.language_models.chat_models import BaseChatModel
14
+
15
+ logger = logging.getLogger("chronos.llm_router")
16
+
17
+ def get_fast_pipeline_kwargs() -> dict[str, Any]:
18
+ """
19
+ Routing configuration for the Fast Pipeline (Memory Extraction).
20
+ Primary: Cerebras Llama 3.1 8B (Massive limit, extreme speed)
21
+ Fallback: Groq Llama 3.1 8B Instant
22
+ """
23
+ has_cerebras = bool(os.getenv("CEREBRAS_API_KEY"))
24
+ has_groq = bool(os.getenv("GROQ_API_KEY"))
25
+
26
+ if has_cerebras and has_groq:
27
+ return {
28
+ "model": "cerebras/llama3.1-8b",
29
+ "fallbacks": [{"model": "groq/llama-3.1-8b-instant"}]
30
+ }
31
+ elif has_cerebras:
32
+ return {"model": "cerebras/llama3.1-8b"}
33
+ else:
34
+ # Default to Groq if no Cerebras key is found
35
+ return {"model": "groq/llama-3.1-8b-instant"}
36
+
37
+
38
+ def get_heavy_pipeline() -> BaseChatModel:
39
+ """
40
+ Routing configuration for the Heavy Pipeline (Agentic Reasoning).
41
+ Primary: Cerebras Qwen 3 235B (Elite logic and deep reasoning)
42
+ Fallback: Groq Llama 3.3 70B
43
+ """
44
+ has_cerebras = bool(os.getenv("CEREBRAS_API_KEY"))
45
+ has_groq = bool(os.getenv("GROQ_API_KEY"))
46
+
47
+ # Initialize the robust Groq fallback (always available if .env set)
48
+ # Using litellm via langchain
49
+ groq_llm = ChatLiteLLM(
50
+ model="groq/llama-3.3-70b-versatile",
51
+ temperature=0.4
52
+ )
53
+
54
+ if has_cerebras:
55
+ cerebras_llm = ChatLiteLLM(
56
+ model="cerebras/qwen3-235b",
57
+ temperature=0.4
58
+ )
59
+ if has_groq:
60
+ # Langchain native fallback mechanism
61
+ return cerebras_llm.with_fallbacks([groq_llm])
62
+ return cerebras_llm
63
+
64
+ return groq_llm
chronos_core/svo_parser.py CHANGED
@@ -128,36 +128,14 @@ class SVOParser:
128
  """
129
  Extracts Subject-Verb-Object tuples from raw text.
130
 
131
- Primary: Groq API (free tier — DeepSeek R1 / Llama at 300+ tok/sec)
132
- Fallback: Regex patterns (when LLM fails or quota exhausted)
133
  """
134
 
135
- def __init__(
136
- self,
137
- api_key: Optional[str] = None,
138
- model: str = "llama-3.3-70b-versatile",
139
- ):
140
- self.api_key = api_key or os.getenv("GROQ_API_KEY", "")
141
- self.model = model
142
- self._client = None
143
-
144
- if self.api_key:
145
- try:
146
- from groq import Groq
147
- self._client = Groq(api_key=self.api_key)
148
- logger.info(f"Groq SVO parser initialized with model: {self.model}")
149
- except ImportError:
150
- logger.warning(
151
- "groq not installed. Using regex fallback. "
152
- "Install with: pip install groq"
153
- )
154
- except Exception as e:
155
- logger.warning(f"Failed to init Groq client: {e}. Using regex fallback.")
156
- else:
157
- logger.warning(
158
- "No GROQ_API_KEY set. SVO parser will use regex fallback only. "
159
- "Get a free key at https://console.groq.com"
160
- )
161
 
162
  async def parse(
163
  self,
@@ -166,51 +144,52 @@ class SVOParser:
166
  ) -> list[SVOTuple]:
167
  """
168
  Extract SVO tuples from text.
169
- Tries Groq (DeepSeek R1) first, falls back to regex.
170
  """
171
  if not text or not text.strip():
172
  return []
173
 
174
  ts = timestamp or datetime.utcnow()
175
 
176
- # Try LLM extraction
177
- if self._client:
178
- try:
179
- return await self._parse_with_groq(text, ts)
180
- except Exception as e:
181
- logger.warning(f"Groq SVO extraction failed: {e}. Falling back to regex.")
182
 
183
  # Regex fallback
184
  return _regex_fallback(text, ts)
185
 
186
- async def _parse_with_groq(
187
  self,
188
  text: str,
189
  timestamp: datetime,
190
  ) -> list[SVOTuple]:
191
- """Call Groq API (DeepSeek R1) to extract SVO tuples."""
192
- import asyncio
 
193
 
194
  prompt = SVO_EXTRACTION_PROMPT.format(
195
  text=text,
196
  current_time=timestamp.isoformat(),
197
  )
198
 
199
- # Groq SDK is synchronous — wrap for async
200
- response = await asyncio.to_thread(
201
- self._client.chat.completions.create,
202
- model=self.model,
203
- messages=[
204
- {
205
- "role": "system",
206
- "content": "You are a precise JSON event extractor. Output ONLY valid JSON arrays.",
207
- },
208
- {"role": "user", "content": prompt},
209
- ],
210
- temperature=0.1,
211
- max_tokens=2000,
212
- response_format={"type": "json_object"},
213
- )
 
214
 
215
  raw = response.choices[0].message.content.strip()
216
 
 
128
  """
129
  Extracts Subject-Verb-Object tuples from raw text.
130
 
131
+ Primary: High Speed Model via LiteLLM Router.
132
+ Fallback: Regex patterns (when LLM fails or limits exhausted)
133
  """
134
 
135
+ def __init__(self):
136
+ from .llm_router import get_fast_pipeline_kwargs
137
+ self._llm_kwargs = get_fast_pipeline_kwargs()
138
+ logger.info(f"SVO parser initialized with fast pipeline: {self._llm_kwargs.get('model')}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  async def parse(
141
  self,
 
144
  ) -> list[SVOTuple]:
145
  """
146
  Extract SVO tuples from text.
147
+ Tries Litellm Fast Pipeline first, falls back to regex.
148
  """
149
  if not text or not text.strip():
150
  return []
151
 
152
  ts = timestamp or datetime.utcnow()
153
 
154
+ # Try LLM extraction via LiteLLM
155
+ try:
156
+ return await self._parse_with_litellm(text, ts)
157
+ except Exception as e:
158
+ logger.warning(f"Fast Pipeline SVO extraction failed: {e}. Falling back to regex.")
 
159
 
160
  # Regex fallback
161
  return _regex_fallback(text, ts)
162
 
163
+ async def _parse_with_litellm(
164
  self,
165
  text: str,
166
  timestamp: datetime,
167
  ) -> list[SVOTuple]:
168
+ """Call LiteLLM unified completion to extract SVO tuples."""
169
+ import litellm
170
+ from .llm_router import get_fast_pipeline_kwargs
171
 
172
  prompt = SVO_EXTRACTION_PROMPT.format(
173
  text=text,
174
  current_time=timestamp.isoformat(),
175
  )
176
 
177
+ kwargs = get_fast_pipeline_kwargs()
178
+ kwargs["messages"] = [
179
+ {
180
+ "role": "system",
181
+ "content": "You are a precise JSON event extractor. Output ONLY valid JSON arrays.",
182
+ },
183
+ {"role": "user", "content": prompt},
184
+ ]
185
+ kwargs["temperature"] = 0.1
186
+ kwargs["max_tokens"] = 2000
187
+ # Not all fallback models gracefully support strict json format enforcement in litellm uniformly if the provider doesn't support it.
188
+ # But we will use it for safety.
189
+ kwargs["response_format"] = {"type": "json_object"}
190
+
191
+ # LiteLLM acompletion directly leverages the fallback arrays natively!
192
+ response = await litellm.acompletion(**kwargs)
193
 
194
  raw = response.choices[0].message.content.strip()
195
 
dashboard/app.py CHANGED
@@ -560,7 +560,7 @@ if page == "⟡ Overview":
560
  with col4:
561
  st.markdown("""
562
  <div class="metric-card">
563
- <div class="metric-value" style="font-size:1.8rem">Llama 3.3 70B</div>
564
  <div class="metric-label">AI Engine</div>
565
  </div>
566
  """, unsafe_allow_html=True)
@@ -613,7 +613,7 @@ elif page == "⭳ Ingest Events":
613
  label_visibility="collapsed",
614
  )
615
 
616
- parse_svo = st.checkbox("✧ Enable AI SVO Parsing (Llama 3.3 70B)", value=True)
617
 
618
  if st.button("⭳ Ingest Into Memory") and stored_key and events_text.strip():
619
  lines = [l.strip() for l in events_text.strip().split("\n") if l.strip()]
@@ -623,7 +623,7 @@ elif page == "⭳ Ingest Events":
623
  "parse_svo": parse_svo,
624
  }
625
 
626
- with st.spinner("Llama 3.3 70B is extracting temporal events..."):
627
  result = api_call("POST", "/ingest", stored_key, json=payload)
628
 
629
  if "error" not in result:
 
560
  with col4:
561
  st.markdown("""
562
  <div class="metric-card">
563
+ <div class="metric-value" style="font-size:1.4rem; padding-top: 0.3rem;">Qwen 3 + Llama Mixture</div>
564
  <div class="metric-label">AI Engine</div>
565
  </div>
566
  """, unsafe_allow_html=True)
 
613
  label_visibility="collapsed",
614
  )
615
 
616
+ parse_svo = st.checkbox("✧ Enable Fast SVO Parsing (Llama Mixture)", value=True)
617
 
618
  if st.button("⭳ Ingest Into Memory") and stored_key and events_text.strip():
619
  lines = [l.strip() for l in events_text.strip().split("\n") if l.strip()]
 
623
  "parse_svo": parse_svo,
624
  }
625
 
626
+ with st.spinner("Llama Engine is extracting temporal events..."):
627
  result = api_call("POST", "/ingest", stored_key, json=payload)
628
 
629
  if "error" not in result:
requirements.txt CHANGED
@@ -13,3 +13,4 @@ razorpay>=1.4.0
13
  python-dotenv>=1.0.0
14
  httpx>=0.28.0
15
  sentence-transformers>=3.0.0
 
 
13
  python-dotenv>=1.0.0
14
  httpx>=0.28.0
15
  sentence-transformers>=3.0.0
16
+ langchain-community>=0.3.0
scratch_remove.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ def remove_emojis():
4
+ filepath = "dashboard/app.py"
5
+ with open(filepath, "r", encoding="utf-8") as f:
6
+ content = f.read()
7
+
8
+ # Define exact replacements
9
+ replacements = {
10
+ "🔑": "⚷",
11
+ "📥": "⭳",
12
+ "🔍": "⚲",
13
+ "🤖": "✦",
14
+ "🔗": "⚙",
15
+ "📊": "▤",
16
+ "✅": "✓",
17
+ "⚠️": "⚠",
18
+ "✨": "✧",
19
+ "🕰️": "⧗",
20
+ "🏠": "⌂"
21
+ }
22
+
23
+ for emoji, symbol in replacements.items():
24
+ content = content.replace(emoji, symbol)
25
+
26
+ # Some old ones might still have the space, or might need removal.
27
+ # The symbols above are very elegant, but if the user wants them completely gone:
28
+ # "remove evry possible emoji... replace them with something better and adjust that name also"
29
+
30
+ with open(filepath, "w", encoding="utf-8") as f:
31
+ f.write(content)
32
+
33
+ if __name__ == "__main__":
34
+ remove_emojis()
35
+ print("Emojis replaced successfully.")