From 7e5bfc3f8cc0803348d3807855c64127aa5bb347 Mon Sep 17 00:00:00 2001 From: Francia Riesco Date: Wed, 30 Apr 2025 18:05:00 -0400 Subject: [PATCH 1/6] remove logs --- src/backend/app_config.py | 9 ++------- src/backend/app_kernel.py | 1 - src/backend/kernel_agents/agent_base.py | 17 +++++++++-------- src/backend/kernel_agents/agent_factory.py | 5 ++++- src/backend/kernel_agents/generic_agent.py | 6 ------ 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/backend/app_config.py b/src/backend/app_config.py index 1878bd7d9..2923d6d61 100644 --- a/src/backend/app_config.py +++ b/src/backend/app_config.py @@ -183,7 +183,7 @@ def get_ai_project_client(self): self._ai_project_client = AIProjectClient.from_connection_string( credential=credential, conn_str=connection_string ) - logging.info("Successfully created AIProjectClient using connection string") + return self._ai_project_client except Exception as exc: logging.error("Failed to create AIProjectClient: %s", exc) @@ -221,10 +221,8 @@ async def create_azure_ai_agent( # First try to get an existing agent with this name as assistant_id try: - logging.info(f"Trying to retrieve existing agent with ID: {agent_name}") - existing_definition = await project_client.agents.get_agent(agent_name) - logging.info(f"Found existing agent with ID: {agent_name}") + existing_definition = await project_client.agents.get_agent(agent_name) # Create the agent instance directly with project_client and existing definition agent = AzureAIAgent( client=project_client, @@ -233,9 +231,6 @@ async def create_azure_ai_agent( plugins=tools, ) - logging.info( - f"Successfully loaded existing Azure AI Agent for {agent_name}" - ) return agent except Exception as e: # The Azure AI Projects SDK throws an exception when the agent doesn't exist diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index e717384d2..f811985e4 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -135,7 +135,6 @@ async def input_task_endpoint(input_task: InputTask, request: Request): # Convert input task to JSON for the kernel function, add user_id here - logging.info(f"Input task: {input_task}") # Use the planner to handle the task result = await group_chat_manager.handle_input_task(input_task) diff --git a/src/backend/kernel_agents/agent_base.py b/src/backend/kernel_agents/agent_base.py index a60d79735..badc18308 100644 --- a/src/backend/kernel_agents/agent_base.py +++ b/src/backend/kernel_agents/agent_base.py @@ -128,12 +128,15 @@ async def async_init(self): """ logging.info(f"Initializing agent: {self._agent_name}") # Create Azure AI Agent or fallback - self._agent = await config.create_azure_ai_agent( - kernel=self._kernel, - agent_name=self._agent_name, - instructions=self._system_message, - tools=self._tools, - ) + if not self._agent: + self._agent = await config.create_azure_ai_agent( + kernel=self._kernel, + agent_name=self._agent_name, + instructions=self._system_message, + tools=self._tools, + ) + else: + logging.info(f"Agent {self._agent_name} already initialized.") # Tools are registered with the kernel via get_tools_from_config return self @@ -248,8 +251,6 @@ async def handle_action_request(self, action_request: ActionRequest) -> str: ) return response.json() - logging.info(f"Task completed: {response_content}") - # Update step status step.status = StepStatus.completed step.agent_reply = response_content diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index 7a06f5efa..7135d0106 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -129,6 +129,9 @@ async def create_agent( session_id in cls._agent_cache and agent_type in cls._agent_cache[session_id] ): + logger.info( + f"Returning cached agent instance for session {session_id} and agent type {agent_type}" + ) return cls._agent_cache[session_id][agent_type] # Get the agent class @@ -302,7 +305,7 @@ async def create_all_agents( # Phase 2: Create the planner agent with agent_instances planner_agent = await cls.create_agent( - agent_type=planner_agent_type, + agent_type=AgentType.PLANNER, session_id=session_id, user_id=user_id, temperature=temperature, diff --git a/src/backend/kernel_agents/generic_agent.py b/src/backend/kernel_agents/generic_agent.py index a5eb9bb9c..73de7789b 100644 --- a/src/backend/kernel_agents/generic_agent.py +++ b/src/backend/kernel_agents/generic_agent.py @@ -43,14 +43,8 @@ def __init__( if not tools: # Get tools directly from GenericTools class tools_dict = GenericTools.get_all_kernel_functions() - logging.info( - f"GenericAgent: Got tools_dict with {len(tools_dict)} functions: {list(tools_dict.keys())}" - ) tools = [KernelFunction.from_method(func) for func in tools_dict.values()] - logging.info( - f"GenericAgent: Created {len(tools)} KernelFunctions from tools_dict" - ) # Use system message from config if not explicitly provided if not system_message: From a6b34df1a6235c2c82f306baa8608d9981e620bb Mon Sep 17 00:00:00 2001 From: Francia Riesco Date: Wed, 30 Apr 2025 18:22:11 -0400 Subject: [PATCH 2/6] preparing to storage thread_id --- src/backend/context/cosmos_memory_kernel.py | 204 ++++++++++++-------- src/backend/kernel_agents/planner_agent.py | 170 +--------------- src/backend/models/messages_kernel.py | 20 ++ 3 files changed, 145 insertions(+), 249 deletions(-) diff --git a/src/backend/context/cosmos_memory_kernel.py b/src/backend/context/cosmos_memory_kernel.py index de4639fc1..ec8e47dee 100644 --- a/src/backend/context/cosmos_memory_kernel.py +++ b/src/backend/context/cosmos_memory_kernel.py @@ -19,14 +19,17 @@ from app_config import config from models.messages_kernel import BaseDataModel, Plan, Session, Step, AgentMessage + # Add custom JSON encoder class for datetime objects class DateTimeEncoder(json.JSONEncoder): """Custom JSON encoder for handling datetime objects.""" + def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() return super().default(obj) + class CosmosMemoryContext(MemoryStoreBase): """A buffered chat completion context that saves messages and data models to Cosmos DB.""" @@ -43,19 +46,19 @@ def __init__( session_id: str, user_id: str, cosmos_container: str = None, - cosmos_endpoint: str = None, + cosmos_endpoint: str = None, cosmos_database: str = None, buffer_size: int = 100, initial_messages: Optional[List[ChatMessageContent]] = None, ) -> None: self._buffer_size = buffer_size self._messages = initial_messages or [] - + # Use values from AppConfig instance if not provided self._cosmos_container = cosmos_container or config.COSMOSDB_CONTAINER self._cosmos_endpoint = cosmos_endpoint or config.COSMOSDB_ENDPOINT self._cosmos_database = cosmos_database or config.COSMOSDB_DATABASE - + self._database = None self._container = None self.session_id = session_id @@ -70,11 +73,12 @@ async def initialize(self): if not self._database: # Create Cosmos client cosmos_client = CosmosClient( - self._cosmos_endpoint, - credential=DefaultAzureCredential() + self._cosmos_endpoint, credential=DefaultAzureCredential() + ) + self._database = cosmos_client.get_database_client( + self._cosmos_database ) - self._database = cosmos_client.get_database_client(self._cosmos_database) - + # Set up CosmosDB container self._container = await self._database.create_container_if_not_exists( id=self._cosmos_container, @@ -82,10 +86,12 @@ async def initialize(self): ) logging.info("Successfully connected to CosmosDB") except Exception as e: - logging.error(f"Failed to initialize CosmosDB container: {e}. Continuing without CosmosDB for testing.") + logging.error( + f"Failed to initialize CosmosDB container: {e}. Continuing without CosmosDB for testing." + ) # Do not raise to prevent test failures self._container = None - + self._initialized.set() # Helper method for awaiting initialization @@ -94,7 +100,7 @@ async def ensure_initialized(self): if not self._initialized.is_set(): # If the initialization hasn't been done, do it now await self.initialize() - + # If after initialization the container is still None, that means initialization failed if self._container is None: # Re-attempt initialization once in case the previous attempt failed @@ -102,10 +108,12 @@ async def ensure_initialized(self): await self.initialize() except Exception as e: logging.error(f"Re-initialization attempt failed: {e}") - + # If still not initialized, raise error if self._container is None: - raise RuntimeError("CosmosDB container is not available. Initialization failed.") + raise RuntimeError( + "CosmosDB container is not available. Initialization failed." + ) async def add_item(self, item: BaseDataModel) -> None: """Add a data model item to Cosmos DB.""" @@ -114,12 +122,12 @@ async def add_item(self, item: BaseDataModel) -> None: try: # Convert the model to a dict document = item.model_dump() - + # Handle datetime objects by converting them to ISO format strings for key, value in list(document.items()): if isinstance(value, datetime.datetime): document[key] = value.isoformat() - + # Now create the item with the serialized datetime values await self._container.create_item(body=document) logging.info(f"Item added to Cosmos DB - {document['id']}") @@ -134,12 +142,12 @@ async def update_item(self, item: BaseDataModel) -> None: try: # Convert the model to a dict document = item.model_dump() - + # Handle datetime objects by converting them to ISO format strings for key, value in list(document.items()): if isinstance(value, datetime.datetime): document[key] = value.isoformat() - + # Now upsert the item with the serialized datetime values await self._container.upsert_item(body=document) except Exception as e: @@ -223,12 +231,23 @@ async def get_plan_by_session(self, session_id: str) -> Optional[Plan]: plans = await self.query_items(query, parameters, Plan) return plans[0] if plans else None + async def get_thread_by_session(self, session_id: str) -> Optional[Any]: + """Retrieve a plan associated with a session.""" + query = "SELECT * FROM c WHERE c.session_id=@session_id AND c.user_id=@user_id AND c.data_type=@data_type" + parameters = [ + {"name": "@session_id", "value": session_id}, + {"name": "@data_type", "value": "thread"}, + {"name": "@user_id", "value": self.user_id}, + ] + threads = await self.query_items(query, parameters, Plan) + return threads[0] if threads else None + async def get_plan(self, plan_id: str) -> Optional[Plan]: """Retrieve a plan by its ID. - + Args: plan_id: The ID of the plan to retrieve - + Returns: The Plan object or None if not found """ @@ -266,13 +285,15 @@ async def get_steps_by_plan(self, plan_id: str) -> List[Step]: steps = await self.query_items(query, parameters, Step) return steps - async def get_steps_for_plan(self, plan_id: str, session_id: Optional[str] = None) -> List[Step]: + async def get_steps_for_plan( + self, plan_id: str, session_id: Optional[str] = None + ) -> List[Step]: """Retrieve all steps associated with a plan. - + Args: plan_id: The ID of the plan to retrieve steps for session_id: Optional session ID if known - + Returns: List of Step objects """ @@ -285,18 +306,20 @@ async def get_step(self, step_id: str, session_id: str) -> Optional[Step]: async def add_agent_message(self, message: AgentMessage) -> None: """Add an agent message to Cosmos DB. - + Args: message: The AgentMessage to add """ await self.add_item(message) - async def get_agent_messages_by_session(self, session_id: str) -> List[AgentMessage]: + async def get_agent_messages_by_session( + self, session_id: str + ) -> List[AgentMessage]: """Retrieve agent messages for a specific session. - + Args: session_id: The session ID to get messages for - + Returns: List of AgentMessage objects """ @@ -317,7 +340,7 @@ async def add_message(self, message: ChatMessageContent) -> None: # Ensure buffer size is maintained while len(self._messages) > self._buffer_size: self._messages.pop(0) - + message_dict = { "id": str(uuid.uuid4()), "session_id": self.session_id, @@ -326,7 +349,7 @@ async def add_message(self, message: ChatMessageContent) -> None: "content": { "role": message.role.value, "content": message.content, - "metadata": message.metadata + "metadata": message.metadata, }, "source": message.metadata.get("source", ""), } @@ -366,11 +389,11 @@ async def get_messages(self) -> List[ChatMessageContent]: chat_role = AuthorRole.SYSTEM elif role == "tool": # Equivalent to FunctionExecutionResultMessage chat_role = AuthorRole.TOOL - + message = ChatMessageContent( role=chat_role, content=content.get("content", ""), - metadata=content.get("metadata", {}) + metadata=content.get("metadata", {}), ) messages.append(message) return messages @@ -384,7 +407,7 @@ def get_chat_history(self) -> ChatHistory: for message in self._messages: history.add_message(message) return history - + async def save_chat_history(self, history: ChatHistory) -> None: """Save a ChatHistory object to the store.""" for message in history.messages: @@ -457,7 +480,7 @@ async def get_all_messages(self) -> List[Dict[str, Any]]: query = "SELECT * FROM c WHERE c.user_id=@user_id OFFSET 0 LIMIT @limit" parameters = [ {"name": "@user_id", "value": self.user_id}, - {"name": "@limit", "value": 100} + {"name": "@limit", "value": 100}, ] items = self._container.query_items(query=query, parameters=parameters) async for item in items: @@ -499,7 +522,7 @@ async def create_collection(self, collection_name: str) -> None: async def get_collections(self) -> List[str]: """Get all collections.""" await self.ensure_initialized() - + try: query = """ SELECT DISTINCT c.collection @@ -507,7 +530,7 @@ async def get_collections(self) -> List[str]: WHERE c.data_type = 'memory' AND c.session_id = @session_id """ parameters = [{"name": "@session_id", "value": self.session_id}] - + items = self._container.query_items(query=query, parameters=parameters) collections = [] async for item in items: @@ -526,7 +549,7 @@ async def does_collection_exist(self, collection_name: str) -> bool: async def delete_collection(self, collection_name: str) -> None: """Delete a collection.""" await self.ensure_initialized() - + try: query = """ SELECT c.id, c.session_id @@ -535,14 +558,13 @@ async def delete_collection(self, collection_name: str) -> None: """ parameters = [ {"name": "@collection", "value": collection_name}, - {"name": "@session_id", "value": self.session_id} + {"name": "@session_id", "value": self.session_id}, ] - + items = self._container.query_items(query=query, parameters=parameters) async for item in items: await self._container.delete_item( - item=item["id"], - partition_key=item["session_id"] + item=item["id"], partition_key=item["session_id"] ) except Exception as e: logging.exception(f"Failed to delete collection from Cosmos DB: {e}") @@ -559,14 +581,18 @@ async def upsert_memory_record(self, collection: str, record: MemoryRecord) -> s "description": record.description, "external_source_name": record.external_source_name, "additional_metadata": record.additional_metadata, - "embedding": record.embedding.tolist() if record.embedding is not None else None, - "key": record.key + "embedding": ( + record.embedding.tolist() if record.embedding is not None else None + ), + "key": record.key, } - + await self._container.upsert_item(body=memory_dict) return memory_dict["id"] - async def get_memory_record(self, collection: str, key: str, with_embedding: bool = False) -> Optional[MemoryRecord]: + async def get_memory_record( + self, collection: str, key: str, with_embedding: bool = False + ) -> Optional[MemoryRecord]: """Retrieve a memory record.""" query = """ SELECT * FROM c @@ -576,9 +602,9 @@ async def get_memory_record(self, collection: str, key: str, with_embedding: boo {"name": "@collection", "value": collection}, {"name": "@key", "value": key}, {"name": "@session_id", "value": self.session_id}, - {"name": "@data_type", "value": "memory"} + {"name": "@data_type", "value": "memory"}, ] - + items = self._container.query_items(query=query, parameters=parameters) async for item in items: return MemoryRecord( @@ -587,8 +613,12 @@ async def get_memory_record(self, collection: str, key: str, with_embedding: boo description=item["description"], external_source_name=item["external_source_name"], additional_metadata=item["additional_metadata"], - embedding=np.array(item["embedding"]) if with_embedding and "embedding" in item else None, - key=item["key"] + embedding=( + np.array(item["embedding"]) + if with_embedding and "embedding" in item + else None + ), + key=item["key"], ) return None @@ -602,36 +632,38 @@ async def remove_memory_record(self, collection: str, key: str) -> None: {"name": "@collection", "value": collection}, {"name": "@key", "value": key}, {"name": "@session_id", "value": self.session_id}, - {"name": "@data_type", "value": "memory"} + {"name": "@data_type", "value": "memory"}, ] - + items = self._container.query_items(query=query, parameters=parameters) async for item in items: - await self._container.delete_item(item=item["id"], partition_key=self.session_id) + await self._container.delete_item( + item=item["id"], partition_key=self.session_id + ) async def upsert_async(self, collection_name: str, record: Dict[str, Any]) -> str: """Helper method to insert documents directly.""" await self.ensure_initialized() - + try: if "session_id" not in record: record["session_id"] = self.session_id - + if "id" not in record: record["id"] = str(uuid.uuid4()) - + await self._container.upsert_item(body=record) return record["id"] except Exception as e: logging.exception(f"Failed to upsert item to Cosmos DB: {e}") return "" - + async def get_memory_records( self, collection: str, limit: int = 1000, with_embeddings: bool = False ) -> List[MemoryRecord]: """Get memory records from a collection.""" await self.ensure_initialized() - + try: query = """ SELECT * @@ -645,16 +677,16 @@ async def get_memory_records( parameters = [ {"name": "@collection", "value": collection}, {"name": "@session_id", "value": self.session_id}, - {"name": "@limit", "value": limit} + {"name": "@limit", "value": limit}, ] - + items = self._container.query_items(query=query, parameters=parameters) records = [] async for item in items: embedding = None if with_embeddings and "embedding" in item and item["embedding"]: embedding = np.array(item["embedding"]) - + record = MemoryRecord( id=item["id"], key=item.get("key", ""), @@ -662,7 +694,7 @@ async def get_memory_records( embedding=embedding, description=item.get("description", ""), additional_metadata=item.get("additional_metadata", ""), - external_source_name=item.get("external_source_name", "") + external_source_name=item.get("external_source_name", ""), ) records.append(record) return records @@ -674,7 +706,9 @@ async def upsert(self, collection_name: str, record: MemoryRecord) -> str: """Upsert a memory record into the store.""" return await self.upsert_memory_record(collection_name, record) - async def upsert_batch(self, collection_name: str, records: List[MemoryRecord]) -> List[str]: + async def upsert_batch( + self, collection_name: str, records: List[MemoryRecord] + ) -> List[str]: """Upsert a batch of memory records into the store.""" result_ids = [] for record in records: @@ -682,11 +716,15 @@ async def upsert_batch(self, collection_name: str, records: List[MemoryRecord]) result_ids.append(record_id) return result_ids - async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord: + async def get( + self, collection_name: str, key: str, with_embedding: bool = False + ) -> MemoryRecord: """Get a memory record from the store.""" return await self.get_memory_record(collection_name, key, with_embedding) - async def get_batch(self, collection_name: str, keys: List[str], with_embeddings: bool = False) -> List[MemoryRecord]: + async def get_batch( + self, collection_name: str, keys: List[str], with_embeddings: bool = False + ) -> List[MemoryRecord]: """Get a batch of memory records from the store.""" results = [] for key in keys: @@ -705,51 +743,49 @@ async def remove_batch(self, collection_name: str, keys: List[str]) -> None: await self.remove_memory_record(collection_name, key) async def get_nearest_match( - self, - collection_name: str, - embedding: np.ndarray, - limit: int = 1, - min_relevance_score: float = 0.0, - with_embeddings: bool = False + self, + collection_name: str, + embedding: np.ndarray, + limit: int = 1, + min_relevance_score: float = 0.0, + with_embeddings: bool = False, ) -> Tuple[MemoryRecord, float]: """Get the nearest match to the given embedding.""" matches = await self.get_nearest_matches( - collection_name, - embedding, - limit, - min_relevance_score, - with_embeddings + collection_name, embedding, limit, min_relevance_score, with_embeddings ) return matches[0] if matches else (None, 0.0) async def get_nearest_matches( - self, - collection_name: str, - embedding: np.ndarray, - limit: int = 1, - min_relevance_score: float = 0.0, - with_embeddings: bool = False + self, + collection_name: str, + embedding: np.ndarray, + limit: int = 1, + min_relevance_score: float = 0.0, + with_embeddings: bool = False, ) -> List[Tuple[MemoryRecord, float]]: """Get the nearest matches to the given embedding.""" await self.ensure_initialized() - + try: - records = await self.get_memory_records(collection_name, limit=100, with_embeddings=True) - + records = await self.get_memory_records( + collection_name, limit=100, with_embeddings=True + ) + results = [] for record in records: if record.embedding is not None: similarity = np.dot(embedding, record.embedding) / ( np.linalg.norm(embedding) * np.linalg.norm(record.embedding) ) - + if similarity >= min_relevance_score: if not with_embeddings: record.embedding = None results.append((record, float(similarity))) - + results.sort(key=lambda x: x[1], reverse=True) return results[:limit] except Exception as e: logging.exception(f"Failed to get nearest matches from Cosmos DB: {e}") - return [] \ No newline at end of file + return [] diff --git a/src/backend/kernel_agents/planner_agent.py b/src/backend/kernel_agents/planner_agent.py index 1585f1485..bd4fcf88c 100644 --- a/src/backend/kernel_agents/planner_agent.py +++ b/src/backend/kernel_agents/planner_agent.py @@ -365,75 +365,17 @@ async def _create_structured_plan( # Try various parsing approaches in sequence try: # 1. First attempt: Try to parse the raw response directly - try: - parsed_result = PlannerResponsePlan.parse_raw(response_content) - logging.info("Successfully parsed response with direct parsing") - logging.info(f"\n\n\n\n") - logging.info(f"Parsed result: {parsed_result}") - logging.info(f"\n\n\n\n") - except Exception as parse_error: - logging.warning(f"Failed direct parse: {parse_error}") - - # 2. Try to extract JSON from markdown code blocks - json_match = re.search( - r"```(?:json)?\s*(.*?)\s*```", response_content, re.DOTALL - ) - if json_match: - json_content = json_match.group(1) - logging.info(f"Found JSON in code block, attempting to parse") - try: - parsed_result = PlannerResponsePlan.parse_raw(json_content) - logging.info("Successfully parsed JSON from code block") - except Exception as code_block_error: - logging.warning( - f"Failed to parse JSON in code block: {code_block_error}" - ) - # Try parsing as dict first, then convert to model - try: - json_dict = json.loads(json_content) - parsed_result = PlannerResponsePlan.parse_obj(json_dict) - logging.info( - "Successfully parsed JSON dict from code block" - ) - except Exception as dict_error: - logging.warning( - f"Failed to parse JSON dict from code block: {dict_error}" - ) - - # 3. Look for patterns like { ... } that might contain JSON - if parsed_result is None: - json_pattern = r'\{.*?"initial_goal".*?"steps".*?\}' - alt_match = re.search(json_pattern, response_content, re.DOTALL) - if alt_match: - potential_json = alt_match.group(0) - logging.info( - f"Found potential JSON pattern in text, attempting to parse" - ) - try: - json_dict = json.loads(potential_json) - parsed_result = PlannerResponsePlan.parse_obj(json_dict) - logging.info( - "Successfully parsed JSON using regex pattern extraction" - ) - except Exception as pattern_error: - logging.warning( - f"Failed to parse JSON pattern: {pattern_error}" - ) - + parsed_result = PlannerResponsePlan.parse_raw(response_content) if parsed_result is None: # If all parsing attempts fail, create a fallback plan from the text content - logging.warning( - "All JSON parsing attempts failed, creating fallback plan from text" - ) - return await self._create_fallback_plan_from_text( - input_task, response_content + logging.info( + "All parsing attempts failed, creating fallback plan from text content" ) + raise ValueError("Failed to parse JSON response") except Exception as parsing_exception: logging.exception(f"Error during parsing attempts: {parsing_exception}") - return await self._create_fallback_plan_from_text( - input_task, response_content - ) + raise ValueError("Failed to parse JSON response") # At this point, we have a valid parsed_result @@ -579,108 +521,6 @@ async def _create_structured_plan( return dummy_plan, [dummy_step, clarification_step] - async def _create_fallback_plan_from_text( - self, input_task: InputTask, text_content: str - ) -> Tuple[Plan, List[Step]]: - """Create a plan from unstructured text when JSON parsing fails. - - Args: - input_task: The input task - text_content: The text content from the LLM - - Returns: - Tuple containing the created plan and list of steps - """ - logging.info("Creating fallback plan from text content") - - # Extract goal from the text (first line or use input task description) - goal_match = re.search( - r"(?:Goal|Initial Goal|Plan):\s*(.+?)(?:\n|$)", text_content - ) - goal = goal_match.group(1).strip() if goal_match else input_task.description - - # Create the plan - plan = Plan( - id=str(uuid.uuid4()), - session_id=input_task.session_id, - user_id=self._user_id, - initial_goal=goal, - overall_status=PlanStatus.in_progress, - summary=f"Plan created from {input_task.description}", - ) - - # Store the plan - await self._memory_store.add_plan(plan) - - # Parse steps using regex - step_pattern = re.compile( - r"(?:Step|)\s*(\d+)[:.]\s*\*?\*?(?:Agent|):\s*\*?([^:*\n]+)\*?[:\s]*(.+?)(?=(?:Step|)\s*\d+[:.]\s*|$)", - re.DOTALL, - ) - matches = step_pattern.findall(text_content) - - if not matches: - # Fallback to simpler pattern - step_pattern = re.compile( - r"(\d+)[.:\)]\s*([^:]*?):\s*(.*?)(?=\d+[.:\)]|$)", re.DOTALL - ) - matches = step_pattern.findall(text_content) - - # If still no matches, look for bullet points or numbered lists - if not matches: - step_pattern = re.compile( - r"[•\-*]\s*([^:]*?):\s*(.*?)(?=[•\-*]|$)", re.DOTALL - ) - bullet_matches = step_pattern.findall(text_content) - if bullet_matches: - # Convert bullet matches to our expected format (number, agent, action) - matches = [] - for i, (agent_text, action) in enumerate(bullet_matches, 1): - matches.append((str(i), agent_text.strip(), action.strip())) - - steps = [] - # If we found no steps at all, create at least one generic step - if not matches: - generic_step = Step( - id=str(uuid.uuid4()), - plan_id=plan.id, - session_id=input_task.session_id, - user_id=self._user_id, - action=f"Process the request: {input_task.description}", - agent="GenericAgent", - status=StepStatus.planned, - human_approval_status=HumanFeedbackStatus.requested, - ) - await self._memory_store.add_step(generic_step) - steps.append(generic_step) - else: - for match in matches: - number = match[0].strip() - agent_text = match[1].strip() - action = match[2].strip() - - # Clean up agent name - agent = re.sub(r"\s+", "", agent_text) - if not agent or agent not in self._available_agents: - agent = "GenericAgent" # Default to GenericAgent if not recognized - - # Create and store the step - step = Step( - id=str(uuid.uuid4()), - plan_id=plan.id, - session_id=input_task.session_id, - user_id=self._user_id, - action=action, - agent=agent, - status=StepStatus.planned, - human_approval_status=HumanFeedbackStatus.requested, - ) - - await self._memory_store.add_step(step) - steps.append(step) - - return plan, steps - def _generate_args(self, objective: str) -> any: """Generate instruction for the LLM to create a plan. diff --git a/src/backend/models/messages_kernel.py b/src/backend/models/messages_kernel.py index f9aaec79d..faca41d39 100644 --- a/src/backend/models/messages_kernel.py +++ b/src/backend/models/messages_kernel.py @@ -198,6 +198,26 @@ class Step(BaseDataModel): updated_action: Optional[str] = None +class ThreadIdAgent(BaseDataModel): + """Represents an individual thread_id.""" + + data_type: Literal["thread"] = Field("thread", Literal=True) + session_id: str # Partition key + user_id: str + thread_id: str + + +class AzureIdAgent(BaseDataModel): + """Represents an individual thread_id.""" + + data_type: Literal["agent"] = Field("agent", Literal=True) + session_id: str # Partition key + user_id: str + action: str + agent: AgentType + agent_id: str + + class PlanWithSteps(Plan): """Plan model that includes the associated steps.""" From 3419a5e1c715dc024648de60a99ee6e4a013fa91 Mon Sep 17 00:00:00 2001 From: Francia Riesco Date: Wed, 30 Apr 2025 18:37:01 -0400 Subject: [PATCH 3/6] Update agent_factory.py --- src/backend/kernel_agents/agent_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index 7135d0106..b39fcf11e 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -325,7 +325,7 @@ async def create_all_agents( # Phase 3: Create group chat manager with all agents including the planner group_chat_manager = await cls.create_agent( - agent_type=group_chat_manager_type, + agent_type=AgentType.GROUP_CHAT_MANAGER, session_id=session_id, user_id=user_id, temperature=temperature, From c0da85f192a5c5b6733f738402d3018b3d762619 Mon Sep 17 00:00:00 2001 From: Francia Riesco Date: Wed, 30 Apr 2025 20:43:53 -0400 Subject: [PATCH 4/6] cleaning up logs --- src/backend/kernel_agents/agent_factory.py | 3 +- src/backend/kernel_agents/planner_agent.py | 51 ++++++++++------------ 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index b39fcf11e..45541ae8a 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -214,13 +214,12 @@ async def create_agent( if k in valid_keys } agent = agent_class(**filtered_kwargs) - logger.info(f"[DEBUG] Agent object after instantiation: {agent}") + # Initialize the agent asynchronously if it has async_init if hasattr(agent, "async_init") and inspect.iscoroutinefunction( agent.async_init ): init_result = await agent.async_init() - logger.info(f"[DEBUG] Result of agent.async_init(): {init_result}") except Exception as e: logger.error( diff --git a/src/backend/kernel_agents/planner_agent.py b/src/backend/kernel_agents/planner_agent.py index bd4fcf88c..0272ad0bb 100644 --- a/src/backend/kernel_agents/planner_agent.py +++ b/src/backend/kernel_agents/planner_agent.py @@ -103,10 +103,6 @@ def __init__( self._agent_tools_list = agent_tools_list or [] self._agent_instances = agent_instances or {} - # Create the Azure AI Agent for planning operations - # This will be initialized in async_init - self._azure_ai_agent = None - @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. @@ -130,22 +126,22 @@ async def async_init(self) -> None: # Get the agent template - defined in function to allow for easy updates instructions = self._get_template() - - # Create the Azure AI Agent using AppConfig with string instructions - self._azure_ai_agent = await config.create_azure_ai_agent( - kernel=self._kernel, - agent_name=self._agent_name, - instructions=instructions, # Pass the formatted string, not an object - temperature=0.0, - response_format=ResponseFormatJsonSchemaType( - json_schema=ResponseFormatJsonSchema( - name=PlannerResponsePlan.__name__, - description=f"respond with {PlannerResponsePlan.__name__.lower()}", - schema=PlannerResponsePlan.model_json_schema(), - ) - ), - ) - logging.info("Successfully created Azure AI Agent for PlannerAgent") + if not self._agent: + # Create the Azure AI Agent using AppConfig with string instructions + self._agent = await config.create_azure_ai_agent( + kernel=self._kernel, + agent_name=self._agent_name, + instructions=instructions, # Pass the formatted string, not an object + temperature=0.0, + response_format=ResponseFormatJsonSchemaType( + json_schema=ResponseFormatJsonSchema( + name=PlannerResponsePlan.__name__, + description=f"respond with {PlannerResponsePlan.__name__.lower()}", + schema=PlannerResponsePlan.model_json_schema(), + ) + ), + ) + logging.info("Successfully created Azure AI Agent for PlannerAgent") return True except Exception as e: logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") @@ -300,22 +296,16 @@ async def _create_structured_plan( """ try: # Generate the instruction for the LLM - logging.info("Generating instruction for the LLM") - logging.info(f"Input: {input_task}") - logging.info(f"Available agents: {self._available_agents}") # Get template variables as a dictionary args = self._generate_args(input_task.description) - logging.info(f"Generated args: {args}") - logging.info(f"Creating plan for task: '{input_task.description}'") - logging.info(f"Using available agents: {self._available_agents}") # Use the Azure AI Agent instead of direct function invocation - if self._azure_ai_agent is None: + if self._agent is None: # Initialize the agent if it's not already done await self.async_init() - if self._azure_ai_agent is None: + if self._agent is None: raise RuntimeError("Failed to initialize Azure AI Agent for planning") # Log detailed information about the instruction being sent @@ -336,7 +326,7 @@ async def _create_structured_plan( # ) thread = None # thread = self.client.agents.create_thread(thread_id=input_task.session_id) - async_generator = self._azure_ai_agent.invoke( + async_generator = self._agent.invoke( arguments=kernel_args, settings={ "temperature": 0.0, # Keep temperature low for consistent planning @@ -705,6 +695,9 @@ def _generate_args(self, objective: str) -> any: ) # Convert the tools list to a string representation + logging.info(f"Tools list: {len(tools_list)}") + logging + tools_str = str(tools_list) # Return a dictionary with template variables From 4bc136c3203931d4074862a2608e556c23ec9a04 Mon Sep 17 00:00:00 2001 From: Francia Riesco Date: Wed, 30 Apr 2025 21:47:46 -0400 Subject: [PATCH 5/6] remove kernel as parameter for the agent --- src/backend/app_config.py | 3 --- src/backend/app_kernel.py | 8 +----- src/backend/kernel_agents/agent_base.py | 25 ------------------- src/backend/kernel_agents/agent_factory.py | 8 +----- src/backend/kernel_agents/generic_agent.py | 3 --- .../kernel_agents/group_chat_manager.py | 2 -- src/backend/kernel_agents/hr_agent.py | 2 -- src/backend/kernel_agents/human_agent.py | 2 -- src/backend/kernel_agents/marketing_agent.py | 3 --- src/backend/kernel_agents/planner_agent.py | 4 --- .../kernel_agents/procurement_agent.py | 2 -- src/backend/kernel_agents/product_agent.py | 3 --- .../kernel_agents/tech_support_agent.py | 2 -- 13 files changed, 2 insertions(+), 65 deletions(-) diff --git a/src/backend/app_config.py b/src/backend/app_config.py index 2923d6d61..e05e8393e 100644 --- a/src/backend/app_config.py +++ b/src/backend/app_config.py @@ -191,7 +191,6 @@ def get_ai_project_client(self): async def create_azure_ai_agent( self, - kernel: Kernel, agent_name: str, instructions: str, tools: Optional[List[KernelFunction]] = None, @@ -227,7 +226,6 @@ async def create_azure_ai_agent( agent = AzureAIAgent( client=project_client, definition=existing_definition, - kernel=kernel, plugins=tools, ) @@ -258,7 +256,6 @@ async def create_azure_ai_agent( agent = AzureAIAgent( client=project_client, definition=agent_definition, - kernel=kernel, plugins=tools, ) diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index f811985e4..ccca8a743 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -127,7 +127,6 @@ async def input_task_endpoint(input_task: InputTask, request: Request): agents = await AgentFactory.create_all_agents( session_id=input_task.session_id, user_id=user_id, - kernel=kernel, memory_store=memory_store, ) @@ -251,10 +250,7 @@ async def human_feedback_endpoint(human_feedback: HumanFeedback, request: Reques human_feedback.session_id, user_id ) agents = await AgentFactory.create_all_agents( - session_id=human_feedback.session_id, - user_id=user_id, - memory_store=memory_store, - kernel=kernel, + session_id=human_feedback.session_id, user_id=user_id, memory_store=memory_store ) # Send the feedback to the human agent @@ -341,7 +337,6 @@ async def human_clarification_endpoint( session_id=human_clarification.session_id, user_id=user_id, memory_store=memory_store, - kernel=kernel, ) # Send the feedback to the human agent @@ -435,7 +430,6 @@ async def approve_step_endpoint( agents = await AgentFactory.create_all_agents( session_id=human_feedback.session_id, user_id=user_id, - kernel=kernel, memory_store=memory_store, ) diff --git a/src/backend/kernel_agents/agent_base.py b/src/backend/kernel_agents/agent_base.py index badc18308..ce6381d85 100644 --- a/src/backend/kernel_agents/agent_base.py +++ b/src/backend/kernel_agents/agent_base.py @@ -10,26 +10,6 @@ from semantic_kernel.functions.kernel_function_decorator import kernel_function from semantic_kernel.agents import AzureAIAgentThread -# Updated imports for compatibility -try: - # Try importing from newer structure first - from semantic_kernel.contents import ChatHistory, ChatMessageContent -except ImportError: - # Fall back to older structure for compatibility - class ChatMessageContent: - """Compatibility class for older SK versions.""" - - def __init__(self, role="", content="", name=None): - self.role = role - self.content = content - self.name = name - - class ChatHistory: - """Compatibility class for older SK versions.""" - - def __init__(self): - self.messages = [] - # Import the new AppConfig instance from app_config import config @@ -53,7 +33,6 @@ class BaseAgent(AzureAIAgent): def __init__( self, agent_name: str, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -66,7 +45,6 @@ def __init__( Args: agent_name: The name of the agent - kernel: The semantic kernel instance session_id: The session ID user_id: The user ID memory_store: The memory context for storing agent state @@ -82,7 +60,6 @@ def __init__( # Call AzureAIAgent constructor with required client and definition super().__init__( - kernel=kernel, deployment_name=None, # Set as needed plugins=tools, # Use the loaded plugins, endpoint=None, # Set as needed @@ -96,7 +73,6 @@ def __init__( # Store instance variables self._agent_name = agent_name - self._kernel = kernel self._session_id = session_id self._user_id = user_id self._memory_store = memory_store @@ -130,7 +106,6 @@ async def async_init(self): # Create Azure AI Agent or fallback if not self._agent: self._agent = await config.create_azure_ai_agent( - kernel=self._kernel, agent_name=self._agent_name, instructions=self._system_message, tools=self._tools, diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index 45541ae8a..5fd1eda52 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -91,7 +91,6 @@ async def create_agent( user_id: str, temperature: float = 0.0, memory_store: Optional[CosmosMemoryContext] = None, - kernel: Optional[Kernel] = None, system_message: Optional[str] = None, response_format: Optional[Any] = None, **kwargs, @@ -143,9 +142,7 @@ async def create_agent( if memory_store is None: memory_store = CosmosMemoryContext(session_id, user_id) - # Create a kernel using the AppConfig instance - if kernel is None: - kernel = config.create_kernel() + kernel = config.create_kernel() # Use default system message if none provided if system_message is None: @@ -201,7 +198,6 @@ async def create_agent( k: v for k, v in { "agent_name": agent_type_str, - "kernel": kernel, "session_id": session_id, "user_id": user_id, "memory_store": memory_store, @@ -241,7 +237,6 @@ async def create_all_agents( user_id: str, temperature: float = 0.0, memory_store: Optional[CosmosMemoryContext] = None, - kernel: Optional[Kernel] = None, ) -> Dict[AgentType, BaseAgent]: """Create all agent types for a session in a specific order. @@ -284,7 +279,6 @@ async def create_all_agents( user_id=user_id, temperature=temperature, memory_store=memory_store, - kernel=kernel, ) # Create agent name to instance mapping for the planner diff --git a/src/backend/kernel_agents/generic_agent.py b/src/backend/kernel_agents/generic_agent.py index 5e675b9ef..17c7c8eeb 100644 --- a/src/backend/kernel_agents/generic_agent.py +++ b/src/backend/kernel_agents/generic_agent.py @@ -14,7 +14,6 @@ class GenericAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -27,7 +26,6 @@ def __init__( """Initialize the Generic Agent. Args: - kernel: The semantic kernel instance session_id: The current session identifier user_id: The user identifier memory_store: The Cosmos memory context @@ -55,7 +53,6 @@ def __init__( # Call the parent initializer super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/group_chat_manager.py b/src/backend/kernel_agents/group_chat_manager.py index 312c8d93c..b45e9a3e6 100644 --- a/src/backend/kernel_agents/group_chat_manager.py +++ b/src/backend/kernel_agents/group_chat_manager.py @@ -40,7 +40,6 @@ class GroupChatManager(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -76,7 +75,6 @@ def __init__( # Initialize the base agent super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/hr_agent.py b/src/backend/kernel_agents/hr_agent.py index 01b86709f..2957ca2c7 100644 --- a/src/backend/kernel_agents/hr_agent.py +++ b/src/backend/kernel_agents/hr_agent.py @@ -17,7 +17,6 @@ class HrAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -55,7 +54,6 @@ def __init__( super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/human_agent.py b/src/backend/kernel_agents/human_agent.py index 1902b9981..8bc2a6783 100644 --- a/src/backend/kernel_agents/human_agent.py +++ b/src/backend/kernel_agents/human_agent.py @@ -28,7 +28,6 @@ class HumanAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -67,7 +66,6 @@ def __init__( super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/marketing_agent.py b/src/backend/kernel_agents/marketing_agent.py index c190c9a48..8c1ed80cd 100644 --- a/src/backend/kernel_agents/marketing_agent.py +++ b/src/backend/kernel_agents/marketing_agent.py @@ -16,14 +16,12 @@ class MarketingAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, tools: Optional[List[KernelFunction]] = None, system_message: Optional[str] = None, agent_name: str = AgentType.MARKETING.value, - config_path: Optional[str] = None, client=None, definition=None, ) -> None: @@ -55,7 +53,6 @@ def __init__( super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/planner_agent.py b/src/backend/kernel_agents/planner_agent.py index 0272ad0bb..4138a2d8c 100644 --- a/src/backend/kernel_agents/planner_agent.py +++ b/src/backend/kernel_agents/planner_agent.py @@ -43,7 +43,6 @@ class PlannerAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -59,7 +58,6 @@ def __init__( """Initialize the Planner Agent. Args: - kernel: The semantic kernel instance session_id: The current session identifier user_id: The user identifier memory_store: The Cosmos memory context @@ -80,7 +78,6 @@ def __init__( # Initialize the base agent super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, @@ -129,7 +126,6 @@ async def async_init(self) -> None: if not self._agent: # Create the Azure AI Agent using AppConfig with string instructions self._agent = await config.create_azure_ai_agent( - kernel=self._kernel, agent_name=self._agent_name, instructions=instructions, # Pass the formatted string, not an object temperature=0.0, diff --git a/src/backend/kernel_agents/procurement_agent.py b/src/backend/kernel_agents/procurement_agent.py index 5706ea0c7..cc3261c35 100644 --- a/src/backend/kernel_agents/procurement_agent.py +++ b/src/backend/kernel_agents/procurement_agent.py @@ -16,7 +16,6 @@ class ProcurementAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -54,7 +53,6 @@ def __init__( super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/product_agent.py b/src/backend/kernel_agents/product_agent.py index a47a90df0..6251de25e 100644 --- a/src/backend/kernel_agents/product_agent.py +++ b/src/backend/kernel_agents/product_agent.py @@ -19,14 +19,12 @@ class ProductAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, tools: Optional[List[KernelFunction]] = None, system_message: Optional[str] = None, agent_name: str = AgentType.PRODUCT.value, - config_path: Optional[str] = None, client=None, definition=None, ) -> None: @@ -59,7 +57,6 @@ def __init__( super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, diff --git a/src/backend/kernel_agents/tech_support_agent.py b/src/backend/kernel_agents/tech_support_agent.py index 7b0b33df8..b2c90b872 100644 --- a/src/backend/kernel_agents/tech_support_agent.py +++ b/src/backend/kernel_agents/tech_support_agent.py @@ -16,7 +16,6 @@ class TechSupportAgent(BaseAgent): def __init__( self, - kernel: sk.Kernel, session_id: str, user_id: str, memory_store: CosmosMemoryContext, @@ -55,7 +54,6 @@ def __init__( super().__init__( agent_name=agent_name, - kernel=kernel, session_id=session_id, user_id=user_id, memory_store=memory_store, From 5cb8a48f8f06a024086b7c73b6cfc3e01307d4f8 Mon Sep 17 00:00:00 2001 From: Francia Riesco Date: Wed, 30 Apr 2025 22:09:16 -0400 Subject: [PATCH 6/6] Update agent_factory.py --- src/backend/kernel_agents/agent_factory.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index 5fd1eda52..f45ef6ac9 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -142,8 +142,6 @@ async def create_agent( if memory_store is None: memory_store = CosmosMemoryContext(session_id, user_id) - kernel = config.create_kernel() - # Use default system message if none provided if system_message is None: system_message = cls._agent_system_messages.get(