Spaces:
Sleeping
Sleeping
Download src/graph.py from Raiquia/VentureForge: direct link, hf CLI and curl.
- Browser
- Download file 5.38 kB
-
https://huggingface.co/spaces/Raiquia/VentureForge/resolve/main/src/graph.py
- Command line
-
hf download hf://spaces/Raiquia/VentureForge/src/graph.py
-
curl -L -o graph.py https://huggingface.co/spaces/Raiquia/VentureForge/resolve/main/src/graph.py
5.38 kB
| """ | |
| VentureForge LangGraph | |
| ====================== | |
| Assembles the hierarchical multi-agent graph with reflection loop and SQLite checkpoint persistence. | |
| Usage: | |
| from src.graph import build_graph, GRAPH | |
| graph = build_graph() | |
| result = graph.invoke(state) | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import sqlite3 | |
| import time | |
| from collections.abc import Callable | |
| from pathlib import Path | |
| from typing import Any | |
| from langgraph.checkpoint.base import BaseCheckpointSaver | |
| from langgraph.checkpoint.memory import MemorySaver | |
| from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer | |
| from langgraph.checkpoint.sqlite import SqliteSaver | |
| from langgraph.graph import END, StateGraph | |
| from src.agents import critic as critic_agent | |
| from src.agents import idea_generator as idea_generator_agent | |
| from src.agents import pain_point_miner as pain_point_miner_agent | |
| from src.agents import pitch_writer as pitch_writer_agent | |
| from src.agents import scorer as scorer_agent | |
| from src.agents.orchestrator import orchestrator | |
| from src.state.graph_state import VentureForgeState | |
| logger = logging.getLogger(__name__) | |
| DEFAULT_CHECKPOINT_DB_PATH = ".cache/ventureforge.db" | |
| ALLOWED_MSGPACK_MODULES = [ | |
| ("src.models.common", "PipelineStage"), | |
| ("src.models.common", "DataSource"), | |
| ("src.models.common", "Verdict"), | |
| ("src.models.common", "TargetAgent"), | |
| ("src.models.common", "RunEvent"), | |
| ("src.models.common", "ErrorEntry"), | |
| ("src.models.pain_point", "PainPoint"), | |
| ("src.models.pain_point", "PainPointEvidence"), | |
| ("src.models.pain_point", "PainPointRubric"), | |
| ("src.models.idea", "Idea"), | |
| ("src.models.idea", "ScoredIdea"), | |
| ("src.models.pitch", "PitchBrief"), | |
| ("src.models.critique", "Critique"), | |
| ("src.models.critique", "CritiqueRubric"), | |
| ] | |
| def timed( | |
| name: str, | |
| fn: Callable[[VentureForgeState], dict[str, Any]], | |
| ) -> Callable[[VentureForgeState], dict[str, Any]]: | |
| """Wrap a worker node function with elapsed-time recording. | |
| Returns a closure that calls fn(state), merges the agent_timings | |
| patch, and preserves the original function name for LangGraph. | |
| """ | |
| def _wrapper(state: VentureForgeState) -> dict[str, Any]: | |
| t0 = time.monotonic() | |
| result = fn(state) | |
| return {**result, **state.record_timing(name, time.monotonic() - t0)} | |
| _wrapper.__name__ = name | |
| _wrapper.__qualname__ = name | |
| return _wrapper | |
| def get_checkpointer(db_path: str | None = DEFAULT_CHECKPOINT_DB_PATH) -> BaseCheckpointSaver: | |
| """Create a persistent SQLite checkpointer, falling back to in-memory if unavailable.""" | |
| serde = JsonPlusSerializer().with_msgpack_allowlist(ALLOWED_MSGPACK_MODULES) | |
| if db_path: | |
| try: | |
| db_file = Path(db_path) | |
| db_file.parent.mkdir(parents=True, exist_ok=True) | |
| conn = sqlite3.connect(str(db_file), check_same_thread=False) | |
| logger.info(f"[graph] Initialized SqliteSaver checkpoint persistence at '{db_path}'.") | |
| return SqliteSaver(conn, serde=serde) | |
| except Exception as e: | |
| logger.warning( | |
| f"[graph] Failed to initialize SQLite checkpointer at '{db_path}': {e}. Using MemorySaver." | |
| ) | |
| return MemorySaver(serde=serde) | |
| def route_after_orchestrator(state: VentureForgeState) -> str: | |
| """Return the next node name after the orchestrator runs.""" | |
| return state.next_node | |
| def route_after_critic(state: VentureForgeState) -> str: | |
| """After critic, always return to orchestrator for routing decisions.""" | |
| return "orchestrator" | |
| def build_graph(checkpointer: BaseCheckpointSaver | None = None) -> StateGraph: | |
| """Build and return the compiled LangGraph StateGraph.""" | |
| workflow = StateGraph(VentureForgeState) | |
| # Register nodes — workers are wrapped with timed() at the wiring site | |
| workflow.add_node("orchestrator", orchestrator) | |
| workflow.add_node("pain_point_miner", timed("pain_point_miner", pain_point_miner_agent.run)) | |
| workflow.add_node("idea_generator", timed("idea_generator", idea_generator_agent.run)) | |
| workflow.add_node("scorer", timed("scorer", scorer_agent.run)) | |
| workflow.add_node("pitch_writer", timed("pitch_writer", pitch_writer_agent.run)) | |
| workflow.add_node("critic", timed("critic", critic_agent.run)) | |
| # Entry point | |
| workflow.set_entry_point("orchestrator") | |
| # Orchestrator routes to the appropriate worker (or end) | |
| workflow.add_conditional_edges( | |
| "orchestrator", | |
| route_after_orchestrator, | |
| { | |
| "pain_point_miner": "pain_point_miner", | |
| "idea_generator": "idea_generator", | |
| "scorer": "scorer", | |
| "pitch_writer": "pitch_writer", | |
| "critic": "critic", | |
| "__end__": END, | |
| }, | |
| ) | |
| # Workers always return to orchestrator | |
| for worker in ("pain_point_miner", "idea_generator", "scorer", "pitch_writer"): | |
| workflow.add_edge(worker, "orchestrator") | |
| # Critic returns to orchestrator | |
| workflow.add_conditional_edges( | |
| "critic", | |
| route_after_critic, | |
| { | |
| "orchestrator": "orchestrator", | |
| END: END, | |
| }, | |
| ) | |
| saver = checkpointer if checkpointer is not None else get_checkpointer() | |
| return workflow.compile(checkpointer=saver) | |
| # Convenience: pre-compiled graph instance | |
| GRAPH = build_graph() | |