File size: 3,391 Bytes
33516f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
90
91
92
93
94
95
96
97
98
99
"""React Agent Graph - Main agent implementation.

Purpose:
    Defines a simple ReAct agent using `create_agent` from langchain.agents.
    The agent loops: Reason → Act (call tool) → Observe → Repeat until done.
    
Key Components:
    - create_agent: LangChain helper for an agent runtime (built on LangGraph)
    - Tools: Imported from shared tools module (Chroma, Neo4j)
    - Model: Configurable LLM (default: gemini-flash-latest)
    - Checkpointer: SqliteSaver on a gitignored path (survives process restart)
    
Usage:
    from src.react_agent.graph import react_agent
    result = await react_agent.ainvoke(
        {"messages": [HumanMessage(content="...")]},
        config={"configurable": {"thread_id": "my-thread"}}
    )
"""

from typing import Any, Optional
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from langchain_core.runnables import RunnableConfig

from src.react_agent.configuration import Configuration
from src.react_agent.tools import tools
from src.react_agent.utils import create_chat_model
from src.react_agent.prompts import system_prompt
from src.react_agent.checkpointer import get_sqlite_checkpointer

_memory = None
_react_agent = None


def _default_memory():
    """Open SQLite on first use so importing the package does not create files."""
    global _memory
    if _memory is None:
        _memory = get_sqlite_checkpointer()
    return _memory


def get_react_agent(
    config: Optional[RunnableConfig] = None,
    checkpointer: Optional[Any] = None,
) -> Any:
    """Create a ReAct agent with the specified configuration.
    
    Args:
        config: Optional RunnableConfig with configurable dict (model, max_tokens, etc.)
        checkpointer: Optional checkpointer for conversation memory. Defaults to
            the production SqliteSaver. Tests may inject MemorySaver. Pass a
            thread_id in config to enable memory:
            config={"configurable": {"thread_id": "my-thread"}}
        
    Returns:
        LangChain agent runnable that accepts {"messages": [...]}
    """
    cfg = Configuration.from_runnable_config(config)
    
    # Create the chat model instance
    model = create_chat_model(
        model_name=cfg.model,
        config=config,
        max_tokens=cfg.max_tokens,
        temperature=cfg.temperature,
        timeout=cfg.timeout,
    )
    
    # Create agent with model, tools, and checkpointer for conversation memory
    # create_agent returns a compiled graph that acts as a runnable
    agent: Any = create_agent(
        model,
        tools=tools,
        system_prompt=system_prompt,
        checkpointer=checkpointer if checkpointer is not None else _default_memory(),
    )

    # Respect configured iteration limits; default to Configuration.max_iterations
    recursion_limit = None
    if isinstance(config, dict):
        recursion_limit = config.get("recursion_limit")
    if recursion_limit is None:
        recursion_limit = cfg.max_iterations

    return agent.with_config(recursion_limit=recursion_limit)


def __getattr__(name: str) -> Any:
    global _react_agent
    if name == "memory":
        return _default_memory()
    if name == "react_agent":
        if _react_agent is None:
            _react_agent = get_react_agent()
        return _react_agent
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")