chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
.vercel
|
||||
|
||||
# python
|
||||
.venv/
|
||||
.langgraph_api/
|
||||
@@ -0,0 +1 @@
|
||||
3.13
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"python_version": "3.12",
|
||||
"dockerfile_lines": [],
|
||||
"dependencies": ["."],
|
||||
"package_manager": "uv",
|
||||
"graphs": {
|
||||
"sample_agent": "./main.py:graph"
|
||||
},
|
||||
"env": "../.env"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
This is the main entry point for the agent.
|
||||
It defines the workflow graph, state, tools, nodes and edges.
|
||||
"""
|
||||
|
||||
from copilotkit import CopilotKitMiddleware, StateStreamingMiddleware, StateItem
|
||||
from langchain.agents import create_agent
|
||||
|
||||
# Data & state tools
|
||||
from src.query import query_data
|
||||
from src.todos import AgentState, todo_tools
|
||||
|
||||
# A2UI tools
|
||||
from src.a2ui_dynamic_schema import generate_a2ui
|
||||
from src.a2ui_fixed_schema import search_flights
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
model = ChatOpenAI(model="gpt-5.4-mini", model_kwargs={"parallel_tool_calls": False})
|
||||
|
||||
agent = create_agent(
|
||||
model=model,
|
||||
tools=[query_data, *todo_tools, generate_a2ui, search_flights],
|
||||
middleware=[
|
||||
CopilotKitMiddleware(),
|
||||
StateStreamingMiddleware(
|
||||
StateItem(state_key="todos", tool="manage_todos", tool_argument="todos")
|
||||
),
|
||||
],
|
||||
state_schema=AgentState,
|
||||
system_prompt="""
|
||||
You are a polished, professional demo assistant. Keep responses to 1-2 sentences.
|
||||
|
||||
Tool guidance:
|
||||
- Flights: call search_flights to show flight cards with a pre-built schema.
|
||||
- Dashboards & rich UI: call generate_a2ui to create dashboard UIs with metrics,
|
||||
charts, tables, and cards. It handles rendering automatically.
|
||||
- Charts: call query_data first, then render with the chart component.
|
||||
- Todos: enable app mode first, then manage todos.
|
||||
- A2UI actions: when you see a log_a2ui_event result (e.g. "view_details"),
|
||||
respond with a brief confirmation. The UI already updated on the frontend.
|
||||
""",
|
||||
)
|
||||
|
||||
graph = agent
|
||||
@@ -0,0 +1,22 @@
|
||||
[project]
|
||||
name = "sample-agent"
|
||||
version = "0.1.0"
|
||||
description = "A LangGraph agent"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"langchain==1.2.15",
|
||||
"langgraph==1.1.6",
|
||||
"langsmith==0.7.33",
|
||||
"openai==1.109.1",
|
||||
"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.21",
|
||||
"langchain-openai==1.1.9",
|
||||
"copilotkit==0.1.94",
|
||||
"ag-ui-protocol==0.1.18",
|
||||
"langgraph-api==0.7.101",
|
||||
"pip>=26.0.1",
|
||||
"langchain-anthropic==1.4.1",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"id": "root",
|
||||
"component": "Row",
|
||||
"children": {
|
||||
"componentId": "flight-card",
|
||||
"path": "/flights"
|
||||
},
|
||||
"gap": 16
|
||||
},
|
||||
{
|
||||
"id": "flight-card",
|
||||
"component": "FlightCard",
|
||||
"airline": { "path": "airline" },
|
||||
"airlineLogo": { "path": "airlineLogo" },
|
||||
"flightNumber": { "path": "flightNumber" },
|
||||
"origin": { "path": "origin" },
|
||||
"destination": { "path": "destination" },
|
||||
"date": { "path": "date" },
|
||||
"departureTime": { "path": "departureTime" },
|
||||
"arrivalTime": { "path": "arrivalTime" },
|
||||
"duration": { "path": "duration" },
|
||||
"status": { "path": "status" },
|
||||
"price": { "path": "price" },
|
||||
"action": {
|
||||
"event": {
|
||||
"name": "book_flight",
|
||||
"context": {
|
||||
"flightNumber": { "path": "flightNumber" },
|
||||
"origin": { "path": "origin" },
|
||||
"destination": { "path": "destination" },
|
||||
"price": { "path": "price" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Dynamic A2UI tool: LLM-generated UI from conversation context.
|
||||
|
||||
A secondary LLM generates v0.9 A2UI components via a structured tool call.
|
||||
The generate_a2ui tool wraps the output as a2ui_operations, which the
|
||||
middleware detects in the TOOL_CALL_RESULT and renders automatically.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import tool, ToolRuntime
|
||||
from langchain_core.messages import SystemMessage
|
||||
from langchain_core.tools import tool as lc_tool
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from copilotkit import a2ui
|
||||
|
||||
CUSTOM_CATALOG_ID = "copilotkit://app-dashboard-catalog"
|
||||
|
||||
|
||||
@lc_tool
|
||||
def render_a2ui(
|
||||
surfaceId: str,
|
||||
catalogId: str,
|
||||
components: list[dict],
|
||||
data: dict | None = None,
|
||||
) -> str:
|
||||
"""Render a dynamic A2UI v0.9 surface.
|
||||
|
||||
Args:
|
||||
surfaceId: Unique surface identifier.
|
||||
catalogId: The catalog ID (use "copilotkit://app-dashboard-catalog").
|
||||
components: A2UI v0.9 component array (flat format). The root
|
||||
component must have id "root".
|
||||
data: Optional initial data model for the surface (e.g. form values,
|
||||
list items for data-bound components).
|
||||
"""
|
||||
return "rendered"
|
||||
|
||||
|
||||
@tool()
|
||||
def generate_a2ui(runtime: ToolRuntime[Any]) -> str:
|
||||
"""Generate dynamic A2UI components based on the conversation.
|
||||
|
||||
A secondary LLM designs the UI schema and data. The result is
|
||||
returned as an a2ui_operations container for the middleware to detect.
|
||||
"""
|
||||
import time
|
||||
|
||||
t0 = time.time()
|
||||
print(f"[A2UI-DEBUG] generate_a2ui STARTED at t=0")
|
||||
|
||||
messages = runtime.state["messages"][:-1]
|
||||
print(f"[A2UI-DEBUG] messages count: {len(messages)}")
|
||||
|
||||
# Get context entries from copilotkit state (catalog capabilities + component schema)
|
||||
context_entries = runtime.state.get("copilotkit", {}).get("context", [])
|
||||
context_text = "\n\n".join(
|
||||
entry.get("value", "")
|
||||
for entry in context_entries
|
||||
if isinstance(entry, dict) and entry.get("value")
|
||||
)
|
||||
print(
|
||||
f"[A2UI-DEBUG] context entries: {len(context_entries)}, context_text_len: {len(context_text)}"
|
||||
)
|
||||
|
||||
prompt = context_text
|
||||
|
||||
model = ChatOpenAI(model="gpt-4.1")
|
||||
model_with_tool = model.bind_tools(
|
||||
[render_a2ui],
|
||||
tool_choice="render_a2ui",
|
||||
)
|
||||
|
||||
print(f"[A2UI-DEBUG] calling secondary LLM at t={time.time() - t0:.1f}s")
|
||||
response = model_with_tool.invoke(
|
||||
[SystemMessage(content=prompt), *messages],
|
||||
)
|
||||
print(f"[A2UI-RESPONSE] {response}")
|
||||
print(f"[A2UI-DEBUG] secondary LLM responded at t={time.time() - t0:.1f}s")
|
||||
|
||||
if not response.tool_calls:
|
||||
print(f"[A2UI-DEBUG] ERROR: no tool calls in response")
|
||||
return json.dumps({"error": "LLM did not call render_a2ui"})
|
||||
|
||||
tool_call = response.tool_calls[0]
|
||||
args = tool_call["args"]
|
||||
|
||||
surface_id = args.get("surfaceId", "dynamic-surface")
|
||||
catalog_id = args.get("catalogId", CUSTOM_CATALOG_ID)
|
||||
components = args.get("components", [])
|
||||
data = args.get("data", {})
|
||||
print(
|
||||
f"[A2UI-DEBUG] components={len(components)} data_keys={list(data.keys()) if data else []} surface={surface_id}"
|
||||
)
|
||||
|
||||
ops = [
|
||||
a2ui.create_surface(surface_id, catalog_id=catalog_id),
|
||||
a2ui.update_components(surface_id, components),
|
||||
]
|
||||
if data:
|
||||
ops.append(a2ui.update_data_model(surface_id, data))
|
||||
|
||||
result = a2ui.render(operations=ops)
|
||||
print(
|
||||
f"[A2UI-DEBUG] generate_a2ui DONE at t={time.time() - t0:.1f}s result_len={len(result)}"
|
||||
)
|
||||
return result
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Fixed-schema A2UI tool: flight search results.
|
||||
|
||||
Schema is loaded from a JSON file. Only the data changes per invocation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
|
||||
from copilotkit import a2ui
|
||||
from langchain.tools import tool
|
||||
|
||||
CATALOG_ID = "copilotkit://app-dashboard-catalog"
|
||||
SURFACE_ID = "flight-search-results"
|
||||
FLIGHT_SCHEMA = a2ui.load_schema(
|
||||
Path(__file__).parent / "a2ui" / "schemas" / "flight_schema.json"
|
||||
)
|
||||
|
||||
|
||||
class Flight(TypedDict):
|
||||
id: str
|
||||
airline: str
|
||||
airlineLogo: str
|
||||
flightNumber: str
|
||||
origin: str
|
||||
destination: str
|
||||
date: str
|
||||
departureTime: str
|
||||
arrivalTime: str
|
||||
duration: str
|
||||
status: str
|
||||
statusIcon: str
|
||||
price: str
|
||||
|
||||
|
||||
@tool
|
||||
def search_flights(flights: list[Flight]) -> str:
|
||||
"""Search for flights and display the results as rich cards. Return exactly 2 flights.
|
||||
|
||||
Each flight must have: id, airline (e.g. "United Airlines"),
|
||||
airlineLogo (use Google favicon API: https://www.google.com/s2/favicons?domain={airline_domain}&sz=128
|
||||
e.g. "https://www.google.com/s2/favicons?domain=united.com&sz=128" for United,
|
||||
"https://www.google.com/s2/favicons?domain=delta.com&sz=128" for Delta,
|
||||
"https://www.google.com/s2/favicons?domain=aa.com&sz=128" for American,
|
||||
"https://www.google.com/s2/favicons?domain=alaskaair.com&sz=128" for Alaska),
|
||||
flightNumber, origin, destination,
|
||||
date (short readable format like "Tue, Mar 18" — use near-future dates),
|
||||
departureTime, arrivalTime,
|
||||
duration (e.g. "4h 25m"), status (e.g. "On Time" or "Delayed"),
|
||||
statusIcon (colored dot: use "https://placehold.co/12/22c55e/22c55e.png"
|
||||
for On Time, "https://placehold.co/12/eab308/eab308.png" for Delayed,
|
||||
"https://placehold.co/12/ef4444/ef4444.png" for Cancelled),
|
||||
and price (e.g. "$289").
|
||||
"""
|
||||
return a2ui.render(
|
||||
operations=[
|
||||
a2ui.create_surface(SURFACE_ID, catalog_id=CATALOG_ID),
|
||||
a2ui.update_components(SURFACE_ID, FLIGHT_SCHEMA),
|
||||
a2ui.update_data_model(SURFACE_ID, {"flights": flights}),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
date,category,subcategory,amount,type,notes
|
||||
2026-01-05,Revenue,Enterprise Subscriptions,28000,income,3 new enterprise customers (Acme Corp, TechFlow, DataViz Inc)
|
||||
2026-01-05,Revenue,Pro Tier Upgrades,18000,income,24 users upgraded from free to pro
|
||||
2026-01-08,Revenue,API Usage Overages,9500,income,High API usage from top 5 customers
|
||||
2026-01-10,Expenses,Engineering Salaries,42000,expense,7 engineers + 2 contractors
|
||||
2026-01-10,Expenses,Product Team,18000,expense,PM and 2 designers
|
||||
2026-01-12,Expenses,AWS Infrastructure,8200,expense,Increased compute for new AI features
|
||||
2026-01-15,Expenses,Marketing - Paid Ads,12000,expense,Google Ads and LinkedIn campaigns
|
||||
2026-01-18,Revenue,Consulting Services,14500,income,Custom integration for Acme Corp
|
||||
2026-01-20,Expenses,Customer Success,15000,expense,3 CSMs + support tools (Intercom)
|
||||
2026-01-22,Expenses,AI Model Costs,4200,expense,OpenAI API usage for product features
|
||||
2026-01-25,Revenue,Marketplace Sales,12800,income,Template and plugin sales
|
||||
2026-01-28,Expenses,Office & Equipment,3500,expense,New laptops and coworking spaces
|
||||
2026-02-03,Revenue,Enterprise Subscriptions,31000,income,2 new customers + expansion from TechFlow
|
||||
2026-02-03,Revenue,Pro Tier Upgrades,22500,income,31 upgrades + reduced churn
|
||||
2026-02-05,Revenue,API Usage Overages,11800,income,DataViz Inc heavy API usage spike
|
||||
2026-02-07,Expenses,Engineering Salaries,42000,expense,Same headcount as January
|
||||
2026-02-07,Expenses,Product Team,18000,expense,No changes to product team
|
||||
2026-02-10,Expenses,AWS Infrastructure,9500,expense,Traffic spike from viral social post
|
||||
2026-02-12,Expenses,Marketing - Paid Ads,15000,expense,Increased ad spend for Q1 push
|
||||
2026-02-14,Revenue,Consulting Services,18000,income,2 custom projects (TechFlow + new client)
|
||||
2026-02-18,Expenses,Customer Success,16500,expense,Hired 1 additional CSM
|
||||
2026-02-20,Expenses,AI Model Costs,5800,expense,Increased usage from new AI features launch
|
||||
2026-02-22,Revenue,Marketplace Sales,14200,income,Top template hit featured list
|
||||
2026-02-25,Expenses,Conference & Travel,4500,expense,Team attended SaaS Conference 2026
|
||||
2026-02-27,Revenue,Partnership Revenue,11500,income,Referral fees from integration partners
|
||||
2026-03-02,Revenue,Enterprise Subscriptions,35000,income,Major win: Fortune 500 customer signed
|
||||
2026-03-02,Revenue,Pro Tier Upgrades,26000,income,42 upgrades - best month yet
|
||||
2026-03-05,Revenue,API Usage Overages,13200,income,Consistent high usage across top tier
|
||||
2026-03-08,Expenses,Engineering Salaries,48000,expense,Hired 1 senior engineer for AI team
|
||||
2026-03-08,Expenses,Product Team,21000,expense,Promoted designer to senior level
|
||||
2026-03-10,Expenses,AWS Infrastructure,11000,expense,Scaled infrastructure for enterprise client
|
||||
2026-03-12,Expenses,Marketing - Paid Ads,18000,expense,Doubled down on successful campaigns
|
||||
2026-03-14,Revenue,Consulting Services,21500,income,Fortune 500 onboarding + 2 other projects
|
||||
2026-03-16,Expenses,Customer Success,19500,expense,Hired dedicated enterprise CSM
|
||||
2026-03-18,Expenses,AI Model Costs,7200,expense,Fortune 500 client heavy AI usage
|
||||
2026-03-20,Revenue,Marketplace Sales,15800,income,3 new templates in top 10
|
||||
2026-03-22,Expenses,Sales & BD,12000,expense,Hired first sales rep for enterprise
|
||||
2026-03-24,Revenue,Partnership Revenue,14200,income,New integration partnerships launched
|
||||
2026-03-26,Expenses,Security & Compliance,6500,expense,SOC 2 audit and security tools
|
||||
2026-03-28,Revenue,Training & Workshops,10200,income,Conducted 2 customer training sessions
|
||||
|
@@ -0,0 +1,22 @@
|
||||
from langchain.tools import tool
|
||||
from pathlib import Path
|
||||
import csv
|
||||
|
||||
# Read data at module load time to avoid file I/O issues in
|
||||
# LangGraph Cloud's sandboxed tool execution environment.
|
||||
_csv_path = Path(__file__).parent / "db.csv"
|
||||
with open(_csv_path) as _f:
|
||||
_cached_data = list(csv.DictReader(_f))
|
||||
|
||||
|
||||
@tool
|
||||
def query_data(query: str):
|
||||
"""
|
||||
Query the database, takes natural language. Always call before showing a chart or graph.
|
||||
"""
|
||||
import time
|
||||
|
||||
print(
|
||||
f"[A2UI-DEBUG] query_data called: query='{query[:60]}' at {time.strftime('%H:%M:%S')}"
|
||||
)
|
||||
return _cached_data
|
||||
@@ -0,0 +1,56 @@
|
||||
from langchain.agents import AgentState as BaseAgentState
|
||||
from langchain.tools import ToolRuntime, tool
|
||||
from langchain.messages import ToolMessage
|
||||
from langgraph.types import Command
|
||||
from typing import TypedDict, Literal
|
||||
import uuid
|
||||
|
||||
|
||||
class Todo(TypedDict):
|
||||
id: str
|
||||
title: str
|
||||
description: str
|
||||
emoji: str
|
||||
status: Literal["pending", "completed"]
|
||||
|
||||
|
||||
class AgentState(BaseAgentState):
|
||||
todos: list[Todo]
|
||||
|
||||
|
||||
@tool
|
||||
def manage_todos(todos: list[Todo], runtime: ToolRuntime) -> Command:
|
||||
"""
|
||||
Manage the current todos.
|
||||
"""
|
||||
# Ensure all todos have IDs that are unique
|
||||
for todo in todos:
|
||||
if "id" not in todo or not todo["id"]:
|
||||
todo["id"] = str(uuid.uuid4())
|
||||
|
||||
# Update the state
|
||||
return Command(
|
||||
update={
|
||||
"todos": todos,
|
||||
"messages": [
|
||||
ToolMessage(
|
||||
content="Successfully updated todos",
|
||||
tool_call_id=runtime.tool_call_id,
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@tool
|
||||
def get_todos(runtime: ToolRuntime):
|
||||
"""
|
||||
Get the current todos.
|
||||
"""
|
||||
return runtime.state.get("todos", [])
|
||||
|
||||
|
||||
todo_tools = [
|
||||
manage_todos,
|
||||
get_todos,
|
||||
]
|
||||
+1993
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user