Spaces:
Sleeping
Sleeping
| from typing import TypedDict, List, Optional | |
| from langgraph.graph import StateGraph, END | |
| from langchain_openai import ChatOpenAI | |
| from langchain_community.tools import DuckDuckGoSearchRun | |
| from langchain.agents import tool | |
| search_tool = DuckDuckGoSearchRun() | |
| def search_web(query: str) -> str: | |
| """Search the web using DuckDuckGo and return the top result.""" | |
| return search_tool.run(query) | |
| class AgentState(TypedDict): | |
| question: str | |
| thoughts: List[str] | |
| tool_results: List[str] | |
| answer: Optional[str] | |
| llm = ChatOpenAI(model="gpt-4", temperature=0.5) | |
| def plan(state: AgentState) -> AgentState: | |
| prompt = f""" | |
| You are an intelligent assistant. Here is the question: | |
| {state['question']} | |
| So far, these are your thoughts: | |
| {state['thoughts']} | |
| What is the next best step? Should you: | |
| - Search the web (if more info is needed), | |
| - Answer the question (if confident), or | |
| - Think more before deciding? | |
| Provide a clear next step starting with one of these prefixes exactly: | |
| 'Search:', 'Answer:', or 'Think:' | |
| Then explain your reasoning. | |
| """ | |
| thought = llm.invoke(prompt).content.strip() | |
| state['thoughts'].append(thought) | |
| return state | |
| def act(state: AgentState) -> AgentState: | |
| latest = state['thoughts'][-1].strip() | |
| lower = latest.lower() | |
| if lower.startswith("search:"): | |
| query = latest[len("Search:"):].strip() | |
| if query: | |
| result = search_web.run(query) | |
| state['tool_results'].append(result) | |
| elif lower.startswith("answer:"): | |
| # Agent thinks it has the answer | |
| state['tool_results'].append("Answer ready") | |
| else: | |
| # Thinking or other - no tool used | |
| state['tool_results'].append("No tool used") | |
| return state | |
| def observe(state: AgentState) -> AgentState: | |
| obs = state['tool_results'][-1] if state['tool_results'] else "Nothing found" | |
| state['thoughts'].append(f"Observed: {obs}") | |
| return state | |
| def decide(state: AgentState) -> str: | |
| # Allow more thinking steps (e.g. 5) before summarizing | |
| return END if len(state["thoughts"]) >= 5 else "plan" | |
| def summarize(state: AgentState) -> AgentState: | |
| prompt = f""" | |
| You have gathered information and thoughts: | |
| {state['thoughts']} | |
| Based on all this, give a clear, concise, and final answer to the question: | |
| {state['question']} | |
| """ | |
| answer = llm.invoke(prompt).content.strip() | |
| state["answer"] = answer | |
| return state | |
| workflow = StateGraph(AgentState) | |
| workflow.add_node("plan", plan) | |
| workflow.add_node("act", act) | |
| workflow.add_node("observe", observe) | |
| workflow.add_node("decide", decide) | |
| workflow.add_node("summarize", summarize) | |
| workflow.set_entry_point("plan") | |
| workflow.add_edge("plan", "act") | |
| workflow.add_edge("act", "observe") | |
| workflow.add_edge("observe", "decide") | |
| def route_decision(state: AgentState) -> str: | |
| return "plan" if len(state["thoughts"]) < 3 else "summarize" | |
| workflow.add_conditional_edges("decide", { | |
| "plan": route_decision | |
| }) | |
| workflow.add_edge("summarize", END) | |
| agent = workflow.compile() | |