diff --git a/agent/poetry.lock b/agent/poetry.lock index f472ea7..6cc5f1c 100644 --- a/agent/poetry.lock +++ b/agent/poetry.lock @@ -2,36 +2,35 @@ [[package]] name = "ag-ui-langgraph" -version = "0.0.1" -description = "" +version = "0.0.18a0" +description = "Implementation of the AG-UI protocol for LangGraph." optional = false python-versions = "<3.14,>=3.10" groups = ["main"] files = [ - {file = "ag_ui_langgraph-0.0.1-py3-none-any.whl", hash = "sha256:b7c90b90fb8d736fc7426219166fa63572bcc10171253d5c982544fa256b568f"}, - {file = "ag_ui_langgraph-0.0.1.tar.gz", hash = "sha256:3208916af56f5c2dd395c85889d089f12c84207bc3e0ae12acc045abe845ce09"}, + {file = "ag_ui_langgraph-0.0.18a0-py3-none-any.whl", hash = "sha256:8bfd26f475adf2cafba4baa4e124a97463203714cd8232970c9f8763b0cc2e30"}, + {file = "ag_ui_langgraph-0.0.18a0.tar.gz", hash = "sha256:a9951ed205016be1a44c4ea73d6117b3d5daa3d9bb69184987847beea62907a9"}, ] [package.dependencies] -ag-ui-protocol = "0.1.7" -fastapi = {version = ">=0.115.12,<0.116.0", optional = true, markers = "extra == \"fastapi\""} +ag-ui-protocol = "0.2.0a0" langchain = ">=0.3.0" langchain-core = ">=0.3.0" -langgraph = ">=0.3.25,<=0.5.0" +langgraph = ">=0.3.25,<1.1.0" [package.extras] fastapi = ["fastapi (>=0.115.12,<0.116.0)"] [[package]] name = "ag-ui-protocol" -version = "0.1.7" +version = "0.2.0a0" description = "" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "ag_ui_protocol-0.1.7-py3-none-any.whl", hash = "sha256:8c821662ca6e9852569022f449b9f7aeb3f16aa75390fa8c28ceae2cce642baa"}, - {file = "ag_ui_protocol-0.1.7.tar.gz", hash = "sha256:0e93fd9f7c74d52afbd824d6e9738bd3422e859503905ba7582481cbc3c67ab2"}, + {file = "ag_ui_protocol-0.2.0a0-py3-none-any.whl", hash = "sha256:3f1fbf7ea1f0333ce8034cdc67df192b3c1a85a6c39dc866c35f9329df85b9cf"}, + {file = "ag_ui_protocol-0.2.0a0.tar.gz", hash = "sha256:422bc284f9ab7019d2796641bac96faddd20eba815e1bad6122d72be1f989b80"}, ] [package.dependencies] @@ -308,29 +307,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "copilotkit" -version = "0.1.55" -description = "CopilotKit python SDK" -optional = false -python-versions = "<3.13,>=3.10" -groups = ["main"] -files = [ - {file = "copilotkit-0.1.55-py3-none-any.whl", hash = "sha256:aa4a5026190760d6ba1dbd920849d62c3500a9c6d583adf4c02950aed75ffbf7"}, - {file = "copilotkit-0.1.55.tar.gz", hash = "sha256:d095be62c58f5cc25ab9e34839ec16ef64e45a88378e5dbf4770efcb8e10ac43"}, -] - -[package.dependencies] -ag-ui-langgraph = {version = "0.0.1", extras = ["fastapi"]} -fastapi = ">=0.115.0,<0.116.0" -langchain = ">=0.3.4,<=0.3.26" -langgraph = ">=0.3.18,<=0.5.0" -partialjson = ">=0.0.8,<0.0.9" -toml = ">=0.10.2,<0.11.0" - -[package.extras] -crewai = ["crewai (==0.118.0)"] - [[package]] name = "distro" version = "1.9.0" @@ -1024,18 +1000,6 @@ files = [ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] -[[package]] -name = "partialjson" -version = "0.0.8" -description = "Parse incomplete or partial json" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "partialjson-0.0.8-py3-none-any.whl", hash = "sha256:22c6c60944137f931a7033fa0eeee2d74b49114f3d45c25a560b07a6ebf22b76"}, - {file = "partialjson-0.0.8.tar.gz", hash = "sha256:91217e19a15049332df534477f56420065ad1729cedee7d8c7433e1d2acc7dca"}, -] - [[package]] name = "pycparser" version = "2.22" @@ -1592,18 +1556,6 @@ requests = ">=2.26.0" [package.extras] blobfile = ["blobfile (>=2)"] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -1940,4 +1892,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "3d6e016ef0a5b22010a8e175f17129a4fbe7d0287539ca58d786ad9497593769" +content-hash = "6a108e2de50bd6623b9a24193c405e9583df1cda6aeae123035547d68ed00a94" diff --git a/agent/pyproject.toml b/agent/pyproject.toml index 42c2012..3bfb56f 100644 --- a/agent/pyproject.toml +++ b/agent/pyproject.toml @@ -9,7 +9,7 @@ package-mode = false [tool.poetry.dependencies] python = ">=3.10,<3.13" -copilotkit = "0.1.55" +ag-ui-langgraph = "0.0.18a0" langchain = "0.3.26" langchain-openai = "0.3.28" langgraph = "0.4.10" diff --git a/agent/sample_agent/demo.py b/agent/sample_agent/demo.py index 983facd..e44da69 100644 --- a/agent/sample_agent/demo.py +++ b/agent/sample_agent/demo.py @@ -5,26 +5,27 @@ import os from dotenv import load_dotenv -load_dotenv() # pylint: disable=wrong-import-position from fastapi import FastAPI import uvicorn -from copilotkit import LangGraphAGUIAgent from sample_agent.agent import graph -from ag_ui_langgraph import add_langgraph_fastapi_endpoint +from ag_ui_langgraph import add_langgraph_fastapi_endpoint, LangGraphAgent + +_ = load_dotenv() # pylint: disable=wrong-import-position app = FastAPI() add_langgraph_fastapi_endpoint( app=app, - agent=LangGraphAGUIAgent( + agent=LangGraphAgent( name="sample_agent", description="An example agent to use as a starting point for your own agent.", - graph=graph + graph=graph, ), - path="/" + path="/", ) + def main(): """Run the uvicorn server.""" port = int(os.getenv("PORT", "8123")) diff --git a/package.json b/package.json index 44de254..923ad69 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,12 @@ "install:agent": "cd agent && poetry install" }, "dependencies": { - "@ag-ui/langgraph": "0.0.6", - "@ai-sdk/openai": "^1.3.22", - "@copilotkit/react-core": "1.9.2", - "@copilotkit/react-ui": "1.9.2", - "@copilotkit/runtime": "1.9.2", + "@ag-ui/core": "0.0.40-alpha.7", + "@ag-ui/langgraph": "0.0.19-alpha.1", + "@copilotkitnext/core": "0.0.19-threads-and-attachements.1", + "@copilotkitnext/react": "0.0.19-threads-and-attachements.1", + "@copilotkitnext/runtime": "0.0.19-threads-and-attachements.1", + "hono": "^4.10.1", "next": "15.3.2", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/src/app/api/copilotkit/[...slug]/route.ts b/src/app/api/copilotkit/[...slug]/route.ts new file mode 100644 index 0000000..d3522d6 --- /dev/null +++ b/src/app/api/copilotkit/[...slug]/route.ts @@ -0,0 +1,26 @@ +import { LangGraphHttpAgent } from "@ag-ui/langgraph"; +import { + CopilotRuntime, + createCopilotEndpoint, + AgentRunner, +} from "@copilotkitnext/runtime"; +import { handle } from "hono/vercel"; + +const runtime = new CopilotRuntime({ + agents: { + default: new LangGraphHttpAgent({ + url: process.env.AGENT_URL || "http://localhost:8123", + }), + foo: new LangGraphHttpAgent({ + url: process.env.AGENT_URL || "http://localhost:8123", + }), + }, +}); + +const app = createCopilotEndpoint({ + runtime, + basePath: "/api/copilotkit", +}); + +export const GET = handle(app); +export const POST = handle(app); diff --git a/src/app/api/copilotkit/route.ts b/src/app/api/copilotkit/route.ts deleted file mode 100644 index 6bf15f7..0000000 --- a/src/app/api/copilotkit/route.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - CopilotRuntime, - ExperimentalEmptyAdapter, - copilotRuntimeNextJSAppRouterEndpoint, -} from "@copilotkit/runtime"; - -import { LangGraphHttpAgent } from "@ag-ui/langgraph" -import { NextRequest } from "next/server"; - -// 1. You can use any service adapter here for multi-agent support. We use -// the empty adapter since we're only using one agent. -const serviceAdapter = new ExperimentalEmptyAdapter(); - -// 2. Create the CopilotRuntime instance and utilize the LangGraph AG-UI -// integration to setup the connection. -const runtime = new CopilotRuntime({ - agents: { - "sample_agent": new LangGraphHttpAgent({ - url: process.env.AGENT_URL || "http://localhost:8123", - }), - } -}); - -// 3. Build a Next.js API route that handles the CopilotKit runtime requests. -export const POST = async (req: NextRequest) => { - const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ - runtime, - serviceAdapter, - endpoint: "/api/copilotkit", - }); - - return handleRequest(req); -}; \ No newline at end of file diff --git a/src/app/components/gen-ui/weather.tsx b/src/app/components/gen-ui/weather.tsx new file mode 100644 index 0000000..2589a42 --- /dev/null +++ b/src/app/components/gen-ui/weather.tsx @@ -0,0 +1,84 @@ +"use client"; + +// Weather card component where the location and themeColor are based on what the agent +// sets via tool calls. + +import { defineToolCallRenderer } from "@copilotkitnext/react"; + +// @ts-ignore +export const weatherToolRenderer = defineToolCallRenderer({ + name: "get_weather", + render: ({ args }) => { + const { location } = args; + const themeColor = "blue"; + return ; + }, +}); + +function WeatherCard({ + location, + themeColor, +}: { + location?: string; + themeColor: string; +}) { + return ( +
+
+
+
+

+ {location} +

+

Current Weather

+
+ +
+ +
+
70°
+
Clear skies
+
+ +
+
+
+

Humidity

+

45%

+
+
+

Wind

+

5 mph

+
+
+

Feels Like

+

72°

+
+
+
+
+
+ ); +} + +// Simple sun icon for the weather card +function SunIcon() { + return ( + + + + + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3c6a56d..f470f76 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,9 @@ import type { Metadata } from "next"; -import { CopilotKit } from "@copilotkit/react-core"; +import { CopilotKitProvider } from "@copilotkitnext/react"; import "./globals.css"; -import "@copilotkit/react-ui/styles.css"; +import "@copilotkitnext/react/styles.css"; +import { weatherToolRenderer } from "./components/gen-ui/weather"; export const metadata: Metadata = { title: "Create Next App", @@ -17,9 +18,12 @@ export default function RootLayout({ return ( - + {children} - + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index cfd777a..28e56e4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,171 +1,58 @@ "use client"; -import { useCoAgent, useCopilotAction } from "@copilotkit/react-core"; -import { CopilotKitCSSProperties, CopilotSidebar } from "@copilotkit/react-ui"; -import { useState } from "react"; +import { + useFrontendTool, + useAgent, + CopilotChat, + CopilotThreadList, + CopilotChatConfigurationProvider, +} from "@copilotkitnext/react"; export default function CopilotKitPage() { - const [themeColor, setThemeColor] = useState("#6366f1"); - - // 🪁 Frontend Actions: https://docs.copilotkit.ai/guides/frontend-actions - useCopilotAction({ - name: "setThemeColor", - parameters: [{ - name: "themeColor", - description: "The theme color to set. Make sure to pick nice colors.", - required: true, - }], - handler({ themeColor }) { - setThemeColor(themeColor); - }, - }); - - return ( -
- - -
- ); -} - -// State of the agent, make sure this aligns with your agent's state. -type AgentState = { - proverbs: string[]; -} - -function YourMainContent({ themeColor }: { themeColor: string }) { - // 🪁 Shared State: https://docs.copilotkit.ai/coagents/shared-state - const { state, setState } = useCoAgent({ - name: "sample_agent", - initialState: { - proverbs: [ - "CopilotKit may be new, but its the best thing since sliced bread.", - ], - }, - }) - - // 🪁 Frontend Actions: https://docs.copilotkit.ai/coagents/frontend-actions - useCopilotAction({ - name: "addProverb", - parameters: [{ - name: "proverb", - description: "The proverb to add. Make it witty, short and concise.", - required: true, - }], - handler: ({ proverb }) => { - setState({ - ...state, - proverbs: [...state.proverbs, proverb], - }); - }, - }); - - //🪁 Generative UI: https://docs.copilotkit.ai/coagents/generative-ui - useCopilotAction({ - name: "get_weather", - description: "Get the weather for a given location.", - available: "disabled", - parameters: [ - { name: "location", type: "string", required: true }, - ], - render: ({ args }) => { - return - }, - }); + const { agent } = useAgent(); + + // example implementation using file-uploads + const handleAddFile = () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = (e: Event) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (!file?.type.startsWith("image/")) return; + + const reader = new FileReader(); + reader.onload = () => { + agent?.addMessage({ + role: "user", + id: crypto.randomUUID(), + content: [ + { + type: "binary", + mimeType: file.type, + url: reader.result as string, + }, + ], + }); + agent?.runAgent(); + }; + reader.readAsDataURL(file); + }; + input.click(); + }; return ( -
-
-

Proverbs

-

This is a demonstrative page, but it could be anything you want! 🪁

-
-
- {state.proverbs?.map((proverb, index) => ( -
-

{proverb}

- -
- ))} +
+ +
+ +
- {state.proverbs?.length === 0 &&

- No proverbs yet. Ask the assistant to add some! -

} -
-
- ); -} - -// Simple sun icon for the weather card -function SunIcon() { - return ( - - - - - ); -} - -// Weather card component where the location and themeColor are based on what the agent -// sets via tool calls. -function WeatherCard({ location, themeColor }: { location?: string, themeColor: string }) { - return ( -
-
-
-
-

{location}

-

Current Weather

-
- -
- -
-
70°
-
Clear skies
-
- -
-
-
-

Humidity

-

45%

-
-
-

Wind

-

5 mph

-
-
-

Feels Like

-

72°

-
-
-
-
-
+ + ); }