Skip to content
Merged
Show file tree
Hide file tree
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
28 changes: 28 additions & 0 deletions app/research_workflow/companion_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,34 @@ def companion_ask(project_id):
return json_error(str(e), status_code=500)


# =========================================================================
# Counter-Evidence (Devil's Advocate)
# =========================================================================

@research_workflow_bp.route('/companion/<int:project_id>/counter-evidence', methods=['POST'])
@login_required
def companion_counter_evidence(project_id):
"""Generate counter-evidence challenging a research finding."""
project = get_user_resource_or_403(ResearchProject, project_id, current_user.id)

data = request.json or {}
finding = data.get('finding', '').strip()
research_question = data.get('research_question', '').strip() or None
step_index = data.get('step_index', project.current_step_index)

if not finding:
return json_validation_error('Finding is required')

try:
argos = ArgosService(user_id=current_user.id)
context = argos.build_research_context(project_id, step_index=step_index)
counter_evidence = argos.generate_counter_evidence(context, finding, research_question)
return json_success('Counter-evidence generated', data={'counter_evidence': counter_evidence})
except Exception as e:
logger.error(f"Counter-evidence generation failed: {e}")
return json_error(str(e), status_code=500)


# =========================================================================
# Session Wrap-Up
# =========================================================================
Expand Down
64 changes: 64 additions & 0 deletions app/services/ai/prompts/companion/counter_evidence.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: "counter_evidence"
description: "Surface disconfirming evidence and questions that challenge a research finding"
version: "1.0"
category: "companion"

preferred_provider: "gemini"
model: "gemini-3-pro-preview"
max_tokens: 6000
temperature: 0.3

system_context: |
You are a devil's advocate for an investor's research finding.
Your role is to surface what would have to be TRUE for the finding to be WRONG.

CRITICAL RULES:
1. You surface DISCONFIRMING EVIDENCE, ALTERNATIVE EXPLANATIONS, and QUESTIONS.
You NEVER give investment opinions or recommendations.
2. Never say "you should sell", "this is a bad investment", or "the thesis is wrong".
Say "this finding would not hold if..." and "the data that would disprove this is...".
3. Distinguish clearly between what you know as fact and what is an untested assumption.
Label anything you cannot verify as "unverified".
4. Attack the finding, never the investor. Reference their history as facts:
"Your mistake log shows a similar assumption on..." not "You tend to...".
5. If the finding is already well-supported by the prior findings, say so plainly
rather than manufacturing weak objections.

template: |
COMPANY: {company_name}
SECTOR: {sector_name}
CURRENT STEP: {step_name}

FINDING TO CHALLENGE:
{finding}

RESEARCH QUESTION IT ANSWERS:
{research_question}

OTHER FINDINGS SO FAR:
{prior_findings}

CURRENT THESIS:
{investment_thesis}

KEY FLAGS:
Red flags: {red_flags}
Green flags: {green_flags}

INVESTOR'S HISTORY:
Past mistakes: {mistake_summary}
Behavioral patterns to watch: {pattern_summary}

Challenge the finding with:
1. ASSUMPTIONS BEING MADE: What the finding takes for granted but has not verified
2. DISCONFIRMING EVIDENCE TO LOOK FOR: Specific data, filings, or metrics that would
contradict the finding if they exist
3. ALTERNATIVE EXPLANATIONS: Other readings of the same evidence
4. QUESTIONS TO ANSWER NEXT: Concrete questions that would settle whether it holds

Be specific and concrete. No opinions, no recommendations. Facts, gaps, and questions only.

output_format: |
Structured text with the four numbered section headers above.
Two to four bullet points per section, one sentence each.
Every bullet should be checkable against a source the investor can go find.
32 changes: 32 additions & 0 deletions app/services/argos/companion.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,38 @@ def ask_companion(
logger.error(f"Companion chat failed: {e}")
return f"Could not process question: {e}"

def generate_counter_evidence(
self,
context: CompanionContext,
finding: str,
research_question: str = None,
) -> str:
"""Play devil's advocate against a finding. Disconfirming evidence and questions, no opinions."""
try:
prompt_data = prompt_service.get_prompt_with_metadata(
'companion', 'counter_evidence',
company_name=context.company_name,
sector_name=context.sector_name,
step_name=context.step_name,
finding=finding,
research_question=research_question or context.research_questions,
prior_findings=context.prior_findings,
investment_thesis=context.investment_thesis,
red_flags=context.red_flags,
green_flags=context.green_flags,
mistake_summary=context.mistake_summary,
pattern_summary=context.pattern_summary,
)
model_enum, provider_enum = resolve_model_provider(
prompt_data.get('metadata', {}), user_id=self.user_id, prompt_category='companion',
)
return ai_service.generate_text(
prompt_data['prompt'], model=model_enum, provider=provider_enum,
)
except Exception as e:
logger.error(f"Counter-evidence generation failed: {e}")
return f"Could not generate counter-evidence: {e}"

def wrap_up_session(
self,
context: CompanionContext,
Expand Down
Loading