Ashmit003 commited on
Commit
be5f49d
Β·
1 Parent(s): 9c65cb5

Uploading modified local folder struture

Browse files
.streamlit/config.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [server]
2
+ enableXsrfProtection = false
3
+ enableCORS = false
4
+ maxUploadSize = 200
5
+ gatherUsageStats = false
Dockerfile CHANGED
@@ -1,20 +1,32 @@
1
- FROM python:3.13.5-slim
2
 
3
  WORKDIR /app
4
 
 
5
  RUN apt-get update && apt-get install -y \
6
  build-essential \
7
  curl \
8
  git \
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
- COPY requirements.txt ./
 
12
  COPY src/ ./src/
 
 
 
 
13
 
14
- RUN pip3 install -r requirements.txt
 
 
 
15
 
16
  EXPOSE 8501
17
 
18
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
 
 
 
19
 
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
1
+ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
+ # Install system dependencies
6
  RUN apt-get update && apt-get install -y \
7
  build-essential \
8
  curl \
9
  git \
10
  && rm -rf /var/lib/apt/lists/*
11
 
12
+ # Copy project files
13
+ COPY requirements/requirements.txt ./requirements.txt
14
  COPY src/ ./src/
15
+ COPY agent_plan_solve_app.py ./
16
+ COPY agent_plan_solve/ ./agent_plan_solve/
17
+ COPY config.py ./config.py
18
+ COPY .streamlit /app/.streamlit
19
 
20
+
21
+
22
+ # Install Python dependencies
23
+ RUN pip3 install --upgrade pip && pip3 install -r requirements.txt
24
 
25
  EXPOSE 8501
26
 
27
+ ENV STREAMLIT_SERVER_HEADLESS=true
28
+
29
+ # Start the Streamlit app
30
+ #CMD ["streamlit", "run", "agent_plan_solve_app.py", "--server.port=8501", "--server.address=0.0.0.0", "--server.enableXsrfProtection=false"]
31
 
32
+ ENTRYPOINT ["streamlit", "run", "agent_plan_solve_app.py", "--server.port=8501", "--server.address=0.0.0.0", "--server.enableXsrfProtection=false"]
README.md CHANGED
@@ -4,14 +4,19 @@ emoji: πŸš€
4
  colorFrom: red
5
  colorTo: red
6
  sdk: docker
 
7
  app_port: 8501
8
  tags:
9
- - streamlit
 
 
 
10
  pinned: false
11
  short_description: AI planner using Wikipedia, ArXiv, Duck-search & Calculator
 
12
  ---
13
 
14
- # Welcome to Streamlit!
15
 
16
  Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
17
 
 
4
  colorFrom: red
5
  colorTo: red
6
  sdk: docker
7
+ app_file: agent_plan_solve_app.py
8
  app_port: 8501
9
  tags:
10
+ - streamlit
11
+ - agentic-ai
12
+ - wikipedia
13
+ - strategic-planning
14
  pinned: false
15
  short_description: AI planner using Wikipedia, ArXiv, Duck-search & Calculator
16
+ startup_duration_timeout: 1h
17
  ---
18
 
19
+
20
 
21
  Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
22
 
agent_plan_solve/__init__.py ADDED
File without changes
agent_plan_solve/graph/__init__.py ADDED
File without changes
agent_plan_solve/graph/build_graph.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agent_plan_solve/graph/graph.py
2
+
3
+ from langgraph.graph import StateGraph, START, END
4
+ from agent_plan_solve.graph.state import PlanState
5
+ from agent_plan_solve.graph.nodes import (
6
+ _build_initial_plan,
7
+ _run_step,
8
+ _get_final_response,
9
+ _should_continue
10
+ )
11
+
12
+ __all__ = ["graph"]
13
+ print("βœ… build_graph.py loaded")
14
+
15
+ # Build the LangGraph
16
+ builder = StateGraph(PlanState)
17
+
18
+ builder.add_node("initial_plan", _build_initial_plan)
19
+ builder.add_node("run", _run_step)
20
+ builder.add_node("response", _get_final_response)
21
+
22
+ builder.add_edge(START, "initial_plan")
23
+ builder.add_edge("initial_plan", "run")
24
+ builder.add_conditional_edges("run", _should_continue)
25
+ builder.add_edge("response", END)
26
+
27
+ graph = builder.compile()
agent_plan_solve/graph/nodes.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agent_plan_solve/graph/nodes.py
2
+
3
+ from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
4
+ from langgraph.prebuilt import create_react_agent
5
+ from langgraph.prebuilt.chat_agent_executor import AgentState
6
+ from agent_plan_solve.graph.state import Plan, PlanState
7
+ from agent_plan_solve.utils.llm_config import get_openai_llm
8
+ from agent_plan_solve.tools.calculator import calculator_tool
9
+ from langchain.agents import load_tools
10
+ from typing import Literal
11
+
12
+ # Load LLM
13
+ llm = get_openai_llm()
14
+
15
+ # Planner prompt
16
+ planner_prompt = ChatPromptTemplate.from_messages([
17
+ ("system",
18
+ "For the given task, come up with a step-by-step plan.\n"
19
+ "Each step should be self-contained and lead to the final answer.\n"
20
+ "Avoid superfluous steps."),
21
+ ("user", "Prepare a plan to solve the following task:\n{task}\n")
22
+ ])
23
+
24
+ planner = planner_prompt | llm.with_structured_output(Plan)
25
+
26
+ # Toolset
27
+ tools = load_tools(
28
+ tool_names=["ddg-search", "arxiv", "wikipedia"],
29
+ llm=llm
30
+ ) + [calculator_tool]
31
+
32
+ # Execution agent prompt
33
+ step_prompt = ChatPromptTemplate.from_messages([
34
+ ("system",
35
+ "You're a smart assistant that carefully helps solve complex tasks.\n"
36
+ "Use tools to verify facts, compute results, and avoid assumptions."),
37
+ ("user",
38
+ "TASK:\n{task}\n\nPLAN:\n{plan}\n\nSTEP TO EXECUTE:\n{step}\n")
39
+ ])
40
+
41
+ class StepState(AgentState):
42
+ task: str
43
+ plan: str
44
+ step: str
45
+
46
+ execution_agent = create_react_agent(
47
+ model=llm,
48
+ tools=tools,
49
+ state_schema=StepState,
50
+ prompt=step_prompt
51
+ )
52
+
53
+ # Final response prompt
54
+ final_prompt = PromptTemplate.from_template(
55
+ "You're a helpful assistant that has executed a plan.\n"
56
+ "Given the results, prepare the final response.\n"
57
+ "TASK:\n{task}\n\nPLAN WITH RESULTS:\n{plan}\nFINAL RESPONSE:\n"
58
+ )
59
+
60
+ # Utility functions
61
+ def get_current_step(state: PlanState) -> int:
62
+ return len(state.get("past_steps", []))
63
+
64
+ def get_full_plan(state: PlanState) -> str:
65
+ full_plan = []
66
+ for i, step in enumerate(state["plan"].steps):
67
+ full_step = f"# {i+1}. Planned step: {step}\n"
68
+ if i < get_current_step(state):
69
+ full_step += f"Result: {state['past_steps'][i]}\n"
70
+ full_plan.append(full_step)
71
+ return "\n".join(full_plan)
72
+
73
+ # Node functions
74
+ async def _build_initial_plan(state: PlanState) -> PlanState:
75
+ plan = await planner.ainvoke(state["task"])
76
+ return {"plan": plan}
77
+
78
+ async def _run_step(state: PlanState) -> PlanState:
79
+ plan = state["plan"]
80
+ current_step = get_current_step(state)
81
+ step = await execution_agent.ainvoke({
82
+ "plan": get_full_plan(state),
83
+ "step": plan.steps[current_step],
84
+ "task": state["task"]
85
+ })
86
+ return {"past_steps": [step["messages"][-1].content]}
87
+
88
+ async def _get_final_response(state: PlanState) -> PlanState:
89
+ final_response = await (final_prompt | llm).ainvoke({
90
+ "task": state["task"],
91
+ "plan": get_full_plan(state)
92
+ })
93
+ return {"final_response": final_response}
94
+
95
+ def _should_continue(state: PlanState) -> Literal["run", "response"]:
96
+ return "run" if get_current_step(state) < len(state["plan"].steps) else "response"
agent_plan_solve/graph/state.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agent_plan_solve/graph/state.py
2
+
3
+ from typing import Annotated, TypedDict
4
+ import operator
5
+ from pydantic import BaseModel, Field
6
+
7
+ class Plan(BaseModel):
8
+ """Plan to follow in future"""
9
+ steps: list[str] = Field(..., description="Different steps to follow, in sorted order")
10
+
11
+ class PlanState(TypedDict):
12
+ task: str
13
+ plan: Plan
14
+ past_steps: Annotated[list[str], operator.add]
15
+ final_response: str
agent_plan_solve/tools/__init__.py ADDED
File without changes
agent_plan_solve/tools/calculator.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agent_plan_solve/tools/calculator.py
2
+
3
+ from pydantic import BaseModel, Field
4
+ from langchain_core.runnables import RunnableLambda, RunnableConfig
5
+ from langchain_core.tools import ToolException
6
+ import numexpr as ne
7
+
8
+ # Custom exception for calculator errors
9
+ class CalculatorToolException(ToolException):
10
+ def __init__(self, message: str, expression: str):
11
+ super().__init__(f"[Calculator Error] {message} | Expression: '{expression}'")
12
+
13
+ # Input schema for the calculator tool
14
+ class CalculatorArgs(BaseModel):
15
+ expression: str = Field(..., description="Mathematical expression to be evaluated")
16
+
17
+ # Core calculator logic
18
+ def calculator(state: CalculatorArgs, config: RunnableConfig) -> str:
19
+ expression = state["expression"]
20
+ math_constants = config["configurable"].get("math_constants", {})
21
+
22
+ try:
23
+ result = ne.evaluate(expression.strip(), local_dict=math_constants)
24
+ return str(result)
25
+ except Exception as e:
26
+ raise CalculatorToolException(str(e), expression)
27
+
28
+ # Wrap with retry logic
29
+ calculator_with_retry = RunnableLambda(calculator).with_retry(
30
+ wait_exponential_jitter=True,
31
+ stop_after_attempt=3,
32
+ )
33
+
34
+ # Convert to LangChain-compatible tool
35
+ calculator_tool = calculator_with_retry.as_tool(
36
+ name="calculator",
37
+ description=(
38
+ "Calculates a single mathematical expression, incl. complex numbers.\n"
39
+ "Always add * to operations, examples:\n73i -> 73*i\n7pi**2 -> 7*pi**2"
40
+ ),
41
+ args_schema=CalculatorArgs
42
+ )
agent_plan_solve/utils/__init__.py ADDED
File without changes
agent_plan_solve/utils/llm_config.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agent_plan_solve/utils/llm_config.py
2
+
3
+ import os
4
+ from config import set_environment
5
+ from langchain_openai import ChatOpenAI
6
+
7
+ # Load environment variables from .env
8
+ set_environment()
9
+
10
+ class ChatOpenAIWrapper(ChatOpenAI):
11
+ def __init__(self, model_name="gpt-5-nano", **kwargs):
12
+ super().__init__(
13
+ model=model_name,
14
+ api_key=os.getenv("OPENAI_API_KEY"),
15
+ **kwargs
16
+ )
17
+
18
+ def get_openai_llm(model_name="gpt-5-nano", streaming=True, **kwargs):
19
+ return ChatOpenAIWrapper(
20
+ model_name=model_name,
21
+ streaming=streaming,
22
+ **kwargs
23
+ )
agent_plan_solve_app.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import asyncio
3
+ import os
4
+ import sys
5
+
6
+ # βœ… Must be first Streamlit call
7
+ st.set_page_config(page_title="Strategic Planner", layout="centered")
8
+
9
+ # ─────────────────────────────────────────────
10
+ # πŸ“¦ Environment & Path Setup
11
+ # ─────────────────────────────────────────────
12
+ project_root = os.path.join(os.getcwd(), "agent_plan_solve")
13
+ if project_root not in sys.path:
14
+ sys.path.insert(0, project_root)
15
+
16
+ from config import set_environment
17
+ set_environment()
18
+
19
+ # ─────────────────────────────────────────────
20
+ # 🧠 LangGraph Agent Import
21
+ # ─────────────────────────────────────────────
22
+ from agent_plan_solve.graph.build_graph import graph
23
+
24
+ # ─────────────────────────────────────────────
25
+ # πŸŽ›οΈ Streamlit UI
26
+ # ─────────────────────────────────────────────
27
+ st.title("🧠Planner & Research Analyst with GPT-5 Nano")
28
+ st.markdown("Enter a task below and let the agent plan and execute it step-by-step.")
29
+ task_input = st.text_area("Task:", placeholder="e.g. Write a strategic one-pager for building an AI startup")
30
+
31
+ # ─────────────────────────────────────────────
32
+ # πŸ”„ Async Agent Execution
33
+ # ─────────────────────────────────────────────
34
+ async def run_agent(task: str):
35
+ return await graph.ainvoke({"task": task})
36
+
37
+ def display_response(response):
38
+ st.markdown("### βœ… Final Output")
39
+ if hasattr(response, "content"):
40
+ st.markdown(response.content)
41
+ elif isinstance(response, str):
42
+ st.markdown(response)
43
+ elif isinstance(response, dict) and "content" in response:
44
+ st.markdown(response["content"])
45
+ else:
46
+ st.error("No response generated.")
47
+
48
+ # ─────────────────────────────────────────────
49
+ # πŸš€ Trigger Agent
50
+ # ─────────────────────────────────────────────
51
+ if st.button("Run Agent"):
52
+ if not task_input.strip():
53
+ st.warning("Please enter a task.")
54
+ else:
55
+ with st.spinner("Running LangGraph agent..."):
56
+ try:
57
+ loop = asyncio.new_event_loop()
58
+ asyncio.set_event_loop(loop)
59
+ result = loop.run_until_complete(run_agent(task_input))
60
+ display_response(result.get("final_response"))
61
+ except Exception as e:
62
+ st.error(f"❌ Agent execution failed: {e}")
config.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ def set_environment(verbose=True) -> dict:
5
+ env_path = os.path.abspath(".env")
6
+
7
+ # Load .env only if it exists
8
+ if os.path.exists(env_path):
9
+ load_dotenv(dotenv_path=env_path, override=True)
10
+ if verbose:
11
+ print(f"βœ… Loaded .env from: {env_path}")
12
+ else:
13
+ if verbose:
14
+ print("⚠️ No .env file found. Relying on system environment variables.")
15
+
16
+ required_keys = [
17
+ "OPENAI_API_KEY",
18
+ "OPENROUTER_API_KEY",
19
+ "GEMINI_API_KEY",
20
+ "GROQ_API_KEY"
21
+ ]
22
+ missing_keys = [key for key in required_keys if not os.getenv(key)]
23
+
24
+ if verbose:
25
+ if missing_keys:
26
+ print(f"⚠️ Missing environment variables: {missing_keys}")
27
+ else:
28
+ print("βœ… All required API keys loaded successfully!")
29
+
30
+ return {key: os.getenv(key) for key in required_keys}
requirements.txt DELETED
@@ -1,3 +0,0 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
requirements/requirements.txt ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ───────── Core LangChain Stack ─────────
2
+ langchain==0.3.17
3
+ langchain-core>=0.3.67,<0.4.0
4
+ langchain-community==0.3.16
5
+ langchain-openai==0.3.19
6
+ langchain-anthropic==0.3.5
7
+ langchain-google-genai==2.0.0
8
+ langchain-google-vertexai==2.0.28
9
+ langchain-huggingface==0.3.1
10
+ langchain-groq==0.2.4
11
+ langchain-ollama==0.2.3
12
+ langchain-mistralai==0.2.6
13
+ langchain-chroma==0.2.2
14
+ langchain-text-splitters==0.3.5
15
+ langchain-experimental==0.3.4
16
+ langgraph==0.3.34
17
+ langsmith>=0.3.45,<0.4.0
18
+
19
+ # ───────── Embeddings & Transformers ─────────
20
+ sentence-transformers>=2.6.1
21
+ transformers>=4.40.0
22
+ huggingface-hub>=0.33.4,<0.40.0
23
+
24
+
25
+ # ───────── Retrieval & Search ─────────
26
+ duckduckgo_search==8.0.4
27
+ arxiv==2.1.3
28
+ wikipedia==1.4.0
29
+ rank_bm25==0.2.2
30
+ datasets==3.4.0
31
+
32
+ # ───────── UI & Dev Tools ─────────
33
+ streamlit==1.37.1
34
+ ruff==0.9.4
35
+ altair>=5.4.0
36
+ python-dotenv
37
+
38
+ # ───────── Document Loaders and document processors ─────────
39
+ docarray==0.41.0
40
+ pypdf
41
+ python-docx
42
+ unstructured
43
+ beautifulsoup4
44
+ html2text
45
+ nltk
46
+
47
+ # ───────── Infra & Orchestration ─────────
48
+ ray==2.43.0
49
+
50
+ #other libraries
51
+ pydantic
52
+ typing