Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions backend/app/ai/agent_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,34 @@ class AgentV2:
"""Enhanced orchestrator with intelligent research/action flow."""

def __init__(self, db=None, organization=None, organization_settings=None, report=None,
model=None, small_model=None, mode=None, messages=[], head_completion=None, system_completion=None, widget=None, step=None, event_queue=None, clients=None, build_id=None):
model=None, small_model=None, mode=None, messages=None, head_completion=None, system_completion=None, widget=None, step=None, event_queue=None, clients=None, build_id=None):
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The messages parameter is changed from a mutable default [] to None, which is good practice. However, this parameter is never used in the constructor or anywhere else in the class. Consider removing it entirely from the function signature to avoid confusion and reduce the API surface. There are call sites (e.g., test_run_service.py:971, completion_service.py:267) that explicitly pass messages=[], which would need to be updated if this parameter is removed.

Copilot uses AI. Check for mistakes.
self.db = db
self.build_id = build_id
self.organization = organization
self.organization_settings = organization_settings
self.top_k_schema = organization_settings.get_config("top_k_schema").value
self.top_k_metadata_resources = organization_settings.get_config("top_k_metadata_resources").value
self.mode = mode

# Safe retrieval of configuration values with null checks
# Prevent AttributeError if get_config() returns None
if organization_settings:
top_k_schema_config = organization_settings.get_config("top_k_schema")
self.top_k_schema = top_k_schema_config.value if top_k_schema_config else 10

top_k_metadata_config = organization_settings.get_config("top_k_metadata_resources")
self.top_k_metadata_resources = top_k_metadata_config.value if top_k_metadata_config else 5
Comment on lines +56 to +59
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If top_k_schema_config exists but top_k_schema_config.value is None, then self.top_k_schema will be set to None. This could cause downstream errors when self.top_k_schema is used in operations that expect an integer (e.g., line 627 where it's passed to render_combined(top_k_per_ds=self.top_k_schema)). Consider adding an additional check: self.top_k_schema = top_k_schema_config.value if (top_k_schema_config and top_k_schema_config.value is not None) else 10

Suggested change
self.top_k_schema = top_k_schema_config.value if top_k_schema_config else 10
top_k_metadata_config = organization_settings.get_config("top_k_metadata_resources")
self.top_k_metadata_resources = top_k_metadata_config.value if top_k_metadata_config else 5
self.top_k_schema = (
top_k_schema_config.value
if (top_k_schema_config is not None and top_k_schema_config.value is not None)
else 10
)
top_k_metadata_config = organization_settings.get_config("top_k_metadata_resources")
self.top_k_metadata_resources = (
top_k_metadata_config.value
if (top_k_metadata_config is not None and top_k_metadata_config.value is not None)
else 5
)

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +59
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the issue on line 56, if top_k_metadata_config exists but top_k_metadata_config.value is None, then self.top_k_metadata_resources will be set to None. This could cause downstream errors when the value is used in operations that expect an integer. Consider adding an additional check: self.top_k_metadata_resources = top_k_metadata_config.value if (top_k_metadata_config and top_k_metadata_config.value is not None) else 5

Suggested change
self.top_k_schema = top_k_schema_config.value if top_k_schema_config else 10
top_k_metadata_config = organization_settings.get_config("top_k_metadata_resources")
self.top_k_metadata_resources = top_k_metadata_config.value if top_k_metadata_config else 5
self.top_k_schema = (
top_k_schema_config.value
if (top_k_schema_config and top_k_schema_config.value is not None)
else 10
)
top_k_metadata_config = organization_settings.get_config("top_k_metadata_resources")
self.top_k_metadata_resources = (
top_k_metadata_config.value
if (top_k_metadata_config and top_k_metadata_config.value is not None)
else 5
)

Copilot uses AI. Check for mistakes.

self.ai_analyst_name = organization_settings.config.get('general', {}).get('ai_analyst_name', "AI Analyst")
# Safe access to nested config dictionary
self.ai_analyst_name = (
organization_settings.config.get('general', {}).get('ai_analyst_name', "AI Analyst")
if hasattr(organization_settings, 'config') and organization_settings.config
else "AI Analyst"
)
else:
# Default values when organization_settings is None
self.top_k_schema = 10
self.top_k_metadata_resources = 5
self.ai_analyst_name = "AI Analyst"

self.mode = mode

self.report = report
self.report_type = getattr(report, 'report_type', 'regular')
Expand Down Expand Up @@ -187,7 +204,17 @@ async def _run_early_scoring_background(self, planner_input: PlannerInput):
async with SessionLocal() as session:
try:
# Use a new Judge instance (stateless) and score from the same planner input
if self.organization_settings.get_config("enable_llm_judgement") and self.organization_settings.get_config("enable_llm_judgement").value and self.report_type == 'regular':
# Cache get_config() result to avoid redundant calls and prevent race conditions
enable_llm_judgement_config = (
self.organization_settings.get_config("enable_llm_judgement")
if self.organization_settings else None
)
enable_llm_judgement = (
enable_llm_judgement_config.value
if enable_llm_judgement_config else False
)

if enable_llm_judgement and self.report_type == 'regular':
judge = Judge(model=self.model, organization_settings=self.organization_settings)
instructions_score, context_score = await judge.score_instructions_and_context_from_planner_input(planner_input)
else:
Expand All @@ -209,9 +236,23 @@ async def _run_late_scoring_background(self, messages_context: str, observation_
SessionLocal = create_async_session_factory()
async with SessionLocal() as session:
try:
if self.organization_settings.get_config("enable_llm_judgement") and self.organization_settings.get_config("enable_llm_judgement").value and self.report_type == 'regular':
# Cache get_config() result to avoid redundant calls and prevent race conditions
enable_llm_judgement_config = (
self.organization_settings.get_config("enable_llm_judgement")
if self.organization_settings else None
)
enable_llm_judgement = (
enable_llm_judgement_config.value
if enable_llm_judgement_config else False
)

if enable_llm_judgement and self.report_type == 'regular':
judge = Judge(model=self.model, organization_settings=self.organization_settings)
original_prompt = self.head_completion.prompt.get("content", "") if getattr(self.head_completion, "prompt", None) else ""
# Safe access to nested prompt dictionary with proper null checks
original_prompt = ""
if self.head_completion and hasattr(self.head_completion, "prompt") and self.head_completion.prompt:
original_prompt = self.head_completion.prompt.get("content", "")

response_score = await judge.score_response_quality(original_prompt, messages_context, observation_data=observation_data)
else:
response_score = 3
Expand Down