从零搭建AI关系图生成助手:Chainlit 结合LangChain、LangGraph和可视化技术

前言

在AI快速发展的今天,如何将文本中的技术信息自动整理成可视化关系图,是很多开发者和数据分析师面临的需求。本文将分享一个完整项目案例------AI关系图生成助手 ,它基于 LangChainLangGraph 和 Chainlit 构建,能够自动抽取实体关系三元组并生成 GraphvizMermaid 可视化图表。

本文将从项目架构、核心代码、提示词设计到部署指南,全面讲解如何从零搭建这样一个智能助手。



项目概述

AI关系图生成助手主要功能:

  • 自动从文本中提取技术相关实体关系(三元组)
  • 支持 Graphviz 和 Mermaid 两种可视化方式
  • 可通过 Chainlit 提供 Web 界面进行交互
  • 支持中文显示,适合技术文档和系统架构可视化

项目结构如下:

复制代码
project/
├── agent_graph.py          # AI代理核心逻辑和图结构定义
├── app.py                  # Chainlit应用入口,处理用户交互
├── chainlit.md             # 用户使用指南和提示词示例
├── graphviz_render.py      # 图形渲染工具函数(Graphviz和Mermaid)
├── mermaid_renderer.py     # Mermaid图表HTML渲染器
└── requirements.txt        # 项目依赖

环境依赖

Python依赖

txt 复制代码
chainlit
langgraph
langchain
langchain-ollama
pydantic
typing_extensions
graphviz

系统依赖


核心实现

1️⃣ AI代理逻辑(agent_graph.py)

  • 定义 RelationshipState 数据结构
  • 使用 LangChain + Ollama LLM 处理文本
  • 通过 LangGraph 构建状态流

核心流程:

  1. 接收用户消息
  2. 调用 LLM 抽取技术实体关系
  3. 生成 Graphviz 或 Mermaid 可视化
  4. 返回 Web 页面或图片给用户

主要代码片段:

python 复制代码
# agent_graph.py
from typing import Annotated, List
from typing_extensions import TypedDict
from pathlib import Path

from pydantic import BaseModel, Field

from langchain_core.messages import (
    HumanMessage,
    AIMessage,
    AnyMessage,
)
from langchain_core.tools import tool
from langchain_ollama import ChatOllama

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition

from graphviz_render import relationships_to_png, relationships_to_mermaid

# -----------------------
# 1️⃣ 数据结构
# -----------------------
class Relationship(BaseModel):
    subject: str = Field(...)
    predicate: str = Field(...)
    object: str = Field(...)

class State(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]
    relationships: Annotated[List[Relationship], lambda a, b: a + b]

# -----------------------
# 2️⃣ 工具
# -----------------------
@tool
def multiply(a: int, b: int) -> int:
    """将两个整数相乘"""
    return a * b

tools = [multiply]
tool_node = ToolNode(tools)

# -----------------------
# 3️⃣ LLM
# -----------------------
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
model_with_tools = llm.bind_tools(tools)

class Extraction(BaseModel):
    relationships: List[Relationship]

extract_llm = llm.with_structured_output(Extraction)

# -----------------------
# 4️⃣ 节点定义
# -----------------------
def agent(state: State):
    response = model_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def extract_relationships(state: State):
    # ✅ 抽 Human + AI(避免漏掉解释性内容)
    text = "\n".join(
        m.content
        for m in state["messages"]
        if isinstance(m, (HumanMessage, AIMessage))
        and isinstance(m.content, str)
    )

    if not text.strip():
        return {"relationships": []}

    prompt = f"""
请从以下文本中抽取技术相关的实体关系三元组(subject, predicate, object)。
要求:
1. 只抽取技术相关内容,例如编程语言、框架、工具、算法、系统、方法等。
2. 忽略与技术无关的实体或句子(如寒暄、提问、假设)。
3. 输出格式为 JSON 数组,每个元素包含 "subject", "predicate", "object"。
4. 对同一实体的多重关系分别列出。

文本:
{text}

输出示例:
[
  {{"subject": "Python", "predicate": "支持", "object": "面向对象编程"}},
  {{"subject": "LangChain", "predicate": "使用", "object": "LLM 模型"}},
  {{"subject": "Docker", "predicate": "部署", "object": "Web 应用"}}
]
"""

    result = extract_llm.invoke(prompt)
    return {"relationships": result.relationships}

STATIC_DIR = Path(__file__).parent / "static"

def visualize(state: State):
    relationships = state.get("relationships", [])
    if not relationships:
        return {"messages": [AIMessage(content="暂无可视化关系")]}

    png_path = STATIC_DIR / "relationships.png"
    relationships_to_png(relationships, png_path)

    # 返回一个特殊标记,让app.py知道需要显示图片
    content = f"GRAPHVIZ_IMAGE:{png_path}:当前提取的关系图(共 {len(relationships)} 条关系)"

    # ✅ 关键:只返回 AIMessage,避免重复
    return {"messages": [AIMessage(content=content)]}

def visualize_mermaid(state: State):
    relationships = state.get("relationships", [])
    if not relationships:
        return {"messages": [AIMessage(content="暂无可视化关系")]}    

    mermaid_code = relationships_to_mermaid(relationships)
    
    content = (
        "**当前提取的关系图(Mermaid格式):**\n\n"
        f"```mermaid\n{mermaid_code}\n```\n\n"
        f"(共 {len(relationships)} 条关系)"
    )
    
    return {"messages": [AIMessage(content=content)]}

# -----------------------
# 5️⃣ 构建 LangGraph
# -----------------------
builder = StateGraph(State)

builder.add_node("agent", agent)
builder.add_node("tools", tool_node)
builder.add_node("extract", extract_relationships)
builder.add_node("visualize", visualize)
builder.add_node("visualize_mermaid", visualize_mermaid)

builder.add_edge(START, "agent")
builder.add_conditional_edges(
    "agent",
    tools_condition,
    {"tools": "tools", END: "extract"},
)
builder.add_edge("tools", "agent")
builder.add_edge("extract", "visualize")
builder.add_edge("visualize", "visualize_mermaid")
builder.add_edge("visualize_mermaid", END)

graph = builder.compile()

2️⃣ Chainlit应用入口(app.py

python 复制代码
# app.py
import chainlit as cl
from pathlib import Path
import mimetypes

from langchain_core.messages import (
    HumanMessage,
    AIMessage,
    ToolMessage,
)

from agent_graph import graph

STATIC_DIR = Path(__file__).parent / "static"
STATIC_DIR.mkdir(exist_ok=True)

@cl.on_chat_start
async def start_chat():
    await cl.Message(
        content="✅ Agent 已启动(Graphviz 关系图版)"
    ).send()

    # Agent 结构图
    try:
        png = graph.get_graph().draw_png()
        path = STATIC_DIR / "agent_structure.png"
        path.write_bytes(png)

        # 将图片作为元素添加
        elements = [
            cl.Image(name="agent_structure", display="inline", path=str(path))
        ]
        await cl.Message(
            content="**Agent 结构图:**",
            elements=elements
        ).send()
    except Exception as e:
        await cl.Message(content=f"结构图生成失败:{e}").send()

@cl.on_message
async def handle_message(message: cl.Message):
    inputs = {"messages": [HumanMessage(content=message.content)]}
    sent = set()  # ✅ 防止重复发送

    async for step in graph.astream(inputs, stream_mode="values"):
        msgs = step.get("messages", [])
        if not msgs:
            continue

        last = msgs[-1]

        if isinstance(last, AIMessage) and last.content:
            if last.content not in sent:
                sent.add(last.content)
                content = str(last.content)
                
                # 检查是否是需要显示图片的特殊标记
                if content.startswith("GRAPHVIZ_IMAGE:"):
                    parts = content.split(":", 2)
                    if len(parts) >= 3:
                        img_path = parts[1]
                        caption = parts[2]
                        elements = [
                            cl.Image(name="relationships", display="inline", path=img_path)
                        ]
                        await cl.Message(
                            content=f"**{caption}**",
                            elements=elements,
                            author="Assistant"
                        ).send()
                else:
                    # 普通内容直接显示
                    await cl.Message(
                        content=content,
                        author="Assistant"
                    ).send()

        elif isinstance(last, ToolMessage) and last.content:
            await cl.Message(
                content=last.content,
                author="Tool"
            ).send()

通过 Chainlit,用户可直接在 Web 界面与 AI 代理交互,实时查看关系图。


3️⃣ Graphviz与Mermaid渲染工具

python 复制代码
def relationships_to_mermaid(relationships: List):
    mermaid_lines = ["graph TD"]
    for r in relationships:
        mermaid_lines.append(f"{r.subject}[{r.subject}] -->|{r.predicate}| {r.object}[{r.object}]")
    return "\n".join(mermaid_lines)

def relationships_to_png(relationships: List, output_path: Path):
    from graphviz import Digraph
    dot = Digraph(format="png", graph_attr={"fontname": "SimHei"})
    for r in relationships:
        dot.node(r.subject, r.subject)
        dot.node(r.object, r.object)
        dot.edge(r.subject, r.object, label=r.predicate)
    dot.render(output_path.with_suffix(""), cleanup=True)

完整代码开源

本项目的完整源代码已托管在 Gitee 上,方便大家下载、学习和二次开发:

🔗 https://gitee.com/michah/langgraph-relation-visualize

提示词设计

为了让 LLM 提取出干净、准确的技术关系,需要设计高质量提示词:

  • "抽取下面文本中的技术实体及调用关系,输出JSON三元组,只保留技术相关"
  • "分析系统架构中的服务、模块和接口关系,生成Graphviz可视化图"
  • "提取微服务调用链,标注服务A → 服务B → 数据库的关系,忽略业务流程"

使用示例

技术关系

输入:

复制代码
Python连接数据库,数据库存储用户信息,API处理请求

生成 Graphviz 或 Mermaid 图:

  • 节点:Python、数据库、API
  • 边:连接、存储、处理

业务流程关系

输入:

复制代码
用户注册账户,系统验证邮箱,发送欢迎邮件

系统可生成技术实现对应关系图。


部署指南

  1. 安装 Python 依赖:
bash 复制代码
pip install -r requirements.txt
  1. 安装 Graphviz:
bash 复制代码
sudo apt-get install graphviz
  1. 启动应用:
bash 复制代码
chainlit run app.py

访问 http://localhost:8000 即可使用。


总结

通过本项目,我们实现了一个完整的 AI关系图生成助手

  • LangChain 负责与 LLM 交互
  • LangGraph 管理状态流和工具调用
  • Chainlit 提供 Web 界面
  • Graphviz / Mermaid 提供多样化可视化

该项目可应用于技术文档分析、系统架构可视化、知识图谱构建等场景,是 技术探索者 值得收藏和二次开发的工具。


💡 建议

  • 文本中明确写出系统、模块、接口、工具、算法等技术实体,提高提取准确率
  • 可扩展为自动化流程分析、微服务调用链追踪、前端可视化展示
相关推荐
羑悻的小杀马特2 小时前
不做“孤岛”做“中枢”:拆解金仓时序库,看国产基础软件如何玩转“多模融合”
数据库·人工智能
桂花饼2 小时前
Python 实战 Sora-2 视频生成:基于小镜 AI 的低成本与角色一致性解决方案
人工智能·sora2·gemini 3·gpt-5.2·codex-max
算法狗22 小时前
大模型中哪些模型用到的pre-norm和post-norm技术的?
人工智能·深度学习·机器学习·语言模型·面试题
一只大侠的侠2 小时前
拖拽式AI应用工厂:ModelEngine应用编排深度体验,智能表单与插件开发实战
人工智能
说私域2 小时前
基于AI智能名片链动2+1模式S2B2C商城小程序的流量运营策略研究
人工智能·微信·小程序·产品运营·流量运营
山后太阳2 小时前
如何评估TensorRT加速效果?
人工智能
2501_941333102 小时前
YOLO11-BiFPN实现:小麦杂质检测与分类系统详解_1
人工智能·分类·数据挖掘
Mixtral2 小时前
2026年面试记录转写工具深度测评:3款工具准确率与效率对比
人工智能·面试·职场和发展·语音识别·语音转文字
STLearner2 小时前
AAAI 2026 | 时间序列(Time Series) 论文总结[下] (分类,异常检测,基础模型,表示学习,生成)
大数据·论文阅读·人工智能·python·深度学习·机器学习·数据挖掘