Home Web Front-end JS Tutorial Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js

Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js

Jan 08, 2025 pm 10:52 PM

AI-powered applications are evolving beyond just autonomous agents performing tasks. A new approach involving Human-in-the-Loop allows users to provide feedback, review results, and decide the next steps for the AI. These run-time agents are known as CoAgents.

TL;DR

In this tutorial, you'll learn how to build a Perplexity clone using LangGraph, CopilotKit, and Tavily.

Time to start building!

What is an Agentic Copilot?

Agentic copilots are how CopilotKit brings LangGraph agents into your application.

CoAgents are CopilotKit's approach to building agentic experiences!

In short, it will handle user requests by performing multiple search queries and stream the search back with status and results in real-time to the client.

Checkout CopilotKit ⭐️


Prerequisites

To fully understand this tutorial, you need to have a basic understanding of React or Next.js.

We'll also make use of the following:

  • Python - a popular programming language for building AI agents with LangGraph; make sure it is installed on your computer.
  • LangGraph - a framework for creating and deploying AI agents. It also helps to define the control flows and actions to be performed by the agent.
  • OpenAI API Key - to enable us to perform various tasks using the GPT models; for this tutorial, ensure you have access to the GPT-4 model.
  • Tavily AI - a search engine that enables AI agents to conduct research and access real-time knowledge within the application.
  • CopilotKit - an open-source copilot framework for building custom AI chatbots, in-app AI agents, and text areas.
  • Shad Cn UI - provides a collection of reusable UI components within the application.

How to Create AI Agents with LangGraph and CopilotKit

In this section, you'll learn how to create an AI agent using LangGraph and CopilotKit.

First, clone the CopilotKit CoAgents starter repository. The ui directory contains the frontend for the Next.js application, and the agent directory holds the CoAgent for the application.

Inside the agent directory, install the project dependencies using Poetry.

cd agent
poetry install

Create a .env file within the agent folder and copy your OpenAI and Tavily AI API keys into the file:

OPENAI_API_KEY=
TAVILY_API_KEY=

Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js

Copy the code snippet below into the agent.py file:

"""
This is the main entry point for the AI.
It defines the workflow graph and the entry point for the agent.
"""
# pylint: disable=line-too-long, unused-import
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver

from ai_researcher.state import AgentState
from ai_researcher.steps import steps_node
from ai_researcher.search import search_node
from ai_researcher.summarize import summarize_node
from ai_researcher.extract import extract_node

def route(state):
    """Route to research nodes."""
    if not state.get("steps", None):
        return END

    current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)

    if not current_step:
        return "summarize_node"

    if current_step["type"] == "search":
        return "search_node"

    raise ValueError(f"Unknown step type: {current_step['type']}")

# Define a new graph
workflow = StateGraph(AgentState)
workflow.add_node("steps_node", steps_node)
workflow.add_node("search_node", search_node)
workflow.add_node("summarize_node", summarize_node)
workflow.add_node("extract_node", extract_node)
# Chatbot
workflow.set_entry_point("steps_node")

workflow.add_conditional_edges(
    "steps_node", 
    route,
    ["summarize_node", "search_node", END]
)

workflow.add_edge("search_node", "extract_node")

workflow.add_conditional_edges(
    "extract_node",
    route,
    ["summarize_node", "search_node"]
)

workflow.add_edge("summarize_node", END)

memory = MemorySaver()
graph = workflow.compile(checkpointer=memory)

The code snippet above defines the LangGraph agent workflow. It starts from the steps_node, searches for the results, summarizes them, and extracts the key points.

Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js

Next create a demo.py file with the code snippet below:

cd agent
poetry install

The code above creates a FastAPI endpoint that hosts the LangGraph agent and connects it to the CopilotKit SDK.

You can copy the remaining code for creating the CoAgent from the GitHub repository. In the following sections, you'll learn how to build the user interface for the Perplexity clone and handle search requests using CopilotKit.


Building the application interface with Next.js

In this section, I'll walk you through the process of building the user interface for the application.

First, create a Next.js Typescript project by running the code snippet below:

OPENAI_API_KEY=
TAVILY_API_KEY=

Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js

Install the ShadCn UI library to the newly created project by running the code snippet below:

"""
This is the main entry point for the AI.
It defines the workflow graph and the entry point for the agent.
"""
# pylint: disable=line-too-long, unused-import
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver

from ai_researcher.state import AgentState
from ai_researcher.steps import steps_node
from ai_researcher.search import search_node
from ai_researcher.summarize import summarize_node
from ai_researcher.extract import extract_node

def route(state):
    """Route to research nodes."""
    if not state.get("steps", None):
        return END

    current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)

    if not current_step:
        return "summarize_node"

    if current_step["type"] == "search":
        return "search_node"

    raise ValueError(f"Unknown step type: {current_step['type']}")

# Define a new graph
workflow = StateGraph(AgentState)
workflow.add_node("steps_node", steps_node)
workflow.add_node("search_node", search_node)
workflow.add_node("summarize_node", summarize_node)
workflow.add_node("extract_node", extract_node)
# Chatbot
workflow.set_entry_point("steps_node")

workflow.add_conditional_edges(
    "steps_node", 
    route,
    ["summarize_node", "search_node", END]
)

workflow.add_edge("search_node", "extract_node")

workflow.add_conditional_edges(
    "extract_node",
    route,
    ["summarize_node", "search_node"]
)

workflow.add_edge("summarize_node", END)

memory = MemorySaver()
graph = workflow.compile(checkpointer=memory)

Next, create a components folder at the root of the Next.js project, then copy the ui folder from this GitHub repository into that folder. Shadcn allows you to easily add various components to your application by installing them via the command line.

In addition to the Shadcn components, you'll need to create a few components representing different parts of the application interface. Run the following code snippet inside the components folder to add these components to the Next.js project:

"""Demo"""

import os
from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI
import uvicorn
from copilotkit.integrations.fastapi import add_fastapi_endpoint
from copilotkit import CopilotKitSDK, LangGraphAgent
from ai_researcher.agent import graph

app = FastAPI()
sdk = CopilotKitSDK(
    agents=[
        LangGraphAgent(
            name="ai_researcher",
            description="Search agent.",
            graph=graph,
        )
    ],
)

add_fastapi_endpoint(app, sdk, "/copilotkit")

# add new route for health check
@app.get("/health")
def health():
    """Health check."""
    return {"status": "ok"}

def main():
    """Run the uvicorn server."""
    port = int(os.getenv("PORT", "8000"))
    uvicorn.run("ai_researcher.demo:app", host="0.0.0.0", port=port, reload=True)

Copy the code snippet below into the app/page.tsx file:

# ?? Navigate into the ui folder
npx create-next-app ./

In the code snippet above, ResearchProvider is a custom React context provider that shares the user's search query and results, making them accessible to all components within the application. The ResearchWrapper component contains the core application elements and manages the UI.

Create a lib folder containing a research-provider.tsx file at the root of the Next.js project and copy the code below into the file:

npx shadcn@latest init

The states are declared and saved to the ResearchContext to ensure they are properly managed across multiple components within the application.

Create a ResearchWrapper component as shown below:

cd agent
poetry install

The ResearchWrapper component renders the HomeView component as the default view and displays the ResultView when a search query is provided. The useResearchContext hook enables us to access the researchQuery state and update the view accordingly.

Finally, create the HomeView component to render the application home page interface.

OPENAI_API_KEY=
TAVILY_API_KEY=

Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js


How to Connect your CoAgent to a Next.js Application

In this section, you'll learn how to connect the CopilotKit CoAgent to your Next.js application to enable users to perform search operations within the application.

Install the following CopilotKit packages and the OpenAI Node.js SDK. The CopilotKit packages allow the co-agent to interact with the React state values and make decisions within the application.

"""
This is the main entry point for the AI.
It defines the workflow graph and the entry point for the agent.
"""
# pylint: disable=line-too-long, unused-import
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver

from ai_researcher.state import AgentState
from ai_researcher.steps import steps_node
from ai_researcher.search import search_node
from ai_researcher.summarize import summarize_node
from ai_researcher.extract import extract_node

def route(state):
    """Route to research nodes."""
    if not state.get("steps", None):
        return END

    current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)

    if not current_step:
        return "summarize_node"

    if current_step["type"] == "search":
        return "search_node"

    raise ValueError(f"Unknown step type: {current_step['type']}")

# Define a new graph
workflow = StateGraph(AgentState)
workflow.add_node("steps_node", steps_node)
workflow.add_node("search_node", search_node)
workflow.add_node("summarize_node", summarize_node)
workflow.add_node("extract_node", extract_node)
# Chatbot
workflow.set_entry_point("steps_node")

workflow.add_conditional_edges(
    "steps_node", 
    route,
    ["summarize_node", "search_node", END]
)

workflow.add_edge("search_node", "extract_node")

workflow.add_conditional_edges(
    "extract_node",
    route,
    ["summarize_node", "search_node"]
)

workflow.add_edge("summarize_node", END)

memory = MemorySaver()
graph = workflow.compile(checkpointer=memory)

Create an api folder within the Next.js app folder. Inside the api folder, create a copilotkit directory containing a route.ts file. This will create an API endpoint (/api/copilotkit) that connects the frontend application to the CopilotKit CoAgent.

"""Demo"""

import os
from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI
import uvicorn
from copilotkit.integrations.fastapi import add_fastapi_endpoint
from copilotkit import CopilotKitSDK, LangGraphAgent
from ai_researcher.agent import graph

app = FastAPI()
sdk = CopilotKitSDK(
    agents=[
        LangGraphAgent(
            name="ai_researcher",
            description="Search agent.",
            graph=graph,
        )
    ],
)

add_fastapi_endpoint(app, sdk, "/copilotkit")

# add new route for health check
@app.get("/health")
def health():
    """Health check."""
    return {"status": "ok"}

def main():
    """Run the uvicorn server."""
    port = int(os.getenv("PORT", "8000"))
    uvicorn.run("ai_researcher.demo:app", host="0.0.0.0", port=port, reload=True)

Copy the code snippet below into the api/copilotkit/route.ts file:

# ?? Navigate into the ui folder
npx create-next-app ./

The code snippet above sets up the CopilotKit runtime at the /api/copilotkit API endpoint, allowing CopilotKit to process user requests through the AI co-agent.

Finally, update the app/page.tsx by wrapping the entire application with the CopilotKit component which provides the copilot context to all application components.

npx shadcn@latest init

The CopilotKit component wraps the entire application and accepts two props - runtimeUrl and agent. The runtimeUrl is the backend API route that hosts the AI agent and agent is the name of the agent performing the action.

Accepting Requests and Streaming Responses to the Frontend

To enable CopilotKit to access and process user inputs, it provides the useCoAgent hook, which allows access to the agent's state from anywhere within the application.

For example, the code snippet below demonstrates how to use the useCoAgent hook. The state variable allows access to the agent's current state, setState is used to modify the state, and the run function executes instructions using the agent. The start and stop functions initiate and halt the agent's execution.

cd agent
poetry install

Update the HomeView component to execute the agent when a search query is provided.

OPENAI_API_KEY=
TAVILY_API_KEY=

Next, you can stream the search results to the the ResultsView by accessing the state variable within the useCoAgent hook. Copy the code snippet below into the ResultsView component.

"""
This is the main entry point for the AI.
It defines the workflow graph and the entry point for the agent.
"""
# pylint: disable=line-too-long, unused-import
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver

from ai_researcher.state import AgentState
from ai_researcher.steps import steps_node
from ai_researcher.search import search_node
from ai_researcher.summarize import summarize_node
from ai_researcher.extract import extract_node

def route(state):
    """Route to research nodes."""
    if not state.get("steps", None):
        return END

    current_step = next((step for step in state["steps"] if step["status"] == "pending"), None)

    if not current_step:
        return "summarize_node"

    if current_step["type"] == "search":
        return "search_node"

    raise ValueError(f"Unknown step type: {current_step['type']}")

# Define a new graph
workflow = StateGraph(AgentState)
workflow.add_node("steps_node", steps_node)
workflow.add_node("search_node", search_node)
workflow.add_node("summarize_node", summarize_node)
workflow.add_node("extract_node", extract_node)
# Chatbot
workflow.set_entry_point("steps_node")

workflow.add_conditional_edges(
    "steps_node", 
    route,
    ["summarize_node", "search_node", END]
)

workflow.add_edge("search_node", "extract_node")

workflow.add_conditional_edges(
    "extract_node",
    route,
    ["summarize_node", "search_node"]
)

workflow.add_edge("summarize_node", END)

memory = MemorySaver()
graph = workflow.compile(checkpointer=memory)

The code snippet above retrieves the search results from the agent's state and streams them to the frontend using the useCoAgent hook. The search results are returned in markdown format and passed into the AnswerMarkdown component, which renders the content on the page.

Finally, copy the code snippet below into the AnswerMarkdown component. This will render the markdown content as formatted text using the React Markdown library.

"""Demo"""

import os
from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI
import uvicorn
from copilotkit.integrations.fastapi import add_fastapi_endpoint
from copilotkit import CopilotKitSDK, LangGraphAgent
from ai_researcher.agent import graph

app = FastAPI()
sdk = CopilotKitSDK(
    agents=[
        LangGraphAgent(
            name="ai_researcher",
            description="Search agent.",
            graph=graph,
        )
    ],
)

add_fastapi_endpoint(app, sdk, "/copilotkit")

# add new route for health check
@app.get("/health")
def health():
    """Health check."""
    return {"status": "ok"}

def main():
    """Run the uvicorn server."""
    port = int(os.getenv("PORT", "8000"))
    uvicorn.run("ai_researcher.demo:app", host="0.0.0.0", port=port, reload=True)

Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js

Congratulations! You've completed the project for this tutorial. You can also watch the video recording here:

Complete Webinar Recording


Wrapping it up

LLM intelligence is the most effective when it works alongside human intelligence, and CopilotKit CoAgents allows you to integrate AI agents, copilots, and various types of assistants into your software applications in just a few minutes.

If you need to build an AI product or integrate AI agents into your app, you should consider CopilotKit.

The source code for this tutorial is available on GitHub:

https://github.com/CopilotKit/CopilotKit/tree/main/examples/coagents-ai-researcher

Thank you for reading!

The above is the detailed content of Build a clone of Perplexity with LangGraph, CopilotKit, Tavily & Next.js. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

PHP Tutorial
1502
276
How to make an HTTP request in Node.js? How to make an HTTP request in Node.js? Jul 13, 2025 am 02:18 AM

There are three common ways to initiate HTTP requests in Node.js: use built-in modules, axios, and node-fetch. 1. Use the built-in http/https module without dependencies, which is suitable for basic scenarios, but requires manual processing of data stitching and error monitoring, such as using https.get() to obtain data or send POST requests through .write(); 2.axios is a third-party library based on Promise. It has concise syntax and powerful functions, supports async/await, automatic JSON conversion, interceptor, etc. It is recommended to simplify asynchronous request operations; 3.node-fetch provides a style similar to browser fetch, based on Promise and simple syntax

JavaScript Data Types: Primitive vs Reference JavaScript Data Types: Primitive vs Reference Jul 13, 2025 am 02:43 AM

JavaScript data types are divided into primitive types and reference types. Primitive types include string, number, boolean, null, undefined, and symbol. The values are immutable and copies are copied when assigning values, so they do not affect each other; reference types such as objects, arrays and functions store memory addresses, and variables pointing to the same object will affect each other. Typeof and instanceof can be used to determine types, but pay attention to the historical issues of typeofnull. Understanding these two types of differences can help write more stable and reliable code.

JavaScript time object, someone builds an eactexe, faster website on Google Chrome, etc. JavaScript time object, someone builds an eactexe, faster website on Google Chrome, etc. Jul 08, 2025 pm 02:27 PM

Hello, JavaScript developers! Welcome to this week's JavaScript news! This week we will focus on: Oracle's trademark dispute with Deno, new JavaScript time objects are supported by browsers, Google Chrome updates, and some powerful developer tools. Let's get started! Oracle's trademark dispute with Deno Oracle's attempt to register a "JavaScript" trademark has caused controversy. Ryan Dahl, the creator of Node.js and Deno, has filed a petition to cancel the trademark, and he believes that JavaScript is an open standard and should not be used by Oracle

Handling Promises: Chaining, Error Handling, and Promise Combinators in JavaScript Handling Promises: Chaining, Error Handling, and Promise Combinators in JavaScript Jul 08, 2025 am 02:40 AM

Promise is the core mechanism for handling asynchronous operations in JavaScript. Understanding chain calls, error handling and combiners is the key to mastering their applications. 1. The chain call returns a new Promise through .then() to realize asynchronous process concatenation. Each .then() receives the previous result and can return a value or a Promise; 2. Error handling should use .catch() to catch exceptions to avoid silent failures, and can return the default value in catch to continue the process; 3. Combinators such as Promise.all() (successfully successful only after all success), Promise.race() (the first completion is returned) and Promise.allSettled() (waiting for all completions)

What is the cache API and how is it used with Service Workers? What is the cache API and how is it used with Service Workers? Jul 08, 2025 am 02:43 AM

CacheAPI is a tool provided by the browser to cache network requests, which is often used in conjunction with ServiceWorker to improve website performance and offline experience. 1. It allows developers to manually store resources such as scripts, style sheets, pictures, etc.; 2. It can match cache responses according to requests; 3. It supports deleting specific caches or clearing the entire cache; 4. It can implement cache priority or network priority strategies through ServiceWorker listening to fetch events; 5. It is often used for offline support, speed up repeated access speed, preloading key resources and background update content; 6. When using it, you need to pay attention to cache version control, storage restrictions and the difference from HTTP caching mechanism.

JS roundup: a deep dive into the JavaScript event loop JS roundup: a deep dive into the JavaScript event loop Jul 08, 2025 am 02:24 AM

JavaScript's event loop manages asynchronous operations by coordinating call stacks, WebAPIs, and task queues. 1. The call stack executes synchronous code, and when encountering asynchronous tasks, it is handed over to WebAPI for processing; 2. After the WebAPI completes the task in the background, it puts the callback into the corresponding queue (macro task or micro task); 3. The event loop checks whether the call stack is empty. If it is empty, the callback is taken out from the queue and pushed into the call stack for execution; 4. Micro tasks (such as Promise.then) take precedence over macro tasks (such as setTimeout); 5. Understanding the event loop helps to avoid blocking the main thread and optimize the code execution order.

Understanding Event Bubbling and Capturing in JavaScript DOM events Understanding Event Bubbling and Capturing in JavaScript DOM events Jul 08, 2025 am 02:36 AM

Event bubbles propagate from the target element outward to the ancestor node, while event capture propagates from the outer layer inward to the target element. 1. Event bubbles: After clicking the child element, the event triggers the listener of the parent element upwards in turn. For example, after clicking the button, it outputs Childclicked first, and then Parentclicked. 2. Event capture: Set the third parameter to true, so that the listener is executed in the capture stage, such as triggering the capture listener of the parent element before clicking the button. 3. Practical uses include unified management of child element events, interception preprocessing and performance optimization. 4. The DOM event stream is divided into three stages: capture, target and bubble, and the default listener is executed in the bubble stage.

A JS roundup of higher-order functions beyond map and filter A JS roundup of higher-order functions beyond map and filter Jul 10, 2025 am 11:41 AM

In JavaScript arrays, in addition to map and filter, there are other powerful and infrequently used methods. 1. Reduce can not only sum, but also count, group, flatten arrays, and build new structures; 2. Find and findIndex are used to find individual elements or indexes; 3.some and everything are used to determine whether conditions exist or all meet; 4.sort can be sorted but will change the original array; 5. Pay attention to copying the array when using it to avoid side effects. These methods make the code more concise and efficient.

See all articles