Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
pull_request:
branches: main
schedule:
- cron: '0 0 * * *' # Run daily at midnight UTC
- cron: "0 0 * * *" # Run daily at midnight UTC

jobs:
smoke:
Expand All @@ -17,7 +17,7 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [20, 22]
python: [3.11, 3.12]
python: [3.12, 3.13]

steps:
- name: Checkout
Expand All @@ -33,6 +33,14 @@ jobs:
with:
python-version: ${{ matrix.python }}

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true

- name: Configure uv to use matrix Python version
run: echo "UV_PYTHON=python${{ matrix.python }}" >> $GITHUB_ENV

- name: Install Node.js dependencies (root)
run: npm install

Expand All @@ -41,11 +49,6 @@ jobs:
cd agent
npm install

- name: Install Python dependencies (agent)
run: |
cd agent
pip install -r requirements.txt

- name: Build frontend
run: npm run build

Expand All @@ -55,12 +58,12 @@ jobs:
# Start the Next.js frontend in background
npm start &
FRONTEND_PID=$!

# Wait for frontend to start (max 30 seconds)
timeout=30
elapsed=0
started=false

while [ $elapsed -lt $timeout ] && [ "$started" = false ]; do
if curl -s http://localhost:3000 > /dev/null 2>&1; then
started=true
Expand All @@ -70,10 +73,10 @@ jobs:
elapsed=$((elapsed + 1))
fi
done

# Clean up background process
kill $FRONTEND_PID 2>/dev/null || true

if [ "$started" = false ]; then
echo "❌ Frontend failed to start within 30 seconds"
exit 1
Expand All @@ -85,12 +88,12 @@ jobs:
run: |
# Start the Next.js frontend in background
npm start &

# Wait for frontend to start (max 30 seconds)
$timeout = 30
$elapsed = 0
$started = $false

while ($elapsed -lt $timeout -and -not $started) {
try {
$response = Invoke-WebRequest -Uri "http://localhost:3000" -TimeoutSec 1 -ErrorAction SilentlyContinue
Expand All @@ -103,7 +106,7 @@ jobs:
$elapsed++
}
}

if (-not $started) {
Write-Host "❌ Frontend failed to start within 30 seconds"
exit 1
Expand All @@ -118,7 +121,7 @@ jobs:
runs-on: ubuntu-latest
needs: smoke
if: |
failure() &&
failure() &&
github.event_name == 'schedule'
steps:
- name: Notify Slack
Expand All @@ -138,4 +141,4 @@ jobs:
}
}
]
}
}
3 changes: 2 additions & 1 deletion agent/langgraph.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"python_version": "3.12",
"dockerfile_lines": [],
"dependencies": ["."],
"package_manager": "uv",
"graphs": {
"sample_agent": "./agent.py:graph"
"sample_agent": "./main.py:graph"
},
"env": ".env"
}
43 changes: 25 additions & 18 deletions agent/agent.py → agent/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
"""

from typing import Any, List
from typing_extensions import Literal
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, BaseMessage
from langchain_core.runnables import RunnableConfig

from langchain.tools import tool
from langgraph.graph import StateGraph, END
from langgraph.types import Command
from langgraph.graph import MessagesState
from langchain_core.messages import BaseMessage, SystemMessage
from langchain_core.runnables import RunnableConfig
from langchain_openai import ChatOpenAI
from langgraph.graph import END, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode
from langgraph.types import Command


class AgentState(MessagesState):
"""
Expand All @@ -22,17 +22,20 @@ class AgentState(MessagesState):
the CopilotKitState fields. We're also adding a custom field, `language`,
which will be used to set the language of the agent.
"""
proverbs: List[str] = []

proverbs: List[str]
tools: List[Any]
# your_custom_agent_state: str = ""


@tool
def get_weather(location: str):
"""
Get the weather for a given location.
"""
return f"The weather for {location} is 70 degrees."


# @tool
# def your_tool_here(your_arg: str):
# """Your tool description here."""
Expand All @@ -48,7 +51,7 @@ def get_weather(location: str):
backend_tool_names = [tool.name for tool in backend_tools]


async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal["tool_node", "__end__"]]:
async def chat_node(state: AgentState, config: RunnableConfig) -> Command[str]:
"""
Standard chat node based on the ReAct design pattern. It handles:
- The model to use (and binds in CopilotKit actions and the tools defined above)
Expand All @@ -61,16 +64,15 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Litera
"""

# 1. Define the model
model = ChatOpenAI(model="gpt-4o")
model = ChatOpenAI(model="gpt-5-mini")

# 2. Bind the tools to the model
model_with_tools = model.bind_tools(
[
*state.get("tools", []), # bind tools defined by ag-ui
*state.get("tools", []), # bind tools defined by ag-ui
*backend_tools,
# your_tool_here
],

# 2.1 Disable parallel tool calls to avoid race conditions,
# enable this for faster performance if you want to manage
# the complexity of running tool calls in parallel.
Expand All @@ -83,10 +85,13 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Litera
)

# 4. Run the model to generate a response
response = await model_with_tools.ainvoke([
system_message,
*state["messages"],
], config)
response = await model_with_tools.ainvoke(
[
system_message,
*state["messages"],
],
config,
)

# only route to tool node if tool is not in the tools list
if route_to_tool_node(response):
Expand All @@ -95,17 +100,18 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Litera
goto="tool_node",
update={
"messages": [response],
}
},
)

# 5. We've handled all tool calls, so we can end the graph.
return Command(
goto=END,
update={
"messages": [response],
}
},
)


def route_to_tool_node(response: BaseMessage):
"""
Route to tool node if any tool call in the response matches a backend tool name.
Expand All @@ -119,6 +125,7 @@ def route_to_tool_node(response: BaseMessage):
return True
return False


# Define the workflow graph
workflow = StateGraph(AgentState)
workflow.add_node("chat_node", chat_node)
Expand Down
16 changes: 16 additions & 0 deletions agent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[project]
name = "sample-agent"
version = "0.1.0"
description = "A LangGraph agent"
requires-python = ">=3.12"
dependencies = [
"langchain==1.1.0",
"langgraph==1.0.4",
"langsmith>=0.4.49",
"openai>=1.68.2,<2.0.0",
"fastapi>=0.115.5,<1.0.0",
"uvicorn>=0.29.0,<1.0.0",
"python-dotenv>=1.0.0,<2.0.0",
"langgraph-cli[inmem]>=0.4.7",
"langchain-openai>=1.1.0",
]
9 changes: 0 additions & 9 deletions agent/requirements.txt

This file was deleted.

Loading
Loading