import re, ast import logging import re import json import logging from typing import List, Dict from llama_index.core.tools.types import ToolOutput from src.agent.agenttools import ChatBotFunctionTools class LearningAgent: def __init__(self, llm_value: str): tool_builder = ChatBotFunctionTools(llm_type=llm_value) self.llm = tool_builder.generator self.tools = tool_builder.get_tools() self.chat_history: List[Dict[str, str]] = [] self.max_history = 20 def extract_json_from_markdown(self, markdown_text: str): try: match = re.search(r"```json\s*(\{.*?\})\s*```", markdown_text, re.DOTALL) if not match: raise ValueError("No JSON block found in markdown") return json.loads(match.group(1)) except Exception as e: logging.error(f"Could not parse JSON: {e}") return None def determine_tools(self, query: str): previous_questions = "\n".join( [msg["content"] for msg in self.chat_history if msg["role"] == "user"] ) if self.chat_history else "No previous questions" prompt = f"""Analyze this learning query and select appropriate tools also form the condensed query based on previous_questions and Query: For Choosing tool properly analyze the condensed query and then decide. Choose multiple if its necessary based on condensed query Its mandatory to select to atleast 1 tool. If asked for any query related to code generation dont include theory rather give code and its explaination Query: {query} Previous Query: {previous_questions} Available Tools: {list(self.tools.keys())} Return dictionary of tool names and condensed query as dictionary in the below format: ```json {{ "condensed_query": condensed query considering chat history and user query as string, "tool_names": tool names as comma-separated list }} ``` Do Not add additional text""" response = self.llm.complete(prompt) try: response = response.text except: response = response parsed = self.extract_json_from_markdown(response) condensed_query = parsed["condensed_query"] tools = [t.strip() for t in parsed['tool_names'].split(",") if t.strip() in self.tools] return tools if tools else ['llm_query'], condensed_query def execute_tools(self, tools: List[str], query: str) -> tuple[str, str]: tool_results, content_results = [], [] for tool in tools: try: tool_output = self.tools[tool](query) content = tool_output.content if isinstance(tool_output, ToolOutput) else str(tool_output) tool_results.append(tool) content_results.append(content) except Exception as e: logging.error(f"Tool {tool} failed: {str(e)}") tool_results.append(tool) content_results.append(f"Error: {str(e)}") if len(tool_results) > 1: combined = "\n\n".join(f"**{t}**:\n{c}" for t, c in zip(tool_results, content_results)) explanation = self.tools["concept_combiner"](content_results) return "multiple", f"{combined}\n\n**Combined Analysis**:\n{explanation}" return tool_results[0], content_results[0] def process_query(self, query: str) -> tuple[List[Dict[str, str]], str, str]: tools, condensed_query = self.determine_tools(query) tool_used, response = self.execute_tools(tools, condensed_query) self.chat_history += [{"role": "user", "content": query}, {"role": "assistant", "content": response}] self.chat_history = self.chat_history[-(self.max_history * 2):] return self.chat_history, tool_used, response