gcharanteja commited on
Commit
7cbaace
Β·
1 Parent(s): 2881c4f

Refactor Telegram agent initialization and shared state management; add error handling and model path resolution

Browse files
Files changed (4) hide show
  1. .dockerignore +10 -0
  2. main.py +23 -15
  3. shared_state.py +3 -0
  4. telegram_agent.py +66 -22
.dockerignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .env
2
+ *.env
3
+ .venv/
4
+ __pycache__/
5
+ *.pyc
6
+ models/
7
+ *.gguf
8
+ .qwen
9
+ .git/
10
+ .gitignore
main.py CHANGED
@@ -95,31 +95,35 @@ def start_jira_server():
95
  def start_telegram_and_mcp():
96
  """Run the Telegram agent + MCP server in a dedicated event loop."""
97
  import shared_state
98
- from telegram_agent import (
99
- start_telegram_with_mcp, _init_telegram_bot, _init_llm,
100
- _init_embedder, _init_pinecone, bot, llm,
101
- )
102
 
103
  # Initialize components to detect LLM
104
  try:
105
- _init_embedder()
106
- _init_llm()
107
- _init_pinecone()
108
- _init_telegram_bot()
109
  except Exception as e:
110
  state.error = f"Failed to init components: {str(e)}"
 
 
111
  print(f"[orchestrator] ERROR: {state.error}")
112
  return
113
 
114
- # Set refs
115
- shared_state.llm_instance = llm
116
- shared_state.telegram_bot = bot
 
 
117
 
118
  # Run the MCP + Telegram loop
119
  try:
120
- asyncio.run(start_telegram_with_mcp())
121
  except Exception as e:
122
  state.error = f"Telegram/MCP failed: {str(e)}"
 
 
123
  print(f"[orchestrator] ERROR: {state.error}")
124
 
125
 
@@ -185,7 +189,7 @@ def status_mcp():
185
 
186
  if session is None or tools is None:
187
  return {"service": "mcp_server", "status": "down", "tools_count": 0,
188
- "tools": [], "error": "MCP server not initialized"}
189
  return {"service": "mcp_server", "status": "live", "tools_count": len(tools),
190
  "tools": [t["function"]["name"] for t in tools]}
191
 
@@ -200,11 +204,15 @@ def status_telegram():
200
  bot_status = "live" if b is not None else "down"
201
  llm_status = "loaded" if l is not None else "not_loaded"
202
 
203
- model_path = os.environ.get("MODEL_PATH", os.path.join(BASE_DIR, "models", "microsoft_Phi-4-mini-instruct-Q4_K_M.gguf"))
 
 
204
  model_exists = os.path.exists(model_path)
205
 
206
  return {"service": "telegram_bot", "bot_status": bot_status, "llm_status": llm_status,
207
- "model_path": model_path, "model_downloaded": model_exists}
 
 
208
 
209
 
210
  @app.get("/status")
 
95
  def start_telegram_and_mcp():
96
  """Run the Telegram agent + MCP server in a dedicated event loop."""
97
  import shared_state
98
+ import telegram_agent as tg
99
+ shared_state.startup_stage = "initializing"
 
 
100
 
101
  # Initialize components to detect LLM
102
  try:
103
+ tg._init_embedder()
104
+ tg._init_llm()
105
+ tg._init_pinecone()
106
+ tg._init_telegram_bot()
107
  except Exception as e:
108
  state.error = f"Failed to init components: {str(e)}"
109
+ shared_state.telegram_error = state.error
110
+ shared_state.startup_stage = "failed"
111
  print(f"[orchestrator] ERROR: {state.error}")
112
  return
113
 
114
+ # Set refs from live module globals (not from stale imported names)
115
+ shared_state.llm_instance = tg.llm
116
+ shared_state.telegram_bot = tg.bot
117
+ shared_state.model_path = tg.resolved_model_path or tg.MODEL_PATH
118
+ shared_state.telegram_error = None
119
 
120
  # Run the MCP + Telegram loop
121
  try:
122
+ asyncio.run(tg.start_telegram_with_mcp())
123
  except Exception as e:
124
  state.error = f"Telegram/MCP failed: {str(e)}"
125
+ shared_state.telegram_error = state.error
126
+ shared_state.startup_stage = "failed"
127
  print(f"[orchestrator] ERROR: {state.error}")
128
 
129
 
 
189
 
190
  if session is None or tools is None:
191
  return {"service": "mcp_server", "status": "down", "tools_count": 0,
192
+ "tools": [], "error": shared_state.telegram_error or "MCP server not initialized"}
193
  return {"service": "mcp_server", "status": "live", "tools_count": len(tools),
194
  "tools": [t["function"]["name"] for t in tools]}
195
 
 
204
  bot_status = "live" if b is not None else "down"
205
  llm_status = "loaded" if l is not None else "not_loaded"
206
 
207
+ model_path = shared_state.model_path or os.environ.get(
208
+ "MODEL_PATH", os.path.join(BASE_DIR, "models", "microsoft_Phi-4-mini-instruct-Q4_K_M.gguf")
209
+ )
210
  model_exists = os.path.exists(model_path)
211
 
212
  return {"service": "telegram_bot", "bot_status": bot_status, "llm_status": llm_status,
213
+ "model_path": model_path, "model_downloaded": model_exists,
214
+ "startup_stage": shared_state.startup_stage,
215
+ "error": shared_state.telegram_error}
216
 
217
 
218
  @app.get("/status")
shared_state.py CHANGED
@@ -6,3 +6,6 @@ mcp_session = None
6
  openai_tools = None
7
  telegram_bot = None
8
  llm_instance = None
 
 
 
 
6
  openai_tools = None
7
  telegram_bot = None
8
  llm_instance = None
9
+ model_path = None
10
+ telegram_error = None
11
+ startup_stage = "not_started"
telegram_agent.py CHANGED
@@ -41,6 +41,8 @@ PINECONE_INDEX = os.environ.get("PINECONE_INDEX", "jira-agent-memory")
41
  MCP_SERVER_SCRIPT = os.path.join(os.path.dirname(__file__), "jira_mcp_server.py")
42
  EMBED_DIM = 768
43
  MODEL_PATH = os.path.join(os.path.dirname(__file__), "models", "microsoft_Phi-4-mini-instruct-Q4_K_M.gguf")
 
 
44
 
45
  # ── Globals (lazy-initialized) ────────────────────────────────────────────────
46
  bot: Optional[telebot.TeleBot] = None
@@ -51,6 +53,7 @@ user_sessions = {}
51
  loop_ref = None
52
  mcp_session_ref = None
53
  openai_tools_ref = None
 
54
 
55
 
56
  # ── Lazy initializers ────────────────────────────────────────────────────────
@@ -80,7 +83,7 @@ def embed(text: str) -> list[float]:
80
 
81
 
82
  def _init_llm() -> Llama:
83
- global llm
84
  if llm is not None:
85
  return llm
86
 
@@ -89,9 +92,10 @@ def _init_llm() -> Llama:
89
  from huggingface_hub import hf_hub_download
90
  print(" Resolving model from HuggingFace cache...\n")
91
  cached_path = hf_hub_download(
92
- repo_id="bartowski/microsoft_Phi-4-mini-instruct-GGUF",
93
- filename="microsoft_Phi-4-mini-instruct-Q4_K_M.gguf",
94
  )
 
95
  print(f" Model cached at: {cached_path}")
96
  print(f" Loading model...\n")
97
 
@@ -108,35 +112,55 @@ def _init_pinecone():
108
  if pine_index is not None:
109
  return pine_index
110
  if not PINECONE_API_KEY:
111
- raise ValueError("Missing PINECONE_API_KEY")
112
- pc = Pinecone(api_key=PINECONE_API_KEY)
113
- existing = [i.name for i in pc.list_indexes()]
114
- if PINECONE_INDEX not in existing:
115
- print(f" Creating Pinecone index '{PINECONE_INDEX}'...")
116
- pc.create_index(name=PINECONE_INDEX, dimension=EMBED_DIM, metric="cosine",
117
- spec=ServerlessSpec(cloud="aws", region="us-east-1"))
118
- print(" Index created.\n")
119
- pine_index = pc.Index(PINECONE_INDEX)
 
 
 
 
 
120
  return pine_index
121
 
122
 
123
  def store(user_input, agent_reply, tool_name, tool_args, tool_result):
 
 
124
  doc = f"User: {user_input}\nAgent: {agent_reply}\nTool: {tool_name}\nArgs: {json.dumps(tool_args)}\nResult: {json.dumps(tool_result)}"
125
- pine_index.upsert(vectors=[{
126
- "id": str(uuid.uuid4()), "values": embed(doc),
127
- "metadata": {"doc": doc, "user_input": user_input, "agent_reply": agent_reply,
128
- "tool_name": tool_name, "tool_args": json.dumps(tool_args),
129
- "tool_result": json.dumps(tool_result),
130
- "timestamp": datetime.datetime.utcnow().isoformat()},
131
- }])
 
 
 
132
 
133
 
134
  def recall(user_input: str, top_k: int = 2) -> str:
135
- stats = pine_index.describe_index_stats()
 
 
 
 
 
 
136
  total = stats.get("total_vector_count", 0)
137
  if total == 0:
138
  return ""
139
- results = pine_index.query(vector=embed(user_input), top_k=min(top_k, total), include_metadata=True)
 
 
 
 
140
  chunks = [f"[past]\n{m['metadata'].get('user_input','')} β†’ {m['metadata'].get('tool_name','')}"
141
  for m in results.get("matches", []) if m.get('score', 0) > 0.5]
142
  return "\nRECENT SIMILAR (context only):\n" + "\n---\n".join(chunks) + "\n" if chunks else ""
@@ -291,17 +315,28 @@ async def start_telegram_with_mcp():
291
  global mcp_session_ref, openai_tools_ref, loop_ref
292
  loop_ref = asyncio.get_event_loop()
293
 
 
 
294
  # Lazy init all components
295
  _init_telegram_bot()
 
 
296
  _init_embedder()
 
 
297
  _init_llm()
 
 
298
  _init_pinecone()
 
 
299
  _register_handlers()
300
 
301
  server_params = StdioServerParameters(command=sys.executable, args=[MCP_SERVER_SCRIPT], env={**os.environ})
302
 
303
  async with stdio_client(server_params) as (read, write):
304
  async with ClientSession(read, write) as session:
 
305
  await session.initialize()
306
  tools_resp = await session.list_tools()
307
  mcp_tools = tools_resp.tools
@@ -313,10 +348,17 @@ async def start_telegram_with_mcp():
313
  shared_state.openai_tools = openai_tools
314
  shared_state.telegram_bot = bot
315
  shared_state.llm_instance = llm
 
 
316
  mcp_session_ref = session
317
  openai_tools_ref = openai_tools
318
 
319
- stats = pine_index.describe_index_stats()
 
 
 
 
 
320
  print("=" * 60)
321
  print(f" Telegram + MCP | Tools: {[t.name for t in mcp_tools]}")
322
  print(f" Memory: {stats.get('total_vector_count', 0)} vectors")
@@ -328,6 +370,7 @@ async def start_telegram_with_mcp():
328
 
329
  t = threading.Thread(target=run_telegram, daemon=True)
330
  t.start()
 
331
 
332
  try:
333
  while True:
@@ -335,6 +378,7 @@ async def start_telegram_with_mcp():
335
  except (KeyboardInterrupt, SystemExit):
336
  print("\nShutting down Telegram...")
337
  bot.stop_polling()
 
338
 
339
 
340
  # ── Standalone run ────────────────────────────────────────────────────────────
 
41
  MCP_SERVER_SCRIPT = os.path.join(os.path.dirname(__file__), "jira_mcp_server.py")
42
  EMBED_DIM = 768
43
  MODEL_PATH = os.path.join(os.path.dirname(__file__), "models", "microsoft_Phi-4-mini-instruct-Q4_K_M.gguf")
44
+ MODEL_REPO_ID = os.environ.get("MODEL_REPO_ID", "bartowski/microsoft_Phi-4-mini-instruct-GGUF")
45
+ MODEL_FILENAME = os.environ.get("MODEL_FILENAME", "microsoft_Phi-4-mini-instruct-Q4_K_M.gguf")
46
 
47
  # ── Globals (lazy-initialized) ────────────────────────────────────────────────
48
  bot: Optional[telebot.TeleBot] = None
 
53
  loop_ref = None
54
  mcp_session_ref = None
55
  openai_tools_ref = None
56
+ resolved_model_path = None
57
 
58
 
59
  # ── Lazy initializers ────────────────────────────────────────────────────────
 
83
 
84
 
85
  def _init_llm() -> Llama:
86
+ global llm, resolved_model_path
87
  if llm is not None:
88
  return llm
89
 
 
92
  from huggingface_hub import hf_hub_download
93
  print(" Resolving model from HuggingFace cache...\n")
94
  cached_path = hf_hub_download(
95
+ repo_id=MODEL_REPO_ID,
96
+ filename=MODEL_FILENAME,
97
  )
98
+ resolved_model_path = cached_path
99
  print(f" Model cached at: {cached_path}")
100
  print(f" Loading model...\n")
101
 
 
112
  if pine_index is not None:
113
  return pine_index
114
  if not PINECONE_API_KEY:
115
+ print(" Pinecone disabled: missing PINECONE_API_KEY.\n")
116
+ return None
117
+ try:
118
+ pc = Pinecone(api_key=PINECONE_API_KEY)
119
+ existing = [i.name for i in pc.list_indexes()]
120
+ if PINECONE_INDEX not in existing:
121
+ print(f" Creating Pinecone index '{PINECONE_INDEX}'...")
122
+ pc.create_index(name=PINECONE_INDEX, dimension=EMBED_DIM, metric="cosine",
123
+ spec=ServerlessSpec(cloud="aws", region="us-east-1"))
124
+ print(" Index created.\n")
125
+ pine_index = pc.Index(PINECONE_INDEX)
126
+ except Exception as e:
127
+ print(f" Pinecone disabled due to init error: {str(e)}\n")
128
+ pine_index = None
129
  return pine_index
130
 
131
 
132
  def store(user_input, agent_reply, tool_name, tool_args, tool_result):
133
+ if pine_index is None or _embedder is None:
134
+ return
135
  doc = f"User: {user_input}\nAgent: {agent_reply}\nTool: {tool_name}\nArgs: {json.dumps(tool_args)}\nResult: {json.dumps(tool_result)}"
136
+ try:
137
+ pine_index.upsert(vectors=[{
138
+ "id": str(uuid.uuid4()), "values": embed(doc),
139
+ "metadata": {"doc": doc, "user_input": user_input, "agent_reply": agent_reply,
140
+ "tool_name": tool_name, "tool_args": json.dumps(tool_args),
141
+ "tool_result": json.dumps(tool_result),
142
+ "timestamp": datetime.datetime.utcnow().isoformat()},
143
+ }])
144
+ except Exception as e:
145
+ print(f" Warning: failed to store memory: {str(e)}")
146
 
147
 
148
  def recall(user_input: str, top_k: int = 2) -> str:
149
+ if pine_index is None or _embedder is None:
150
+ return ""
151
+ try:
152
+ stats = pine_index.describe_index_stats()
153
+ except Exception as e:
154
+ print(f" Warning: failed to query memory stats: {str(e)}")
155
+ return ""
156
  total = stats.get("total_vector_count", 0)
157
  if total == 0:
158
  return ""
159
+ try:
160
+ results = pine_index.query(vector=embed(user_input), top_k=min(top_k, total), include_metadata=True)
161
+ except Exception as e:
162
+ print(f" Warning: failed to query memory: {str(e)}")
163
+ return ""
164
  chunks = [f"[past]\n{m['metadata'].get('user_input','')} β†’ {m['metadata'].get('tool_name','')}"
165
  for m in results.get("matches", []) if m.get('score', 0) > 0.5]
166
  return "\nRECENT SIMILAR (context only):\n" + "\n---\n".join(chunks) + "\n" if chunks else ""
 
315
  global mcp_session_ref, openai_tools_ref, loop_ref
316
  loop_ref = asyncio.get_event_loop()
317
 
318
+ import shared_state
319
+ shared_state.startup_stage = "init_telegram"
320
  # Lazy init all components
321
  _init_telegram_bot()
322
+
323
+ shared_state.startup_stage = "init_embedder"
324
  _init_embedder()
325
+
326
+ shared_state.startup_stage = "init_llm"
327
  _init_llm()
328
+
329
+ shared_state.startup_stage = "init_pinecone"
330
  _init_pinecone()
331
+
332
+ shared_state.startup_stage = "register_handlers"
333
  _register_handlers()
334
 
335
  server_params = StdioServerParameters(command=sys.executable, args=[MCP_SERVER_SCRIPT], env={**os.environ})
336
 
337
  async with stdio_client(server_params) as (read, write):
338
  async with ClientSession(read, write) as session:
339
+ shared_state.startup_stage = "init_mcp_session"
340
  await session.initialize()
341
  tools_resp = await session.list_tools()
342
  mcp_tools = tools_resp.tools
 
348
  shared_state.openai_tools = openai_tools
349
  shared_state.telegram_bot = bot
350
  shared_state.llm_instance = llm
351
+ shared_state.model_path = resolved_model_path or MODEL_PATH
352
+ shared_state.telegram_error = None
353
  mcp_session_ref = session
354
  openai_tools_ref = openai_tools
355
 
356
+ stats = {"total_vector_count": 0}
357
+ if pine_index is not None:
358
+ try:
359
+ stats = pine_index.describe_index_stats()
360
+ except Exception as e:
361
+ print(f" Warning: failed to read Pinecone stats: {str(e)}")
362
  print("=" * 60)
363
  print(f" Telegram + MCP | Tools: {[t.name for t in mcp_tools]}")
364
  print(f" Memory: {stats.get('total_vector_count', 0)} vectors")
 
370
 
371
  t = threading.Thread(target=run_telegram, daemon=True)
372
  t.start()
373
+ shared_state.startup_stage = "running"
374
 
375
  try:
376
  while True:
 
378
  except (KeyboardInterrupt, SystemExit):
379
  print("\nShutting down Telegram...")
380
  bot.stop_polling()
381
+ shared_state.startup_stage = "stopped"
382
 
383
 
384
  # ── Standalone run ────────────────────────────────────────────────────────────