大数跨境

如何使用 CopilotKit 为 LangChain Deep Agents 构建前端!

如何使用 CopilotKit 为 LangChain Deep Agents 构建前端! 索引目录
2026-01-21
0
导读:关注「索引目录」公众号,获取更多干货。LangChain 最近推出了:一种构建结构化多智能体系统的新方法,该系统可以跨多个步骤进行规划、委派和推理。

关注「索引目录」公众号,获取更多干货。

LangChain 最近推出了:一种构建结构化多智能体系统的新方法,该系统可以跨多个步骤进行规划、委派和推理。

它内置了规划功能、上下文文件系统和子代理生成功能。但令人惊讶的是,将该代理连接到真正的前端仍然非常困难。

今天,我们将构建一个由 Deep Agents 驱动的求职助手,并将其连接到使用的实时 Next.js UI ,以便前端与代理实时保持同步。

您将找到架构、关键模式、UI ↔ 代理之间的状态流动方式,以及从头开始构建此功能的逐步指南。

我们来建造它。



1. 什么是深度代理?

如今大多数智能体只是“循环中的 LLM + 工具”。这种方法虽然可行,但往往不够深入:没有明确的计划,长期执行能力弱,而且随着运行时间的延长,状态会变得混乱。

、和这样的流行智能体通过遵循一种常见的模式来解决这个问题:它们首先进行规划,将工作环境外部化(通常通过文件或 shell),并将孤立的工作片段委派给子智能体。

Deep Agents 将这些基本元素打包成一个可重用的代理运行时。

与其从头开始设计自己的代理循环,不如调用create_deep_agent(...)并获取一个预先配置好的执行图,该执行图已经知道如何在多个步骤中进行规划、委派和管理状态。

图片来源:LangChain

实际上,通过该方法创建的深度代理create_deep_agent本质上就是一个 LangGraph 图。它没有单独的运行时或隐藏的编排层。

这意味着标准的 LangGraph 功能可以直接使用:

流媒体

检查点和中断

人机回路控制

心智模型(其运行方式)

从概念上讲,执行流程如下所示:

User goal

Deep Agent (LangGraph StateGraph)

├─ Plan: write_todos → updates "todos" in state

├─ Delegate: task(...) → runs a subagent with its own tool loop

├─ Context: ls/read_file/write_file/edit_file → persists working notes/artifacts

Final answer

这样就为“计划→执行工作→存储中间工件→继续”提供了一个可用的结构,而无需发明自己的计划格式、内存层或委托协议。

您可以访问阅读更多内容并查看。

CopilotKit 的适用范围

深度代理会将关键部分(例如文件和消息)推送到显式状态todos,这使得运行情况的检查更加容易。这种显式状态也使得与集成成为可能。

CopilotKit 是一个前端运行时,它通过实时流式传输代理事件和状态更新(底层使用

这个中间件(CopilotKitMiddleware)使得前端能够在运行过程中与代理保持同步。您可以在阅读相关文档。

agent = create_deep_agent(

model="openai:gpt-4o",

tools=[get_weather],

middleware=[CopilotKitMiddleware()],

for frontend tools and context


system_prompt="You are a helpful research assistant."

)

下图显示了 UI 中的用户操作如何通过 AG-UI 发送到任何代理后端,以及响应如何作为标准化事件流回。


2. 核心组件

以下是我们稍后将用到的核心组件:

1) 规划工具(通过深度代理内置) - 内置规划/待办行为,以便代理可以将工作流程分解为步骤,而无需您编写单独的规划工具。


Conceptual example (not required in codebase)


@tool

def todo_write(tasks: List[str]) -> str:

formatted = "\n".join([f"- {task}" for task in tasks])

return f"Todo list created:\n{formatted}"

2) 子代理——让主代理将特定任务委派到独立的执行循环中。每个子代理都有自己的提示、工具和上下文。

subagents = [

{

"name": "job-search-agent",

"description": "Finds relevant jobs and outputs structured job candidates.",

"system_prompt": JOB_SEARCH_PROMPT,

"tools": [internet_search],

}

]

3) 工具——这是代理实际执行操作的方式。这里,finalize()表示完成信号。

@tool

def finalize() -> dict:

"""Signal that the agent is done."""

return {"status": "done"}

深度代理的实现方式(中间件)

如果您想知道create_deep_agent()LangGraph 代理是如何实际注入规划、文件和子代理的,答案是中间件。

每个功能都以单独的中间件形式实现。默认情况下,附加了三个中间件:


- 添加write_todos工具和指令,推动代理在多步骤任务期间明确地规划和更新实时待办事项列表。


- 添加文件工具(ls,,,),以便代理可以将笔记和工件外部化,而不是将所有内容都塞进聊天记录中read_filewrite_fileedit_file


- 添加该task工具,允许主代理将工作委托给具有隔离上下文和自己提示/工具的子代理。

这使得 Deep Agents 无需引入新的运行时环境即可实现“预配置”的功能。如果您想深入了解,可以查看相关其中展示了具体的实现细节。

我们正在建造什么?

让我们创建一个代理,该代理可以:

接受简历(PDF格式),并提取技能和背景信息

使用深度代理来规划和协调子代理

使用工具(Tavily)在网络上搜索相关工作

通过 CopilotKit (AG-UI) 将工具结果流式传输回用户界面

在构建代理的过程中,我们将看到这些概念中的一些实际应用。


3. 前端:将代理连接到用户界面

我们先来构建前端部分。这就是我们的目录结构。

src目录托管 Next.js 前端,包括 UI、共享组件和/api/copilotkit用于代理通信的 CopilotKit API 路由()。

.

├── src/                               ← Next.js frontend

│   ├── app/

│   │   ├── page.tsx

│   │   ├── layout.tsx                 ← CopilotKit provider

│   │   └── api/

│   │       ├── upload-resume/route.ts ← upload endpoint

│   │       └── copilotkit/route.ts    ← CopilotKit AG-UI runtime

│   ├── components/

│   │   ├── ChatPanel.tsx              ← Chat + tool capture

│   │   ├── ResumeUpload.tsx           ← PDF upload UI

│   │   ├── JobsResults.tsx            ← Jobs table renderer

│   │   └── LivePreviewPanel.tsx

│   └── lib/

│       └── types.ts

├── package.json

├── next.config.ts

└── README.md

步骤 1:CopilotKit 提供程序和布局

安装必要的 CopilotKit 软件包。

npm install @copilotkit/react-core @copilotkit/react-ui @copilotkit/runtime

@copilotkit/react-core

提供核心 React hooks 和上下文,将您的 UI 连接到与 AG-UI 兼容的代理后端。

@copilotkit/react-ui

提供现成的 UI 组件,可快速构建 AI 聊天或助手界面。

@copilotkit/runtime

服务器端运行时,它公开 API 端点,并使用 HTTP 和 SSE 将前端与外部 AG-UI 兼容的代理后端连接起来。

该组件必须包裹应用程序中与 Copilot 相关的部分。在大多数情况下,最好将其放置在整个应用程序周围,例如在layout.tsx.

import type { Metadata } from "next";

import { CopilotKit } from "@copilotkit/react-core";

import "./globals.css";

import "@copilotkit/react-ui/styles.css";

export const metadata: Metadata = {

title: "Job Finder | Deep Agents with CopilotKit",

description: "A job search assistant powered by Deep Agents and CopilotKit",

};

export default function RootLayout({

children,

}: Readonly<{

children: React.ReactNode;

}>) {

return (

{children}

);

}

这里runtimeUrl="/api/copilotkit"指向 CopilotKit 用于与代理后端通信的 Next.js API 路由。

每个页面都包含在这个上下文中,以便 UI 组件知道要调用哪个代理以及将请求发送到哪里。

步骤 2:Next.js API 路由:代理到 FastAPI

这个 Next.js API 路由充当浏览器和深度代理之间的轻量级代理。它:

接受来自用户界面的 CopilotKit 请求。

通过 AG-UI 将它们转发给代理。

将代理状态和事件流式传输回前端

所有请求都通过一个单一的端点,而不是让前端直接与 FastAPI 代理通信/api/copilotkit

import {

CopilotRuntime,

ExperimentalEmptyAdapter,

copilotRuntimeNextJSAppRouterEndpoint,

} from "@copilotkit/runtime";

import { LangGraphHttpAgent } from "@copilotkit/runtime/langgraph";

import { NextRequest } from "next/server";

const serviceAdapter = new ExperimentalEmptyAdapter();

const runtime = new CopilotRuntime({

agents: {

job_application_assistant: new LangGraphHttpAgent({

url: process.env.LANGGRAPH_DEPLOYMENT_URL || "http://localhost:8123",

}),

},

});

export const POST = async (req: NextRequest) => {

const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({

runtime,

serviceAdapter,

endpoint: "/api/copilotkit",

});

return handleRequest(req);

};

以下是对上述代码的简单解释:

上面的代码注册了job_application_assistant代理。

LangGraphHttpAgent

:定义了一个远程 LangGraph 代理端点。它指向运行在 FastAPI 上的 Deep Agents 后端。

ExperimentalEmptyAdapter

:当代理后端处理自己的 LLM 调用和编排时,使用简单的空操作适配器

copilotRuntimeNextJSAppRouterEndpoint

一个小型辅助函数,用于将 Copilot 运行时适配到 Next.js 应用路由 API 路由,并返回一个handleRequest函数。

步骤 3:恢复上传 API 端点

此 API 路由(src\app\api\upload-resume\route.ts)处理来自前端的简历上传请求,并将其转发到 FastAPI 后端。它:

接受来自浏览器的多部分文件上传

将文件代理到后端简历解析器

将提取的文本和技能返回给用户界面。

将简历解析放在后端可以让代理重用相同的逻辑,并保持前端轻量级。

import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {

try {

const formData = await req.formData();

const file = formData.get("file") as File;

if (!file) {

return NextResponse.json({ error: "No file provided" }, { status: 400 });

}

const backendFormData = new FormData();

backendFormData.append("file", file);

const backendUrl = process.env.BACKEND_URL || "http://localhost:8123";

const response = await fetch(`${backendUrl}/api/upload-resume`, {

method: "POST",

body: backendFormData,

});

if (!response.ok) {

throw new Error("Backend upload failed");

}

const data = await response.json();

return NextResponse.json(data);

} catch (error) {

return NextResponse.json(

{ error: error instanceof Error ? error.message : "Upload failed" },

{ status: 500 }

);

}

}

步骤 4:构建关键组件

由于整体代码量巨大,我只介绍每个组件背后的核心逻辑。您可以在代码仓库中找到所有src\components

这些组件使用(如 useCopilotReadable)将所有内容连接在一起。

✅ 简历上传组件

该客户端组件处理简历选择并将文件转发到后端进行解析。

它接受一个 PDF/TXT 文件,将其 POST 到/api/upload-resume父组件,并将提取的文本和技能提升回父组件。

"use client";

import { useRef, useState } from "react";

type ResumeUploadResponse = { success: boolean; text: string; skills: string[]; filename: string };

export function ResumeUpload({ onUploadSuccess }: { onUploadSuccess(d: ResumeUploadResponse): void }) {

const [selectedFile, setSelectedFile] = useState(null);

const [isLoading, setIsLoading] = useState(false);

const [error, setError] = useState(null);

const inputRef = useRef(null);

const onSelect = (e: React.ChangeEvent) => {

setError(null);

const f = e.target.files?.[0] ?? null;

if (f && !["application/pdf", "text/plain"].includes(f.type)) {

setSelectedFile(null);

setError("Please upload a PDF or TXT file");

e.target.value = ""; // allow re-selecting same file

return;

}

setSelectedFile(f);

};

const onSubmit = async (e: React.FormEvent) => {

e.preventDefault();

if (!selectedFile) return;

setIsLoading(true);

setError(null);

try {

const fd = new FormData();

fd.append("file", selectedFile);

const res = await fetch("/api/upload-resume", { method: "POST", body: fd });

if (!res.ok) throw new Error("Upload failed");

onUploadSuccess((await res.json()) as ResumeUploadResponse);

setSelectedFile(null);

if (inputRef.current) inputRef.current.value = "";

} catch (err) {

setError(err instanceof Error ? err.message : "Failed to upload resume");

} finally {

setIsLoading(false);

}

};

return (

{isLoading ? "Uploading..." : "Upload Resume"}

{error && {error}}

{/* ... UI/styling omitted ... */}

);

}

以下是简要说明:

接受用户输入的 PDF/TXT 文件

/api/upload-resume

使用以下方式发送文件FormData

从后端接收提取的文本和技能

通过某种方式提取数据onUploadSuccess,以便稍后将其注入代理程序。

中的完整代码。

✅ 聊天面板组件

这是连接用户、客服人员和工具输出的核心用户界面。聊天面板:

嵌入 CopilotChat 以处理对话输入和流式代理响应

用于useCopilotReadable将简历文本、检测到的技能和用户偏好持续同步到代理的上下文中

拦截工具调用(例如update_jobs_list),以更新本地 UI 状态,而无需将作业 JSON 输出到聊天窗口。

我们还使用该组件构建了对话式用户界面。

"use client";

import { useState, useRef } from "react";

import { useDefaultTool, useCopilotReadable } from "@copilotkit/react-core";

import { CopilotChat } from "@copilotkit/react-ui";

import { ResumeUpload } from "./ResumeUpload";

import { JobsResults } from "./JobsResults";

export function ChatPanel() {

// form + resume state (title, location, skills, resume text…)

const [jobs, setJobs] = useState([]);

const processedKeyRef = useRef(null); // dedupe repeated tool calls

// Capture tool output

useDefaultTool({

render: ({ name, status, args, result }) => {

if (name === "update_jobs_list" && status === "complete" && result?.jobs_list) {

const key = JSON.stringify({

len: result.jobs_list.length,

first: result.jobs_list[0]?.url,

});

if (processedKeyRef.current !== key) {

processedKeyRef.current = key;

// Avoid setState during render

queueMicrotask(() => {

setJobs(result.jobs_list);

});

}

}

// Render tool calls inline

return (

...

);

},

});

// Send UI state + resume data into agent context

useCopilotReadable({

description: "Job search preferences",

value: {

targetTitle,

targetLocation,

skillsHint,

resumeText,

detectedSkills,

},

});

return (

{/* Resume upload + extracted skills UI */}

{!resumeUploaded && }

{/* Job search inputs (title / location / skills) */}

{/* CopilotKit chat UI */}

{/* Structured output rendered outside chat */}

);

}

中的完整代码。

✅ 工作成果组成部分

这是一个纯粹的展示组件。它接收jobs数组(update_jobs_list完成后填充数据),并将其渲染成表格,从而保持聊天输出的简洁。

"use client";

import { JobPosting } from "@/lib/types";

export function JobsResults({ jobs }: { jobs: JobPosting[] }) {

if (!jobs.length) return null;

return (

Jobs

{/* Company | Title | Location | Link | Good match */}

{jobs.map((j, idx) => (

{j.company}

{j.title}

{j.location}


Open


{j.goodMatch || "Yes"}

))}

);

}

中的完整代码。

步骤 5:将聊天用户界面连接到代理

至此,所有组件均已就位。此页面仅用于渲染ChatPanel,它已通过 CopilotKit 与 Deep Agents 后端完全连接。

LivePreviewPanel旁边还安装了辅助面板。由于工具调用已经在面板内部内联渲染CopilotChat,因此该面板目前是可选的,它作为一个正在开发中的空间,用于实现更丰富的调试和可视化功能。

"use client";

import { ChatPanel } from "@/components/ChatPanel";

import { LivePreviewPanel } from "@/components/LivePreviewPanel";

export default function Page() {

return (

{/* App header (branding + description) */}

Job Application Assistant

Find personalized jobs with AI.

{/* ... badges / styling omitted ... */}

{/* Tool calls are already rendered inside CopilotChat */}

{/* This panel is optional and currently used for experimentation */}

{/* Footer */}

{/* ... footer content omitted ... */}

);

}


4. 后端:构建代理服务(FastAPI + Deep Agents + AG-UI)

接下来我们将构建托管我们深度代理的 FastAPI 后端。

/agent目录下运行着一个 FastAPI 服务器,用于运行作业应用程序代理。以下是后端项目的结构。

.

├── agent/                             ← Deep Agents backend

│   ├── main.py                        ← FastAPI + AG-UI endpoint

│   ├── agent.py                       ← Deep Agents graph & tools

│   ├── pyproject.toml                 ← Python deps (uv)

│   └── uv.lock

...

从宏观层面来看,后端负责:

公开一个与 CopilotKit 兼容的代理端点(用于流式传输代理状态和工具调用)

提供/api/upload-resume用于解析简历的接口

构建一个深度代理图,该代理图可以进行规划、将任务委派给子代理,并在网络上搜索匹配的工作。

后端使用进行依赖管理。如果您的系统中没有安装 uv,请安装它。

pip install uv

使用以下命令初始化一个新的 UV 项目。这将生成一个新的 UV 项目pyproject.toml

cd agent

uv init

该后端使用的大多数 AI 工具(尤其是 AG-UI Strands)目前需要 Python 3.12 或更高版本,因此请确保使用以下命令告知 uv 使用兼容的 Python 版本:

uv python pin 3.12

然后安装依赖项。这也会创建项目的虚拟环境。

uv add copilotkit deepagents fastapi langchain langchain-openai pypdf python-dotenv python-multipart tavily-python "uvicorn[standard]"

copilotkit

:通过流式传输、工具和共享状态将代理连接到前端。

deepagents

:用于多步骤执行的规划优先代理框架。

fastapi

:一个公开代理 API 的 Web 框架。

langchain

代理和工具编排层。

langchain-openai

:将 OpenAI 模型集成到 LangChain 中。

pypdf

:从 PDF 文件中提取文本。

python-dotenv

:从以下位置加载环境变量.env

python-multipart

:启用 FastAPI 中的文件上传功能。

tavily-python

:网络搜索实时经纪人研究。

uvicorn[standard]

:用于运行 FastAPI 的 ASGI 服务器。

现在运行以下命令,生成一个uv.lock包含确切版本的文件。

uv sync

添加必要的 API 密钥

.env在这两个目录下分别创建一个文件agent,并将你的和添加到该文件中。我已经附上了文档链接,方便你参考。

OPENAI_API_KEY=sk-proj-...

TAVILY_API_KEY=tvly-dev-...

OPENAI_MODEL=gpt-4-turbo

OpenAI API密钥

Tavily API 密钥

步骤 1:定义智能体的行为

我们首先使用一个严格的系统提示来定义代理的行为agent.py

在深度代理中,系统提示充当工作流的控制层,结合规划和委派,将复杂的任务分解为有序的步骤。

通过强制执行固定的执行顺序来协调MAIN_SYSTEM_PROMPT工具和子代理。此提示确保:

外部行动总是通过工具发生的。

UI状态以受控方式更新。

执行以确定性的方式结束finalize()

MAIN_SYSTEM_PROMPT = """

You are a tool-using agent.

Hard rules:

- Never include job details, URLs, or JSON in assistant messages.

- Only output jobs via update_jobs_list(jobs_json).

- A valid job must be a single job detail page on an ATS or company careers page.

- Do NOT use job boards or listing/search pages.

- company MUST be the hiring company (never Lever/Greenhouse/Ashby/Workday/Talent.com/etc).

Schema (exact keys):

- company, title, location, url, goodMatch

Steps:

1) Call internet_search(query) exactly once.

2) From the returned results, select up to 5 valid individual job postings.

3) Call update_jobs_list(jobs_json) once.

4) Call finalize().

5) Output: Found N jobs.

If you cannot find 5 valid jobs, return as many valid ones as possible.

"""

JOB_SEARCH_PROMPT定义了特定子代理的行为。其职责仅限于寻找相关工作并以受控格式返回结构化结果。

JOB_SEARCH_PROMPT = (

"Search and select 5 real postings that match the user's title, locations, and skills. "

"Output ONLY this block format (no extra text before/after the wrapper):\n"

"\n"

'[{"company":"...","title":"...","location":"...","link":"https://...","Good Match":"one sentence"},'

' {"company":"...","title":"...","location":"...","link":"https://...","Good Match":"one sentence"},'

' {"company":"...","title":"...","location":"...","link":"https://...","Good Match":"one sentence"},'

' {"company":"...","title":"...","location":"...","link":"https://...","Good Match":"one sentence"},'

' {"company":"...","title":"...","location":"...","link":"https://...","Good Match":"one sentence"}]'

"\n"

"Each job MUST:"

"- Be a single opening (not a job board, filter page or company jobs index)"

"- Belong to a specific company with a dedicated job description page"

"You must:"

"- Use internet_search to find relevant jobs."

"- Do NOT output job listings, JSON, or URLs in messages."

"- Return everything ONLY by calling the parent tool `update_jobs_list` with a JSON string."

)

步骤 2:添加简历解析和技能提取工具

我们使用以下函数从上传的 PDF 文件中提取原始文本pypdf。FastAPI 上传端点使用此函数将简历转换为纯文本。

def parse_pdf_resume(file_path: str) -> str:

with open(file_path, "rb") as file:

reader = PdfReader(file)

return "".join(page.extract_text() for page in reader.pages)

接下来,我们从简历中提取轻量级的结构化信号(语言、框架、工具)。这会影响求职查询和匹配质量

def extract_skills_from_resume(resume_text: str) -> List[str]:

skills_db = {

"languages": ["Python", "JavaScript", "Go"],

"frameworks": ["React", "FastAPI", "Django"],

"cloud": ["AWS", "Docker", "Kubernetes"],

}

found = set()

text = resume_text.lower()

for skills in skills_db.values():

for skill in skills:

if skill.lower() in text:

found.add(skill)

return list(found)

步骤 3:定义搜索、UI 更新和终止工具

工具是代理与外部世界/用户界面之间的集成界面。

internet_search工具负责发现真实的招聘信息。它会特意获取额外的搜索结果,过滤掉任何包含“不良”子字符串的URL(招聘网站/搜索页面),BAD_URL_SUBSTRINGS并只返回第一个max_results有效的搜索结果。

BAD_URL_SUBSTRINGS = [

"linkedin.com/jobs/search",

"linkedin.com/jobs/",

"builtin.com/jobs",

"naukri.com",

"glassdoor.",

"/jobs/search",

"/search?",

]

def _is_bad(url: str) -> bool:

u = (url or "").lower()

return any(p in u for p in BAD_URL_SUBSTRINGS)

@tool

def internet_search(query: str, max_results: int = 10) -> List[Dict[str, Any]]:

"""

Search for jobs using Tavily API. Always returns up to 5 results.

"""

tavily_key = os.environ.get("TAVILY_API_KEY")

if not tavily_key:

raise RuntimeError("TAVILY_API_KEY not set")

client = TavilyClient(api_key=tavily_key)

res = client.search(

query=query,

max_results=max_results * 3,

get more, then filter


include_raw_content=False,

topic="general",

)

trimmed = []

for r in res.get("results", []):

url = r.get("url") or ""

if _is_bad(url):

continue

trimmed.append(

{

"title": r.get("title"),

"url": url,

"content": (r.get("content") or "")[:400],

}

)

if len(trimmed) == max_results:

break

print(f"[SEARCH] Returning {len(trimmed)} filtered results")

print(trimmed)

return trimmed

update_jobs_list工具是结构化工作数据到达前端的唯一途径,它能确保 UI 更新清晰明确,并将 JSON 数据排除在聊天消息之外。

@tool

def update_jobs_list(jobs_json: str) -> Dict[str, Any]:

"""Send jobs list to UI state."""

jobs = json.loads(jobs_json)

print(f"[TOOL] update_jobs_list: {len(jobs)} jobs")

return {"jobs_list": jobs}

finalize该工具发出信号,表明代理已完成其工作流程。

@tool

def finalize() -> dict:

"""Signal completion."""

print("[TOOL] finalize: Job search complete")

return {"status": "done"}

步骤 4:构建包含子代理的深度代理图

现在我们将所有内容连接起来,并构建深度代理图build_agent()

def build_agent():

"""Build Deep Agents graph with proper recursion limit"""

api_key = os.environ.get("OPENAI_API_KEY")

if not api_key:

raise RuntimeError("Missing OPENAI_API_KEY")

llm = ChatOpenAI(

model=os.environ.get("OPENAI_MODEL", "gpt-4-turbo"),

temperature=0.7,

api_key=api_key,

)

tools = [

internet_search,

update_jobs_list,

finalize,

]

subagents = [

{

"name": "job-search-agent",

"description": "Finds relevant jobs and outputs JSON.",

"system_prompt": JOB_SEARCH_PROMPT,

"tools": [internet_search],

},

]

agent_graph = create_deep_agent(

model=llm,

system_prompt=MAIN_SYSTEM_PROMPT,

tools=tools,

subagents=subagents,

middleware=[CopilotKitMiddleware()],

checkpointer=MemorySaver(),

)

print("[AGENT] Deep Agents graph created")

print(agent_graph)

return agent_graph

步骤 5:FastAPI 设置

最后一步是初始化后端并将其作为 FastAPI 应用公开。它还负责处理简历上传和 PDF 解析,在将原始文件发送给招聘人员之前,将其转换为清晰的文本和技能信息。

import os

from fastapi import FastAPI, HTTPException, File, UploadFile

import uvicorn

from dotenv import load_dotenv

from ag_ui_langgraph import add_langgraph_fastapi_endpoint

from copilotkit import LangGraphAGUIAgent

from agent import build_agent, parse_pdf_resume, extract_skills_from_resume

import tempfile

load_dotenv()

app = FastAPI(

title="Job Application Assistant",

description="Find personalized job openings based on skills and preferences",

version="1.0.0",

)

try:

agent_graph = build_agent()

print(agent_graph)

add_langgraph_fastapi_endpoint(

app=app,

agent=LangGraphAGUIAgent(

name="job_application_assistant",

description="Job finder",

graph=agent_graph,

),

path="/",

)

print("[MAIN] Agent registered")

except Exception as e:

print(f"[ERROR] Failed to build agent: {str(e)}")

raise

@app.get("/healthz")

async def health_check():

"""Health check"""

return {

"status": "healthy",

"service": "job-application-assistant",

"version": "1.0.0",

}

@app.post("/api/upload-resume")

async def upload_resume(file: UploadFile = File(...)):

"""

Upload and parse resume (PDF, DOCX, TXT).

Returns extracted text and skills.

"""

if not file:

raise HTTPException(status_code=400, detail="No file provided")

try:

with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:

content = await file.read()

tmp.write(content)

tmp_path = tmp.name

if file.filename.endswith(".pdf"):

resume_text = parse_pdf_resume(tmp_path)

else:


for other formats, just read as text


resume_text = content.decode("utf-8", errors="ignore")

skills = extract_skills_from_resume(resume_text)

os.unlink(tmp_path)

return {

"success": True,

"text": resume_text[:1000],

"skills": skills,

"filename": file.filename,

}

except Exception as e:

print(f"[ERROR] Resume upload failed: {str(e)}")

raise HTTPException(status_code=500, detail=str(e))

def main():

"""Run server"""

host = os.getenv("SERVER_HOST", "0.0.0.0")

port = int(os.getenv("SERVER_PORT", 8123))

uvicorn.run(

"main:app",

host=host,

port=port,

reload=True,

log_level="info",

)

if __name__ == "__main__":

main()


5. 运行应用程序

代码所有部分完成后,就可以在本地运行了。请确保已将凭据添加到agent/.env.

从项目根目录导航到该agent目录并启动 FastAPI 服务器:

cd agent

uv run python main.py

后端将于http://localhost:8123.

在新终端窗口中,使用以下命令启动前端开发服务器:

npm run dev

两个服务器都运行起来后,在浏览器中打开即可在本地查看前端。

然后您上传简历并搜索职位。

根据作业查询的不同,它可以返回不同数量的结果。以下是另一个输出结果!

CopilotKit 还提供了,这是一个实时 AG-UI 运行时视图,可让您检查从后端实时传输的代理运行、状态快照、消息和工具调用。您可以通过叠加在应用程序上的 CopilotKit 按钮访问它。


6. 数据流

现在我们已经构建好了前端和代理服务,接下来我们将展示它们之间的数据实际流动方式。如果您一直跟着我们一起构建,应该很容易理解。

[User uploads resume & submits job query]

Next.js UI (ResumeUpload + CopilotChat)

useCopilotReadable syncs resume + preferences

POST /api/copilotkit (AG-UI protocol)

FastAPI + Deep Agents (/copilotkit endpoint)

Resume context + skills injected into the agent

Deep Agents orchestration

├─ internet_search (Tavily)

├─ job filtering & normalization

└─ update_jobs_list (tool call)

AG-UI streaming (SSE)

CopilotKit runtime receives the tool result

Frontend captures the tool output

Jobs rendered in table + chat stay clean


就是这样!🎉

您现在拥有一个由 Deep Agents 提供支持的求职申请助手,其前端层为 CopilotKit。

关注「索引目录」公众号,获取更多干货。

【声明】内容源于网络
0
0
索引目录
索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
内容 444
粉丝 0
索引目录 索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
总阅读12
粉丝0
内容444