news 2026/5/10 1:04:41

Chainlit:快速构建AI应用界面的Python框架,无缝集成LangChain与OpenAI

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Chainlit:快速构建AI应用界面的Python框架,无缝集成LangChain与OpenAI

1. 从零到一:为什么我们需要 Chainlit?

如果你和我一样,在过去一两年里折腾过基于大语言模型(LLM)的应用开发,那你一定对下面这个场景不陌生:你花了好几天,甚至几周时间,用 LangChain 或者 LlamaIndex 搭好了一个复杂的 AI 代理(Agent)流程,它集成了检索增强生成(RAG)、工具调用(Tool Calling)和复杂的逻辑判断。然后,你兴奋地打开终端,准备测试这个“智能大脑”。结果呢?你面对的是一行行冰冷的print输出,或者一个简陋的命令行交互界面。你想给同事或老板演示一下?要么得让他们对着黑乎乎的终端敲命令,要么你就得再花上几个星期,吭哧吭哧地从前端到后端,用 Flask、FastAPI 加上 React 或 Vue,去搭建一个像样的 Web 界面。

这个“最后一公里”的问题,消耗的精力往往不亚于核心 AI 逻辑的开发本身。我们需要处理 WebSocket 连接来支持流式响应,要设计消息气泡的 UI 组件,要管理对话历史的状态,还要把 LangChain 里那些AgentExecutorRunnable对象产生的中间步骤(比如工具调用、思考过程)清晰地展示出来。这还没算上部署和运维的复杂度。

Chainlit 就是为了解决这个痛点而生的。它不是一个 AI 框架,而是一个专门为 AI 应用打造的 UI 开发框架。你可以把它理解成 AI 应用界的 “Streamlit”。它的核心价值在于:让你能用最少的、纯 Python 的代码,快速构建出生产就绪的、交互式的对话式 AI 应用界面。它帮你封装了所有繁琐的 UI 和通信层工作,让你能专注于最核心的 AI 逻辑本身。官方那句“Build in minutes, not weeks”的 slogan,我实测下来,对于原型验证和内部工具开发,确实不算夸张。

2. 核心设计哲学与架构解析

在深入代码之前,理解 Chainlit 的设计思路至关重要。这能帮助你在后续开发中做出更合理的架构选择。

2.1 事件驱动与装饰器模式

Chainlit 采用了非常 Pythonic 的装饰器(Decorator)事件驱动模型。你不需要继承某个复杂的基类,或者实现一整套接口。你只需要在你关心的“事件”处理函数上,加上一个简单的装饰器,Chainlit 就会在相应事件发生时自动调用它。

最核心的两个装饰器是:

  • @cl.on_message: 处理用户发送的每一条新消息。
  • @cl.on_chat_start: 在聊天会话开始时触发,常用于初始化会话状态。

这种设计让代码极其清晰和模块化。你的业务逻辑就是一个个独立的异步函数,Chainlit 负责在后台帮你打理好 HTTP 服务器、WebSocket 连接、前端渲染和会话管理。

2.2 会话(Session)与上下文(Context)管理

每个连接到你的 Chainlit 应用的用户,都会拥有一个独立的会话。Chainlit 自动管理会话的生命周期,并提供了一个强大的cl.user_session字典来存储会话级别的状态。这是你存放“对话记忆”、用户特定配置或数据库连接池的关键位置。

例如,你可以在@cl.on_chat_start中初始化一个 LangChain 的 Agent,并将其存入cl.user_session,然后在后续的@cl.on_message中直接取用,避免每次请求都重新初始化,这对性能至关重要。

import chainlit as cl from langchain.agents import create_react_agent, AgentExecutor from langchain_openai import ChatOpenAI @cl.on_chat_start async def start(): # 初始化LLM和工具 llm = ChatOpenAI(model="gpt-4", temperature=0, streaming=True) tools = [...] # 你的工具列表 agent = create_react_agent(llm, tools) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # 将执行器存入用户会话,供后续使用 cl.user_session.set("agent_executor", agent_executor) await cl.Message(content="你好!我是你的AI助手,已就绪。").send()

2.3 消息(Message)与步骤(Step)系统

这是 Chainlit 交互能力的核心抽象。

  • 消息(cl.Message:代表对话中显示的一条完整内容。可以是用户发的,也可以是 AI 发的。它支持流式输出(stream方法),这对于展示 LLM 逐字生成的效果体验极佳。
  • 步骤(cl.Step:这是 Chainlit 比普通聊天界面更强大的地方。一个“步骤”代表 AI 思考或执行过程中的一个子任务或中间环节。比如,调用一次搜索引擎、执行一段代码、查询一次数据库。

你可以通过@cl.step装饰器将一个函数标记为步骤。当这个函数被执行时,Chainlit 前端会自动渲染出一个可折叠/展开的区块,清晰地展示这个步骤的输入、输出和耗时。这对于调试复杂的 Agent 工作流、向用户解释 AI 的“思考过程”具有无可替代的价值。

import chainlit as cl import requests @cl.step(type="tool", name="查询天气") async def get_weather(city: str): # 模拟一个工具调用 await cl.sleep(1) # 模拟网络延迟 # 假设这里调用真实API # response = requests.get(f"https://api.weather.com/{city}") # return response.json() return f"{city}的天气是晴,25摄氏度。" @cl.on_message async def main(message: cl.Message): # 用户消息 user_input = message.content # 创建一个父级消息,作为本次响应的容器 msg = cl.Message(content="") await msg.send() # 流式输出一些思考过程 async with cl.Step(name="分析用户意图", type="llm") as step: step.output = "用户想查询天气信息。" await msg.stream_token(f"我来帮你查一下天气...\n") # 调用工具步骤 weather_info = await get_weather("北京") await msg.stream_token(f"查询结果:{weather_info}\n") # 最终总结 await msg.stream_token("以上就是天气信息。") # 更新最终消息内容(可选,如果流式内容已完整)

在前端,用户会先看到一条“我来帮你查一下天气...”的消息,然后看到一个“查询天气”的步骤块,点开可以看到详情,最后看到完整的总结。整个 AI 的工作流程一目了然。

3. 深度集成:与主流 AI 框架携手共舞

Chainlit 的威力在于它“不造轮子”,而是完美地接入现有的 AI 生态。下面我们看看如何与几个核心框架深度集成。

3.1 与 LangChain/LangGraph 的深度融合

LangChain 是当前构建 LLM 应用的事实标准框架。Chainlit 对其有原生级的支持。

关键技巧:利用LangchainCallbackHandlerLangChain 提供了回调系统(Callback),Chainlit 实现了自己的LangchainCallbackHandler。将这个回调处理器传入你的 LangChain 对象,Chainlit 就能自动捕获链(Chain)或代理(Agent)执行过程中产生的所有中间步骤(LLM 调用、工具调用等),并将其自动渲染为前端的Step。这几乎实现了“零额外代码”的可观测性。

import chainlit as cl from chainlit import LangchainCallbackHandler from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser @cl.on_message async def main(message: cl.Message): # 1. 创建Chainlit的回调处理器 cb = LangchainCallbackHandler() # 2. 构建一个简单的LangChain链 prompt = ChatPromptTemplate.from_template("请用中文回答:{question}") llm = ChatOpenAI(model="gpt-3.5-turbo", streaming=True) chain = prompt | llm | StrOutputParser() # 3. 创建一条Chainlit消息用于流式输出 msg = cl.Message(content="") # 4. 调用链,并传入回调处理器 # 注意:使用 chain.astream_events 或 chain.ainvoke 并传入 callbacks async for chunk in chain.astream_events( {"question": message.content}, version="v2", config={"callbacks": [cb]} ): if chunk["event"] == "on_chat_model_stream": token = chunk["data"]["chunk"].content if token: await msg.stream_token(token) await msg.send()

当你运行这段代码时,前端不仅会流式显示最终答案,还会在侧边栏或步骤列表中自动生成 LangChain 链执行的各种事件节点,如“ChatPromptTemplate”、“ChatOpenAI”等,极大方便了调试。

对于 LangGraph(LangChain 的状态机框架),集成方式类似。你可以将LangchainCallbackHandler附加到你的 Graph 执行中,Chainlit 会自动可视化整个图的执行路径和每个节点的状态变化,这对于理解复杂、有分支的 Agent 工作流简直是神器。

3.2 原生支持 OpenAI SDK 流式响应

如果你直接使用 OpenAI 的 Python SDK,Chainlit 的流式消息与之配合得天衣无缝。

import chainlit as cl from openai import AsyncOpenAI client = AsyncOpenAI() @cl.on_message async def main(message: cl.Message): msg = cl.Message(content="") await msg.send() # 直接调用OpenAI API,并流式处理响应 stream = await client.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": message.content}], stream=True, ) async for chunk in stream: if chunk.choices[0].delta.content is not None: token = chunk.choices[0].delta.content await msg.stream_token(token)

这种方式简单直接,适合快速原型或不需要复杂编排的场景。

3.3 处理自定义工具与复杂输出

当你的 Agent 调用自定义工具时,你希望工具的输入输出能漂亮地展示出来。@cl.step装饰器在这里大放异彩。

import chainlit as cl import json @cl.step(type="tool", name="数据库查询") async def query_database(sql: str): # 模拟数据库查询 await cl.sleep(0.5) # 你可以在这里美化你的输出,比如语法高亮 result = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}] # 将结果以格式化的JSON形式显示在步骤中 step = cl.context.current_step step.elements = [ # 使用elements属性嵌入更丰富的UI组件 cl.Text(name="SQL", content=sql, language="sql", display="side"), cl.Text(name="Result", content=json.dumps(result, indent=2), language="json", display="page"), ] return result @cl.on_message async def handle_message(message: cl.Message): # 假设消息内容是“查询所有用户” if "查询用户" in message.content: data = await query_database("SELECT * FROM users LIMIT 10;") await cl.Message(content=f"查询到 {len(data)} 条用户记录。").send()

在这个例子中,query_database函数被装饰为一个工具步骤。它不仅返回数据,还通过step.elements附加了两个cl.Text元素。在前端,这个步骤点开后,会以标签页(display=”page”)或侧边栏(display=”side”)的形式优雅地展示格式化的 SQL 和 JSON 结果,而不是一堆混乱的文本。

4. 构建生产级应用:超越 “Hello World”

一个简单的演示应用和一個生产就绪的应用之间,隔着配置、状态管理、身份验证和部署。我们一步步来。

4.1 应用配置与自定义(chainlit.mdconfig.toml

Chainlit 应用根目录下的chainlit.md文件是你的应用“封面”。它支持 Markdown,会在聊天界面启动前显示。这是你放置应用名称、描述、使用说明和示例问题的最佳位置。

# 欢迎使用我的AI数据分析助手 本助手可以帮你: - 分析上传的CSV、Excel文件 - 执行SQL查询并可视化结果 - 生成数据报告 **试试这样问:** - “请分析我上传的销售数据.csv文件” - “用户表中,年龄大于30的有多少人?” - “为这份数据生成一个总结报告”

更强大的配置在于config.toml文件。在这里,你可以进行深度定制:

[project] name = "我的生产AI助手" description = "用于内部数据查询与分析" # 默认情况下,Chainlit会在本地3000端口运行。你可以修改: # [server] # port = 8080 # host = "0.0.0.0" [UI] name = "Data Copilot" # 设置默认的聊天侧边栏是否打开 default_expand_messages = false [features] # 启用或禁用特定功能 multi_modal = true # 允许上传图片等文件

通过config.toml,你还可以配置环境变量、自定义 CSS/JS、设置会话记忆长度等,让应用完全贴合你的品牌和需求。

4.2 文件上传与处理

Chainlit 内置了便捷的文件上传处理器。用户上传的文件可以通过cl.Message对象的elements属性或专门的文件处理回调来获取。

import chainlit as cl import pandas as pd import io @cl.on_message async def main(message: cl.Message): # 检查消息是否带有文件 if message.elements: for element in message.elements: if "text/csv" in element.mime: # 读取CSV文件内容 content = await element.read() df = pd.read_csv(io.BytesIO(content)) # 进行你的数据处理逻辑... summary = df.describe().to_string() await cl.Message(content=f"文件已接收,共 {len(df)} 行数据。\n数据概览:\n{summary}").send() return # 处理文本消息... await cl.Message(content=f“你说了:{message.content}”).send()

4.3 用户身份验证与会话隔离

对于内部工具或 SaaS 应用,身份验证是必须的。Chainlit 支持通过@cl.password_auth_callback装饰器实现简单的密码验证,也支持 OAuth 等更复杂的方案。

import chainlit as cl @cl.password_auth_callback def auth_callback(username: str, password: str): # 这里连接你的数据库或LDAP进行验证 if username == "admin" and password == "supersecret": return cl.User(identifier="admin", metadata={"role": "admin"}) elif username == "user" and password == "mypassword": return cl.User(identifier="user", metadata={"role": "viewer"}) else: return None # 认证失败 @cl.on_chat_start async def start(): user = cl.user_session.get("user") if user.metadata["role"] == "admin": await cl.Message(content="管理员您好,您拥有所有权限。").send() else: await cl.Message(content="欢迎,您的权限为查看。").send()

认证成功后,用户信息会存储在会话中,你可以据此实现功能级权限控制。切记,生产环境务必使用 HTTPS,并且密码验证仅为示例,强烈建议集成企业级 SSO。

4.4 部署方案选型

Chainlit 应用本质上是一个 Python ASGI 应用(基于 FastAPI)。这意味着它可以用任何支持 ASGI 的服务器来部署。

  • 本地/开发chainlit run app.py -w(自带热重载)。
  • 生产部署(推荐)
    1. 使用 Gunicorn/Uvicorn:这是最标准的方式。首先,确保你的应用入口文件(如app.py)顶部有__main__判断。

      # app.py import chainlit as cl # ... 你的所有装饰器函数和逻辑 ... if __name__ == "__main__": # 开发模式运行 cl.run()

      然后,使用 Uvicorn 工人(Worker)通过 Gunicorn 启动:

      pip install gunicorn uvicorn gunicorn app:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8080

      这里app:app第一个app是文件名,第二个app是 Chainlit 在模块中创建的 ASGI 应用对象(cl.make_app()的产物,通常变量名就是app)。-w 4指定 4 个 worker 进程。

    2. 使用 Docker 容器化:这是实现环境一致性和便捷扩缩容的最佳实践。

      # Dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8080 CMD ["gunicorn", "app:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8080"]

      构建并运行:

      docker build -t my-chainlit-app . docker run -p 8080:8080 --env-file .env my-chainlit-app
    3. 部署到云平台:将 Docker 容器部署到 Kubernetes(如 GKE, EKS, AKS)、云厂商的容器服务(如 AWS ECS, Google Cloud Run)或 PaaS 平台(如 Railway, Fly.io)。记得配置好环境变量(如 OpenAI API Key)和持久化存储(如果需要保存聊天记录或上传的文件)。

5. 实战避坑指南与性能优化

踩过不少坑后,我总结了一些关键的经验和优化点。

5.1 异步(Async)的正确使用

Chainlit 的核心 API 完全是异步的(async/await)。这意味着你的处理函数也必须是异步的,并且内部调用的 IO 密集型操作(网络请求、数据库查询、文件读写)最好也是异步的,否则会阻塞整个事件循环,导致应用响应缓慢。

  • 正确做法:使用async函数,并对支持的库使用其异步客户端(如httpx.AsyncClient,aiofiles,asyncpg)。
  • 错误做法:在异步函数内使用同步的requests.get()time.sleep()。如果必须用,请使用await asyncio.to_thread()将其放到线程池中执行,避免阻塞。

5.2 会话状态管理的陷阱

cl.user_session是一个类似字典的对象,但它只在单个 WebSocket 连接的生命周期内有效。不要用它来存储超大对象(如巨大的数据集),也不要指望它在服务器重启后依然存在。对于需要持久化或跨会话共享的数据,应该使用外部存储如 Redis、数据库或内存缓存(cachetools)。

import chainlit as cl from cachetools import TTLCache # 在模块层面定义一个内存缓存(所有会话共享) model_cache = TTLCache(maxsize=100, ttl=3600) @cl.on_chat_start async def start(): user_id = cl.user_session.get("id") # 尝试从共享缓存获取模型,避免重复加载 if user_id not in model_cache: # 模拟加载一个很重的模型 expensive_model = load_my_heavy_ai_model() model_cache[user_id] = expensive_model cl.user_session.set("model", model_cache[user_id])

5.3 流式响应的细节控制

流式响应能极大提升用户体验,但需要注意控制。

  • msg.stream_token的频率:不要一个字一个字地流,这样会产生大量细小的网络包。可以适当缓冲,比如每积累 3-5 个词或一个短句再发送一次。
  • 错误处理:在流式生成过程中,如果后端出错,前端可能会一直显示“正在输入…”。务必用try...except包裹你的流式逻辑,并在出错时发送一条完整的错误信息cl.ErrorMessage来终止流式状态。
    try: async for token in generate_stream(): await msg.stream_token(token) except Exception as e: # 先发送一个错误元素 await cl.ErrorMessage(content=f“生成过程中出错:{e}”).send() # 也可以更新原消息,标记为错误 msg.content = f“{msg.content}\n\n[生成中断]” await msg.update()

5.4 前端自定义与扩展

虽然 Chainlit 提供了漂亮的开箱即用 UI,但你仍然可以深度定制。

  • 自定义 CSS:在config.toml中指定custom_css文件路径,可以覆盖几乎所有样式。
  • 自定义 JS:通过custom_js配置注入 JavaScript,可以实现更复杂的交互,如集成第三方图表库(ECharts、Chart.js)来可视化你的数据查询结果。
  • 使用cl.Element家族:除了Text,Chainlit 还提供了ImagePdfAvatarTaskList等多种元素。合理组合这些元素,可以构建出像 Notion 一样丰富的交互式应用。例如,你可以让一个工具步骤的输出是一个可交互的图表Image元素。

5.5 监控与日志

生产环境必须要有监控。Chainlit 应用本身会输出访问日志和错误日志。建议:

  1. 结构化日志:使用structlogjson-logging库,将日志输出为 JSON 格式,方便被 ELK(Elasticsearch, Logstash, Kibana)或 Loki 收集。
  2. 应用性能监控(APM):集成像 Sentry、Datadog 或 OpenTelemetry 这样的工具,监控请求延迟、错误率和关键业务指标(如平均对话轮次、工具调用成功率)。
  3. Chainlit 自身事件:你可以通过编写自定义的回调(虽然文档较少),来钩住 Chainlit 的内部事件,进行更细粒度的追踪。

6. 从原型到产品:一个完整的项目构想

让我们构想一个从零开始,用 Chainlit 构建一个内部“数据分析 Copilot”的完整路径。

第一阶段:快速原型(1-2天)

  • 目标:验证核心功能可行性。
  • 做法
    1. chainlit hello搭建环境。
    2. 写一个简单的@cl.on_message处理函数,里面硬编码调用 OpenAI API 或一个简单的 LangChain 链,实现基础的问答。
    3. 集成文件上传,用pandas快速实现 CSV 文件读取和描述性统计。
    4. 使用@cl.step装饰器将文件解析、数据清洗、分析等步骤可视化。
  • 产出:一个可在本地运行、功能单一但交互直观的演示应用。

第二阶段:功能深化与架构(1-2周)

  • 目标:完善功能,引入生产级组件。
  • 做法
    1. 状态管理:设计cl.user_session结构,存储当前对话的数据框(DataFrame)、分析历史、用户偏好。
    2. 工具集扩展:开发多个@cl.step工具函数,如query_sql_database(连接公司数据库)、generate_plot(用 Matplotlib/Plotly 生成图表并返回图片元素)、summarize_trends(调用 LLM 总结数据趋势)。
    3. 引入 LangGraph:将上述工具编排成一个有状态的 Agent。用户可以先上传数据,然后说“对比一下这两个月的销售额”,Agent 会自动调用相应的分析和可视化工具。用 Chainlit 回调可视化 LangGraph 的执行图。
    4. 配置化:编写config.tomlchainlit.md,完善应用说明和配置。

第三阶段:生产化改造(1周)

  • 目标:确保安全、稳定、可运维。
  • 做法
    1. 身份认证:集成公司的 OAuth 2.0 或 LDAP 认证。
    2. 部署:编写Dockerfiledocker-compose.yml,将应用、Redis(用于会话缓存或任务队列)打包。
    3. 日志与监控:接入统一的日志平台和 APM。
    4. 安全:审查代码,防止提示词注入(Prompt Injection),对用户上传文件做严格类型和大小限制,对数据库查询做权限隔离(基于认证用户)。
    5. 性能:为耗时的分析任务引入异步任务队列(如 Celery 或 Arq),避免 HTTP 请求超时。Chainlit 前端可以显示一个TaskList元素来展示后台任务状态。

第四阶段:迭代与扩展(持续)

  • A/B测试:尝试不同的提示词(Prompt),通过分析对话日志优化效果。
  • 反馈循环:在 UI 中添加“点赞/点踩”按钮,收集用户反馈,用于微调模型或改进工具。
  • 多模态:探索 Chainlit 的multi_modal特性,支持用户上传图片并让模型解读。

这个路径的核心在于,Chainlit 让你在每一个阶段都能快速获得一个可交互、可演示、可收集反馈的界面,而不是在前后端联调上浪费大量时间。它真正做到了让开发者聚焦于 AI 逻辑的价值创造。

我个人在多个内部工具项目中采用了这套模式,最大的体会是:开发效率的提升是惊人的,而且来自业务方的正向反馈因为直观的界面而来得更快、更具体。以前需要写文档说明的复杂流程,现在他们直接在界面上点一点、问一问就明白了。Chainlit 可能不是构建面向海量用户消费级产品的最终选择(那时你可能需要更定制化的前端),但对于绝大多数需要快速将 AI 能力产品化、工具化的场景,它无疑是当前 Python 生态中最锋利的那把“瑞士军刀”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 1:00:59

CANN学习中心:SuperKernel技术综述

SuperKernel技术综述 【免费下载链接】cann-learning-hub CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。 项目地址: https://gitcode.com/cann/cann-learning-hub 1. 背景介绍…

作者头像 李华
网站建设 2026/5/10 0:56:32

全球南方AI治理:从规则接受者到参与者的战略转型与安全路径

1. 项目概述:一个被忽视的AI治理新战场最近和几位在跨国科技公司做政策研究的朋友聊天,话题总绕不开一个词:AI治理。大家讨论的焦点,往往集中在硅谷巨头、欧盟的《人工智能法案》或者某个大国的国家战略上。但有一个声音&#xff…

作者头像 李华
网站建设 2026/5/10 0:51:04

基于多示例学习与可解释AI的在线评测系统学生风险预测

1. 项目概述:当在线评测系统遇上可解释的机器学习在线评测系统(Online Judge, OJ)对于计算机相关专业的学生和编程爱好者来说,是再熟悉不过的“练兵场”了。从经典的LeetCode、Codeforces,到各大高校自建的OJ平台&…

作者头像 李华
网站建设 2026/5/10 0:50:20

网络安全技术岗怎么选,不止渗透...

网络安全技术岗怎么选,不止渗透… 你真知道网络安全有哪些技术岗吗?不是所有人都去打漏洞,也不是所有人都进红队。 把网络安全行业里常见的技术岗岗位分类职责技术要求面试考点薪资段位都梳理清楚了👇 学习资源 如果你也是零基础…

作者头像 李华
网站建设 2026/5/10 0:47:13

元宇宙边缘计算AI架构:从资源调度到个性化体验塑造

1. 项目概述:为什么元宇宙需要一种全新的边缘计算AI架构?如果你最近关注过科技新闻,大概率会被“元宇宙”这个词刷屏。它描绘了一个物理与虚拟世界深度融合的未来图景,人们通过扩展现实设备在其中工作、社交、娱乐。然而&#xff…

作者头像 李华
网站建设 2026/5/10 0:46:14

如何快速搭建高效本地图片搜索引擎:ImageSearch完整实战指南

如何快速搭建高效本地图片搜索引擎:ImageSearch完整实战指南 【免费下载链接】ImageSearch 基于.NET10的本地硬盘千万级图库以图搜图案例Demo和图片exif信息移除小工具分享 项目地址: https://gitcode.com/gh_mirrors/im/ImageSearch ImageSearch是一个基于.…

作者头像 李华