File size: 3,621 Bytes
65ade69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from pydantic import BaseModel, Field
from src.state import AgentState
from src.agents import competitor_agent, market_sizing_agent, sentiment_agent, os_risk_agent, get_llm

# --- Structured Output Schema ---
# We use Pydantic to tell the LLM exactly what JSON structure we want back.
class FinalReport(BaseModel):
    score: int = Field(description="A confidence score from 0 to 100 based on the viability of the startup idea.")
    verdict: str = Field(description="A categorical verdict: 'Go', 'Caution', or 'No-Go'.")
    summary: str = Field(description="A brief 2-3 sentence summary explaining the final verdict and score.")

# --- The Scoring Engine (Node) ---
# This node runs AFTER all 4 parallel agents have finished.
def scoring_node(state: AgentState) -> dict:
    idea = state["idea"]
    results = state["results"]
    
    print(f"\n[Scoring Engine] All 4 agents finished. Analyzing results for: {idea[:30]}...")
    
    # Format the findings from all 4 agents into a readable string for the LLM
    compiled_research = ""
    for result in results:
        compiled_research += f"\n### {result['agent'].replace('_', ' ').title()} ###\n"
        compiled_research += f"{result['findings']}\n"
        
    prompt = f"""You are a harsh but fair Y Combinator partner evaluating a startup idea.
    
    Idea: '{idea}'
    
    Here is the research from your 4 analysts:
    {compiled_research}
    
    Based on this research, provide a final evaluation.
    - Score (0-100): How viable is this? (100 = Guaranteed unicorn, 0 = Terrible idea)
    - Verdict: 'Go', 'Caution', or 'No-Go'
    - Summary: Justify your decision in 2-3 sentences based on the research.
    """
    
    llm = get_llm()
    # We use `.with_structured_output` to force the LLM to return a JSON object matching our Pydantic schema
    structured_llm = llm.with_structured_output(FinalReport)
    
    response = structured_llm.invoke([{"role": "user", "content": prompt}])
    
    # Return the updates to the state. We are updating the 3 final fields.
    return {
        "score": response.score,
        "verdict": response.verdict,
        "summary": response.summary
    }

# --- Construct the Graph ---
def build_graph():
    # 1. Initialize the graph with our state schema
    workflow = StateGraph(AgentState)
    
    # 2. Add all the nodes (functions)
    workflow.add_node("competitor_agent", competitor_agent)
    workflow.add_node("market_sizing_agent", market_sizing_agent)
    workflow.add_node("sentiment_agent", sentiment_agent)
    workflow.add_node("os_risk_agent", os_risk_agent)
    workflow.add_node("scoring_node", scoring_node)
    
    # 3. Define the routing (Edges)
    
    # Fan-out: START branches to all 4 research agents simultaneously
    workflow.add_edge(START, "competitor_agent")
    workflow.add_edge(START, "market_sizing_agent")
    workflow.add_edge(START, "sentiment_agent")
    workflow.add_edge(START, "os_risk_agent")
    
    # Fan-in: All 4 agents must finish and point to the scoring node
    # LangGraph automatically waits for all incoming edges to a node to complete
    workflow.add_edge("competitor_agent", "scoring_node")
    workflow.add_edge("market_sizing_agent", "scoring_node")
    workflow.add_edge("sentiment_agent", "scoring_node")
    workflow.add_edge("os_risk_agent", "scoring_node")
    
    # Finish the graph
    workflow.add_edge("scoring_node", END)
    
    # Compile the graph into an executable format
    return workflow.compile()

# Export the compiled graph
app = build_graph()