feat(phase1): tool reranking, rejection, reflection, context repository, extended metrics, agent recommender - #10
feat(phase1): tool reranking, rejection, reflection, context repository, extended metrics, agent recommender#10cherninkiy wants to merge 1 commit into
Conversation
- ContextPromptRepository: Prompt templates with variables, semantic search, 5 default templates, success tracking - Tool Reflection Cycle: 8 error categories, auto-fix suggestions, iterative improvement, automatic try-except wrapping - Tests: 51 total (21 context repository, 30 reflection) - Docs: Recommender MVP completed, roadmap moved to docs/ - Fixes: PR review issues, flake8 compliance, .gitignore cleanup - Refactor: Simplified agents/init.py
There was a problem hiding this comment.
Code Review
This pull request implements Phase 1 intelligence modules, featuring a ContextPromptRepository for semantic prompt retrieval, a ReflectionLoop for automated error analysis and correction, and an AgentRecommender system. The update includes enhancements to the metrics logging system and comprehensive unit tests. Reviewer feedback identifies several performance optimization opportunities, such as consolidating loops in trajectory calculations and pre-normalizing error patterns. Additionally, the feedback recommends moving inline imports and constants to the module level for PEP 8 compliance and adopting timezone-aware UTC timestamps for consistency across the codebase.
| count = len(events_list) | ||
| avg_success = sum(float(e.get("success_score", 0.0)) for e in events_list) / count | ||
| avg_latency = sum(float(e.get("execution_time_ms", 0.0)) for e in events_list) / count | ||
| avg_step = ( | ||
| sum(float(e.get("step_number", 0.0) or 0.0) for e in events_list) / count | ||
| ) |
There was a problem hiding this comment.
The trajectory metrics are calculated using multiple generator expressions, which results in iterating over events_list three times. This can be optimized into a single loop for better performance, especially as the number of events grows.
| count = len(events_list) | |
| avg_success = sum(float(e.get("success_score", 0.0)) for e in events_list) / count | |
| avg_latency = sum(float(e.get("execution_time_ms", 0.0)) for e in events_list) / count | |
| avg_step = ( | |
| sum(float(e.get("step_number", 0.0) or 0.0) for e in events_list) / count | |
| ) | |
| count = len(events_list) | |
| total_success = 0.0 | |
| total_latency = 0.0 | |
| total_step = 0.0 | |
| for e in events_list: | |
| total_success += float(e.get("success_score", 0.0)) | |
| total_latency += float(e.get("execution_time_ms", 0.0)) | |
| total_step += float(e.get("step_number", 0.0) or 0.0) | |
| return [ | |
| max(0.0, min(1.0, total_success / count)), | |
| max(0.0, min(1.0, (total_latency / count) / 10000.0)), | |
| max(0.0, min(1.0, (total_step / count) / 20.0)), | |
| max(0.0, min(1.0, count / 20.0)), | |
| ] |
| chosen: list[dict[str, Any]] = [] | ||
| seen_task_types: set[str] = set() | ||
| for agent in candidate_agents: | ||
| task_type = str(agent.get("task_type", "general")) | ||
| if task_type not in seen_task_types: | ||
| chosen.append(agent) | ||
| seen_task_types.add(task_type) | ||
|
|
||
| # Add remaining candidates to fill committee capacity. | ||
| for agent in candidate_agents: | ||
| if agent not in chosen: | ||
| chosen.append(agent) | ||
| return chosen |
There was a problem hiding this comment.
The current implementation uses if agent not in chosen, which performs a full dictionary equality check against every element in the chosen list on every iteration. Using a set of agent_ids to track selected agents is more efficient and idiomatic for this type of filtering.
| chosen: list[dict[str, Any]] = [] | |
| seen_task_types: set[str] = set() | |
| for agent in candidate_agents: | |
| task_type = str(agent.get("task_type", "general")) | |
| if task_type not in seen_task_types: | |
| chosen.append(agent) | |
| seen_task_types.add(task_type) | |
| # Add remaining candidates to fill committee capacity. | |
| for agent in candidate_agents: | |
| if agent not in chosen: | |
| chosen.append(agent) | |
| return chosen | |
| chosen: list[dict[str, Any]] = [] | |
| seen_task_types: set[str] = set() | |
| seen_ids: set[str] = set() | |
| for agent in candidate_agents: | |
| task_type = str(agent.get("task_type", "general")) | |
| agent_id = str(agent.get("agent_id")) | |
| if task_type not in seen_task_types: | |
| chosen.append(agent) | |
| seen_task_types.add(task_type) | |
| seen_ids.add(agent_id) | |
| # Add remaining candidates to fill committee capacity. | |
| for agent in candidate_agents: | |
| agent_id = str(agent.get("agent_id")) | |
| if agent_id not in seen_ids: | |
| chosen.append(agent) | |
| seen_ids.add(agent_id) | |
| return chosen |
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging |
| stopwords = { | ||
| "the", "a", "an", "is", "are", "was", "were", "be", "been", | ||
| "being", "have", "has", "had", "do", "does", "did", "will", | ||
| "would", "could", "should", "may", "might", "must", "shall", | ||
| "can", "need", "to", "of", "in", "for", "on", "with", "at", | ||
| "by", "from", "as", "into", "through", "during", "before", | ||
| "after", "above", "below", "between", "under", "again", | ||
| "further", "then", "once", "here", "there", "when", "where", | ||
| "why", "how", "all", "each", "few", "more", "most", "other", | ||
| "some", "such", "no", "nor", "not", "only", "own", "same", | ||
| "so", "than", "too", "very", "just", "and", "but", "if", "or", | ||
| "because", "until", "while", "although", "though", "this", | ||
| "that", "these", "those", "it", "its" | ||
| } |
| """ | ||
| # Auto-detect variables if not provided | ||
| if variables is None: | ||
| import re |
| data["template_id"] = template_id | ||
| template = PromptTemplate.from_dict(data) |
There was a problem hiding this comment.
This implementation modifies the data dictionary in-place, which is part of the templates_data input. This side effect might be unexpected for the caller. It is safer to create a new dictionary or pass the ID explicitly during instantiation.
| data["template_id"] = template_id | |
| template = PromptTemplate.from_dict(data) | |
| template = PromptTemplate.from_dict({**data, "template_id": template_id}) |
|
|
||
| import logging | ||
| import textwrap | ||
| from datetime import datetime |
| self._error_patterns = { | ||
| category: [pattern.lower() for pattern in patterns] | ||
| for category, patterns in self._error_patterns.items() | ||
| } |
| "tool_call": tool_call, | ||
| "error_analysis": error_analysis.to_dict(), | ||
| "correction": correction.to_dict(), | ||
| "timestamp": datetime.now().isoformat(), |
There was a problem hiding this comment.
Using datetime.now() creates a naive datetime object. It is better practice to use datetime.now(timezone.utc) for consistent, timezone-aware timestamps, matching the pattern used in core/metrics.py.
| "timestamp": datetime.now().isoformat(), | |
| "timestamp": datetime.now(timezone.utc).isoformat(), |
Summary
Implements all Phase 1 tasks from
docs/HOLOBIONT_ROADMAP.md— transforming RawLLM with multi-agent foundations: tool reranking/rejection, self-reflection cycle, context prompt repository, extended metrics with trajectory tracking, and a lightweight agent recommender.Changes
New files:
core/tool_management.py—ToolReranker(confidence-based reranking with success history, recency, context relevance) +ToolRejectionHandler(7 rejection reasons, duplicate detection, parameter constraints, alternative suggestions)core/reflection.py—ErrorAnalyzer(8 error categories with pattern matching),CorrectionGenerator(auto-correction with try-except wrapping),ReflectionLoop(full reflection cycle with history tracking)core/context_repository.py—ContextPromptRepository(template storage/retrieval),SemanticIndex(keyword-based similarity search),PromptTemplate(render with variables), 6 default templates, singleton accessorcore/agents/recommender.py—TrajectoryEncoder(4-dim latent vectors),AgentRecommender(history-based scoring with task similarity),CommitteeBuilder(diversity-aware committee selection)Modified files:
core/metrics.py— Extendedlog_execution()withsuccess_score,trajectory_id,step_number,task_type; addedget_execution_events(),build_agent_history();aggregate_by_plugin()now reportsavg_success_scoreandtrajectory_countNew test files:
tests/test_tool_management.py— 17 tests (reranking, rejection, duplicates, constraints, alternatives)tests/test_context_repository.py— 21 tests (CRUD, semantic search, import/export, defaults, singleton)tests/test_reflection.py— 30 tests (error analysis, correction generation, reflection loop, integration)tests/test_agent_recommender.py— 4 tests (encoder roundtrip, recommendation ranking, success rate fallback, committee constraints)tests/test_metrics.py— Extended with 10 new tests (success_score clamping, trajectory counting, agent history, mixed scores)Test Results
Phase 1 Completion Criteria
success_scoreandtrajectory_idKnown Gaps
prompt_buildir.py—build_startup_prompt()does not yet accept acontext_repositoryparameter (minor wiring task, does not block Phase 2)