完整功能介绍

  1. 统计数据:当前检验最多的虚假新闻,当前检验最多的假新闻种类,总检验的新闻等数据
  2. 新闻检验:
    • 文字:包含概述、句子分析、来源分析
    • 图片:伪造情况报告包含新闻质量、健康度、AI程度、虚假新闻程度、伪造种类相似度
    • 音频:通过SenseVoice转文字获得文字,作为事实判断、情绪识别、人物音色推断
  3. 个人知识库:使用ragflow内的个人知识库进行连接,主要增删改查以及解析功能
  4. 虛假新闻盘点:
    • 知识库:通过个人知识库+在线搜索实现虚假新闻的AI识别
    • 智能图表:可以通过图形例如饼图折现图,去展示我们提供的今日虚假新闻
  5. 虛假新闻预测:包含我们爬到的热点新闻以及对应的预测可能出现的虚假新闻标题,提取一步组织虚假新闻传播
  6. 虛假新闻还原:通过流水线的方式,经过用户提供数据、新闻预处理、虛假新闻检测、报告生成这几个阶段形成一个虚假新闻报告,内部还有大模型尝试还原虚假新闻后的内容
  7. 虚假新闻智能体:用户定制属于自己的工作流,包含爬虫、知识库、图表、LLM等节点
  8. 爬虫:基于自然语言的实时数据爬取的虚假新闻检测
  9. 多平台:利用electron实现跨win、mac打包客户端

创新点

  1. 知识库
  2. 智能图表
  3. 流水线
  4. 智能体
  5. 爬虫
  6. 音频推断

虚假新闻智能体创新点的必要性

可以开放API,其次这一个点是可以区分于我们正常流程只有智能体可以更好的做到,假设一个企业不准备自己开发这一个功能,而是需要对接其他平台,如果将正常流程作为API的话,实际上调用有很多缺点,企业只能利用固定的流程,并且传入的变量,输出的内容企业都是不可控的,但是如果是可编排的可视化智能体的话,流程自定义、输入自定义、输出自定义,这样才能完全发挥它的用处

同时可以作为我们缺失的商业化的一部分写进去,并且市面上也确实没有提供这样服务的项目

之前一直觉得智能体没用实际上是因为我们平台实际上目前就是一个完整的智能体,而智能体最关键的定制功能对于普通用户来说是无关痛痒的,用户的需求就是简单用上最好的智能体,而最关键的定制功能对于c端来说才是关键,能够契合进入不同需求的不同应用

py中的.env使用

  1. .env 文件

.env 文件用于存储环境变量,通常包含敏感信息,如 API 密钥、数据库连接信息等。

示例:

1
2
3
# .env 文件
OPENAI_API_KEY=
OPENAI_BASE_URL=
  1. 安装 python-dotenv

Python 需要 dotenv 库来读取 .env 文件:

1
pip install python-dotenv
  1. 在 Python 中加载 .env

在代码中使用 dotenv.load_dotenv() 加载 .env 文件中的环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
from dotenv import load_dotenv
import os

# 加载 .env 文件
load_dotenv()

# 读取环境变量
api_key = os.getenv("OPENAI_API_KEY")
base_url = os.getenv("OPENAI_BASE_URL")

print(f"API Key: {api_key}")
print(f"Base URL: {base_url}")
  1. 与 OpenAI API 交互

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from openai import OpenAI
from fastapi import HTTPException
from dotenv import load_dotenv
import os

# 加载环境变量
load_dotenv()

# 获取 API 密钥和基础 URL
api_key = os.getenv("OPENAI_API_KEY")
base_url = os.getenv("OPENAI_BASE_URL")

# 创建 OpenAI 客户端
client = OpenAI(api_key=api_key, base_url=base_url)


async def generate_openai_response(system_content: str, user_content: str, stream: bool):
"""调用 OpenAI API 生成回复"""
try:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": system_content},
{"role": "user", "content": user_content},
],
stream=stream
)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error calling OpenAI API: {e}")
  1. 错误处理
  • .env 文件必须放在项目根目录,否则需要在 load_dotenv() 中指定路径,例如 load_dotenv(dotenv_path="path/to/.env")
  • os.getenv("VAR_NAME") 获取不到值时,可能是 .env 文件未正确加载。

.gitignore使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Python 编译文件
__pycache__/
*.pyc
*.pyo

# 虚拟环境
venv/
env/
*.env/
.env

# FastAPI 构建文件
*.sqlite3
db.sqlite3

# 临时文件
*.log
*.pot
*.pyc

# IDE 配置文件
.vscode/
.idea/

# 操作系统文件
.DS_Store
Thumbs.db

fake_news.json
hot_news.json

.gitignore

.gitignore 规则整理

  1. Python 编译文件
1
2
3
__pycache__/
*.pyc
*.pyo
  • __pycache__/:Python 运行时自动生成的字节码缓存文件夹。
  • *.pyc*.pyo:Python 编译的字节码文件,不需要提交到版本控制系统。
  1. 虚拟环境
1
2
3
4
venv/
env/
*.env/
.env
  • venv/env/:Python 虚拟环境目录,防止提交依赖环境。
  • *.env/.env:存储环境变量的文件(如 API 密钥),应避免提交以防止泄露。
  1. FastAPI 相关文件
1
2
*.sqlite3
db.sqlite3
  • *.sqlite3:SQLite 数据库文件,避免提交数据库数据到 Git。
  • db.sqlite3:常见的 SQLite 数据库文件名称,应忽略。
  1. 临时文件
1
2
3
*.log
*.pot
*.pyc
  • *.log:日志文件,通常不需要提交。
  • *.pot:Python 翻译模板文件,可能是自动生成的,不建议提交。
  • *.pyc:重复出现在 Python 编译文件部分,可保持或删除重复项。
  1. IDE 配置文件
1
2
.vscode/
.idea/
  • .vscode/:VS Code 编辑器的配置目录,包含用户自定义设置,不应提交。
  • .idea/:JetBrains IDE(如 PyCharm)的项目配置文件,建议忽略。
  1. 操作系统生成的文件
1
2
.DS_Store
Thumbs.db
  • .DS_Store:macOS 自动生成的目录元数据文件。
  • Thumbs.db:Windows 资源管理器生成的缩略图缓存文件。
  1. Fake News Detection 相关文件
1
2
fake_news.json
hot_news.json
  • fake_news.jsonhot_news.json:可能是 Fake News Detection 平台的临时数据文件,防止提交测试数据或敏感信息。

Crawl4Al

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
my_project/

├── crawler/ # 主应用代码
│ ├── FakeNews.py
│ ├── HotNews.py
│ ├── Links.py
├── data/ # 爬取后的数据处于的位置
│ ├── fake_news.json
│ ├── hot_news.json
│ ├── Links.py
├── .env # 环境变量文件,存储 API 密钥等敏感信息
├── requirements.txt # 依赖项
└── README.md # 项目说明文件

快速上手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig

async def main():
config = CrawlerRunConfig(
# e.g., first 30 items from Hacker News
css_selector="#Con11"
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://news.sina.com.cn/hotnews/",
config=config
)
print("Partial HTML length:", len(result.cleaned_html))
print("Extracted content:", result.markdown)

if __name__ == "__main__":
asyncio.run(main())

基于 Schema 的爬取

  1. Schema 定义

使用 JSON Schema 来定义需要提取的数据结构:

  • baseSelector: 指定数据所在的父级 HTML 元素

  • ```
    fields

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    : 定义具体要提取的字段,包括:

    - `name`: 字段名称
    - `selector`: CSS 选择器
    - `type`: 提取方式,如 `text` 或 `attribute`
    - `attribute`: 当 `type` 为 `attribute` 时,指定提取的属性

    ```python
    schema = {
    "name": "辟谣信息",
    "baseSelector": "ul#list li",
    "fields": [
    {
    "name": "date",
    "selector": "p.domPC",
    "type": "text"
    },
    {
    "name": "link",
    "selector": "h2 a",
    "type": "attribute",
    "attribute": "href"
    }
    ]
    }
  1. 提取策略

使用 JsonCssExtractionStrategy 解析 HTML,并根据 schema 提取数据。

1
extraction_strategy = JsonCssExtractionStrategy(schema, verbose=True)
  1. 爬取配置

创建 CrawlerRunConfig 以配置爬取策略:

  • extraction_strategy: 应用提取规则
  • cache_mode: 设为 CacheMode.BYPASS 以跳过缓存,确保获取最新数据
1
2
3
4
config = CrawlerRunConfig(
extraction_strategy=extraction_strategy,
cache_mode=CacheMode.BYPASS
)
  1. 数据提取

使用 AsyncWebCrawler 运行爬虫,并解析提取的数据:

1
2
3
4
5
async with AsyncWebCrawler(headless=True, verbose=True) as crawler:
result = await crawler.arun(
url="https://www.piyao.org.cn/jrpy/index.htm",
config=config
)
  1. 数据处理

提取成功后:

  • 解析 JSON 数据
  • 修正相对路径
  • 保存为 JSON 文件
1
2
3
4
5
6
7
8
9
10
11
if result.success:
data = json.loads(result.extracted_content)
for item in data:
raw_link = item["link"]
fixed_link = raw_link.replace("../", "/").replace("/jrpy/", "")
if not fixed_link.startswith("http"):
fixed_link = "https://www.piyao.org.cn" + fixed_link
item["link"] = fixed_link

with open("./data/links.json", "w", encoding="utf-8") as file:
json.dump(data, file, indent=4, ensure_ascii=False)

页面交互

  1. 点击 “查看更多” 按钮

模拟用户操作,点击 “查看更多” 按钮,并滚动加载更多内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function clickLoadMore(maxClicks) {
localStorage.removeItem('click_count');
let moreButton = document.querySelector("#more");
let lastHeight = document.body.scrollHeight;
let clickCount = localStorage.getItem('click_count') || 0;

while (moreButton && clickCount < maxClicks) {
window.scrollTo(0, document.body.scrollHeight);
moreButton.click();
await new Promise(resolve => setTimeout(resolve, 1000));
clickCount++;
localStorage.setItem('click_count', clickCount);

let newHeight = document.body.scrollHeight;
if (newHeight === lastHeight) break;
lastHeight = newHeight;
moreButton = document.querySelector("#more");
}
}
clickLoadMore(1);
  • 滚动到底部
  • 点击 “查看更多”
  • 等待加载
  • 记录点击次数
  • 如果页面高度不变,则停止
  1. 等待内容加载

爬虫在执行爬取前,需要等待 JavaScript 动态加载完成:

1
2
3
4
5
6
7
js:() => {
let click_count = localStorage.getItem('click_count');
if (click_count && click_count >= 1) {
return true;
}
return false;
}
  • 读取 click_count
  • 当点击次数达到 1 次后,返回 true
  • 确保页面已经动态加载了新的内容
  1. 集成到爬取流程

爬取时,将 JS 代码作为 js_code 传入:

1
2
3
4
5
6
7
8
9
result = await crawler.arun(
url="https://www.piyao.org.cn/jrpy/index.htm",
config=CrawlerRunConfig(
js_code=js_click_more,
wait_for=js_wait_for,
extraction_strategy=extraction_strategy,
cache_mode=CacheMode.BYPASS
)
)
  • 先执行 js_click_more 进行点击和滚动
  • wait_for 确保数据加载完成
  • 之后再提取数据

浏览器设置

大模型爬取笔记

  1. 使用大模型进行内容提取

爬取过程中,利用大模型(如 DeepSeek)解析网页,并按指定的结构提取信息。

1.1 定义数据结构

使用 pydantic.BaseModel 定义新闻数据的结构:

1
2
3
4
5
6
7
class FakeNews(BaseModel):
headline: str # 虚假新闻标题
field: str # 领域分类
truth: str # 真相
source: str # 来源
measures: str # 注意事项
date: str # 发布日期

1.2 配置 LLM 提取策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
llm_strategy = LLMExtractionStrategy(
provider="deepseek/deepseek-chat",
api_token=api_key,
api_base=base_url,
schema=FakeNews.model_json_schema(), # 指定 JSON Schema
extraction_type="schema",
instruction="从页面中提取所有谣言及误区,包括标题、分类、真相、来源和注意事项。",
chunk_token_threshold=1000,
overlap_rate=0.1,
apply_chunking=True,
input_format="html",
extra_args={"temperature": 0.1, "max_tokens": 1000},
verbose=True
)
  • instruction: 提示大模型需要提取的内容
  • apply_chunking: 采用分块处理,防止长文本截断
  • chunk_token_threshold: 每个分块的最大 Token 数
  • overlap_rate: 分块间的重叠率,确保上下文连贯

1.3 爬取配置

1
2
3
4
crawl_config = CrawlerRunConfig(
extraction_strategy=llm_strategy,
cache_mode=CacheMode.BYPASS
)

确保数据每次都重新爬取,避免缓存影响。

1.4 处理提取的数据

1
2
3
4
5
6
7
8
9
10
11
async def fetch_and_process_link(result, date):
if result.success and result.extracted_content:
try:
data = json.loads(result.extracted_content)
cleaned_data = [{k: v for k, v in item.items() if k != 'error'} for item in data]
for item in cleaned_data:
item['date'] = date
return cleaned_data
except json.JSONDecodeError as e:
print(f"JSON 解析错误,链接:{result.url}, 错误信息:{e}")
return []
  • 解析 LLM 提取的 JSON 数据
  • 移除可能的 error 字段
  • 补充 date 字段

并行爬取笔记

  1. 优化并行任务

使用 SemaphoreDispatcher 控制并行任务,并设定速率限制器 (RateLimiter)。

1.1 配置爬取速率

1
2
3
4
5
6
rate_limiter = RateLimiter(
base_delay=(0.1, 0.2),
max_delay=15.0,
max_retries=5,
rate_limit_codes=[429, 503]
)
  • base_delay: 设定 0.1~0.2 秒的最小请求间隔,加速并发
  • max_retries: 允许最多 5 次重试
  • rate_limit_codes: 当遇到 429(请求过多)或 503(服务不可用)时,自动等待并重试

1.2 监控爬取进度

1
2
3
4
monitor = CrawlerMonitor(
max_visible_rows=15,
display_mode=DisplayMode.DETAILED
)
  • 监控爬取进度,提供可视化日志

1.3 配置并发控制

1
2
3
4
5
dispatcher = SemaphoreDispatcher(
max_session_permit=30,
rate_limiter=rate_limiter,
monitor=monitor
)
  • max_session_permit: 限制最大并发任务数为 30
  • rate_limiter: 结合速率控制,避免触发反爬机制

1.4 批量爬取

1
2
3
4
5
6
async with AsyncWebCrawler(config=browser_cfg) as crawler:
results = await crawler.arun_many(
urls=[link_data['link'] for link_data in links_data],
config=crawl_config,
dispatcher=dispatcher
)
  • 使用 arun_many 并发爬取所有链接
  • 结合 dispatcher 进行并发控制

1.5 数据存储

1
2
with open("./data/fake_news.json", "w", encoding="utf-8") as file:
json.dump(all_news, file, indent=4, ensure_ascii=False)
  • 解析数据并存储为 JSON 文件

超长 HTML 处理方案

🔹 问题

  • HTML 过长时,大模型(LLM)难以一次性处理完整页面,导致报错超出上下文。
  • 需要 分块(chunking) 处理,以保证大模型能完整解析所有内容。

🔹 解决方案

1. 启用 Chunking 机制

1
2
3
chunk_token_threshold=1500,  # 降低分块 token 阈值
overlap_rate=0.3, # 增加分块重叠率
apply_chunking=True, # 启用分块
  • chunk_token_threshold=1500:将单个分块的 Token 数限制在 1500 以内,减少单次输入长度,防止超限。
  • overlap_rate=0.3:提高 30% 的内容重叠,确保跨块内容的连贯性,避免信息缺失。

2. 使用 CSS 选择器提取关键内容

1
css_selector="#Con11"
  • 只抓取新闻排行榜相关内容,减少无关数据,提高提取准确性。

浏览器设置以及部分调试心得

主要需要注意的点是下面的这个无头模式,改为False可以启动浏览器,方便调试

1
2
3
browser_cfg = BrowserConfig(
headless=True # 无头模式,提高爬取效率
)

还有一点就是浏览器设置是,不存在控制浏览器在调试完成后,不自动关闭的属性的

可以在wait_for属性中使用下面的等待策略进行调试,将return true;去掉,这样就可以保证浏览器一直处于等待状态而不关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 定义爬虫配置
async def main():
# 等待策略
js_wait_for = """
js:() => {
let element = document.querySelector('#Con11 > table > tbody > tr:nth-child(4) > td.ConsTi');
if (element !== null) {
console.log("元素已加载完毕,开始抓取数据...");
return true;
} else {
console.log("未加载该元素,继续等待...");
return false;
}
}
"""

# 配置爬虫
crawl_config = CrawlerRunConfig(
cache_mode=CacheMode.BYPASS, # 每次都重新获取最新数据
wait_for=js_wait_for, # 等待页面加载完成
)

FastAPI

启动命令:uvicorn app.main:app –reload(注意main.py需要位于app文件夹的外侧,才能正确启动,不过我感觉内侧比较合适,只不过内侧的话需要增加一个命令用于进入app文件夹下面)

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my_project/

├── app/ # 主应用代码
│ ├── main.py
│ ├── api/ # API 路由相关模块
│ │ ├── ask_fake_news.py # 处理 /ask_fake_news 请求
│ │ ├── generate_chart.py # 处理 /generate_chart 请求
│ ├── core/ # 核心功能和配置
│ │ ├── config.py # 配置文件(API 密钥、环境变量等)
│ │ ├── openai_client.py # OpenAI 客户端封装,包含 API 调用
│ ├── models/ # 数据模型
│ │ ├── user.py # 用户输入的数据模型
│ ├── utils/ # 工具函数
│ │ ├── fake_news_data_loader.py # 用于加载和处理 JSON 的工具函数
├── .env # 环境变量文件,存储 API 密钥等敏感信息
├── requirements.txt # 依赖项
└── README.md # 项目说明文件

main.py

需要注意的是别忘了在main.py将路由注册,以及需要加上app这个主包,保证找得到文件

1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI
from app.api.ask_fake_news import router as ask_fake_news_router
from app.api.generate_chart import router as generate_chart_router
from app.api.show_fake_news import router as show_fake_news
from app.api.show_hot_news import router as show_hot_news

app = FastAPI()

# 注册路由
app.include_router(ask_fake_news_router)
app.include_router(generate_chart_router)
app.include_router(show_fake_news)
app.include_router(show_hot_news)

流式传输

  1. 示例代码

代码片段实现了一个 FastAPI 路由,使用 OpenAI API 生成流式响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from fastapi import APIRouter, HTTPException
from app.utils.fake_news_data_loader import load_fake_news_schema
from app.core.openai_client import generate_openai_response
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import json

router = APIRouter()

class userContent(BaseModel):
user_content: str # 用户的问题内容

@router.post("/ask_fake_news")
async def ask_fake_news(user_input: userContent):
user_content = user_input.user_content
fake_news_data = load_fake_news_schema()

system_content = f"""
你能够根据用户的提问,查找相关的内容并以结构化的方式进行回答。你的依据是以下JSON:
{json.dumps(fake_news_data, ensure_ascii=False)}
"""

response = await generate_openai_response(system_content, user_content, stream=True)

async def response_generator():
for chunk in response:
chunk_message = chunk.choices[0].delta.content
yield chunk_message

return StreamingResponse(response_generator(), media_type="text/plain")
  1. 代码核心解析

2.1 路由与请求参数

  • @router.post("/ask_fake_news") 定义了一个 POST 请求接口 ask_fake_news,用于接收用户的提问。
  • userContent 通过 Pydantic 进行数据校验,要求请求体包含 user_content 字段。

2.2 处理用户请求

  • user_content = user_input.user_content 获取用户输入的内容。
  • fake_news_data = load_fake_news_schema() 加载虚假新闻数据,作为 AI 生成答案的参考依据。

2.3 生成 OpenAI 流式响应

  • ```
    generate_openai_response(system_content, user_content, stream=True)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11



    - `system_content`:传递给 AI 的背景信息,提供 JSON 格式的虚假新闻数据。
    - `user_content`:用户输入的具体问题。
    - `stream=True`:启用流式传输,返回一个异步生成器对象。

    **2.4 定义流式生成器**

    - ```
    response_generator()

    是一个异步生成器:

    • for chunk in response: 遍历 AI 生成的流式内容。
    • chunk_message = chunk.choices[0].delta.content 提取每个返回片段。
    • yield chunk_message 逐步返回数据。

2.5 返回流式响应

  • StreamingResponse(response_generator(), media_type="text/plain") 通过 StreamingResponseresponse_generator() 的数据流式发送给客户端,减少延迟并提升用户体验。

请求openai类库封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from openai import OpenAI
from fastapi import HTTPException
from dotenv import load_dotenv
import os

# 加载环境变量
load_dotenv()

# 获取 API 密钥和基础 URL
api_key = os.getenv("OPENAI_API_KEY")
base_url = os.getenv("OPENAI_BASE_URL")
client = OpenAI(api_key=api_key, base_url=base_url)

# 打印 OpenAI API 的基础 URL


async def generate_openai_response(system_content: str, user_content: str, stream: bool):
try:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": system_content},
{"role": "user", "content": user_content},
],
stream=stream
)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error calling OpenAI API: {e}")

快速导出requirements.txt

一般使用pipreqs的方法

仅导出 requirements.txt 里需要的依赖:

有时 pip freeze 会导出很多不必要的包,可以手动删除无关的行,或者用 pipreqs 生成:

1
2
pip install pipreqs
pipreqs . --force

这将基于代码中的 import 自动生成 requirements.txt,更精简。

你可以使用以下命令快速导出 requirements.txt:

1
pip freeze > requirements.txt

详细解释:

​ • pip freeze 会列出当前 Python 环境中已安装的所有依赖及其版本。

​ • > 符号用于将输出写入 requirements.txt 文件。

docker部署

参考:https://fastapi.tiangolo.com/zh/deployment/docker/

首先使用上面的快速导出requirements.txt的方法导出requirements.txt

需要注意的是uvicorn是没有通过上面的方法导出的,后面报错后才发现的

1
2
3
4
5
fastapi==0.115.7
openai==1.60.2
pydantic==2.10.6
python-dotenv==1.0.1
uvicorn>=0.15.0,<0.16.0

Dockerfile

需要注意的是app.main:app,这个是因为main位于app包下面,之前还遇见过一个错误是使用了官方基于FastAPI的py镜像,导致端口错误,但是实际上官方不建议使用这个镜像

还有就是"--host", "0.0.0.0"非常有必要,如果默认使用127.0.0.1的话,就只会接受本地的IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用官方 FastAPI 镜像(基于 Python)
FROM python:3.9

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目文件到容器中
COPY . .

EXPOSE 8000

# 启动应用(默认使用 gunicorn)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

运行命令

参考:https://www.assen.top/blog/2024-10-12-docker-proxy中的场景二

需要注意的是--network host这个参数,不加的话很大概率报错ERROR [linux/amd64 internal] load metadata for docker.io/tiangolo/uvicorn-gunicorn-fastapi:python3.9 ,主要原因是创建镜像需要网络连接,这个时候需要指定经过的宿主机网络

1
docker buildx build --network host --platform linux/amd64,linux/arm64 -t tecnb/news-guard-api:remote --push .

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3.8'

services:
api:
image: tecnb/news-guard-api:remote
container_name: news-guard-api
restart: unless-stopped
ports:
- "8000:8000" # 将宿主机的80端口映射到容器8000端口
environment:
- APP_ENV=production
# 如需挂载配置文件或持久化数据
# volumes:
# - ./config:/app/config

类openai界面封装

解决unknown at rule apply 问题

参考:https://blog.csdn.net/njkl2166/article/details/135010067

原因是使用了apply去使用taliwindcss中所定的class名称,导致无法找到,需要添加两个设置文件

  1. 新建文件.vscode/tailwindcss.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    {
    "version": 1.1,
    "atDirectives": [
    {
    "name": "@tailwind",
    "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
    "references": [
    {
    "name": "Tailwind Documentation",
    "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
    }
    ]
    },
    {
    "name": "@apply",
    "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.",
    "references": [
    {
    "name": "Tailwind Documentation",
    "url": "https://tailwindcss.com/docs/functions-and-directives#apply"
    }
    ]
    },
    {
    "name": "@responsive",
    "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
    "references": [
    {
    "name": "Tailwind Documentation",
    "url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
    }
    ]
    },
    {
    "name": "@screen",
    "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
    "references": [
    {
    "name": "Tailwind Documentation",
    "url": "https://tailwindcss.com/docs/functions-and-directives#screen"
    }
    ]
    },
    {
    "name": "@variants",
    "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
    "references": [
    {
    "name": "Tailwind Documentation",
    "url": "https://tailwindcss.com/docs/functions-and-directives#variants"
    }
    ]
    }
    ]
    }
  2. 新建文件.vscode/settings.json

    1
    2
    3
    4
    // .vscode/settings.json
    {
    "css.customData": [".vscode/tailwindcss.json"]
    }

express服务器上线

这个是顺道帮忙时解决的

先是直接在终端npm run start,然后通过nginx转发端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
server_name xxxx;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/boomdamn-web; # 前端静态文件的根目录



# 反向代理到 Node.js 后端(3000端口)
location / {
proxy_pass http://localhost:3000; # 将请求代理到 3000 端口的 Node.js 应用
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}


access_log /www/wwwlogs/xxxxxx.log;
error_log /www/wwwlogs/xxxxxx.error.log;
}

但是由于终端会自动关闭,后续使用了pm2这样一个node管理器,保证不会关闭以及实现快速重启

SSL证书部署问题

下面这几行在配置中很关键,如果之前去掉的话会报错站点配置文件中未找到标识信息【#error_page 404/404.html;】,无法确定SSL配置添加位置,请尝试手动添加标识或恢复配置文件

1
2
3
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#SSL-END

完整如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server {
listen 80;
listen 443 ssl http2 ;
server_name xxxxxx www.zhadanegg.com;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/boomdamn-web; # 前端静态文件的根目录

#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#SSL-END

# 反向代理到 Node.js 后端(3000端口)
location / {
proxy_pass http://localhost:3000; # 将请求代理到 3000 端口的 Node.js 应用
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}


access_log /www/wwwlogs/xxxxxx.log;
error_log /www/wwwlogs/xxxxxx.error.log;
}

使用WaveSurfer实现音频波形图

步骤 1: 安装 wavesurfer.js

1
npm install wavesurfer.js

步骤 2: 创建音频波形图组件

202202050225

Zjc093613

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<template>
<div class="w-full h-full">
<div class="w-full h-full flex flex-col justify-center items-center gap-8">
<div ref="waveform" class="waveform w-full"></div>

<!-- 播放控制按钮 -->
<div class="flex flex-col items-center space-y-4">
<div class="flex items-center space-x-4">
<!-- 向左快进1秒按钮 -->
<button @click="skipBackward"
class="px-4 py-1 bg-gray-500 text-white rounded-md text-lg hover:bg-gray-600 focus:outline-none transition">
<i class="fas fa-backward"></i> <!-- FontAwesome icon -->
</button>

<!-- 播放/暂停按钮 -->
<button @click="togglePlayback"
class="px-4 py-1 bg-green-500 text-white rounded-md text-lg hover:bg-green-600 focus:outline-none transition">
<i :class="isPlaying ? 'fas fa-pause' : 'fas fa-play'"></i> <!-- FontAwesome icon -->
</button>

<!-- 向右快进1秒按钮 -->
<button @click="skipForward"
class="px-4 py-1 bg-gray-500 text-white rounded-md text-lg hover:bg-gray-600 focus:outline-none transition">
<i class="fas fa-forward"></i> <!-- FontAwesome icon -->
</button>
</div>
</div>
</div>

</div>
</template>

<script setup lang="ts">
import { onMounted, ref, watch, defineProps } from 'vue';
import WaveSurfer from 'wavesurfer.js';

const props = defineProps({
audioFile: {
type: String,
required: true,
},
});

const waveform = ref<HTMLDivElement | null>(null);
let waveSurfer: WaveSurfer | null = null;

const isPlaying = ref(false);
const currentTime = ref(0); // 当前播放时间
const duration = ref(0); // 音频时长

onMounted(() => {
if (waveform.value && props.audioFile) {
// 初始化 WaveSurfer 实例
waveSurfer = WaveSurfer.create({
container: waveform.value,
waveColor: '#ddd',
progressColor: '#22c55e',
height: 140, // 可根据需要调整高度
// responsive: true, // 响应式适配
cursorColor: '#22c55e',
});

// 加载音频文件
waveSurfer.load(props.audioFile);

// 设置音量
waveSurfer.on('ready', () => {
duration.value = waveSurfer?.getDuration() || 0;
});

// 更新当前播放时间
waveSurfer.on('audioprocess', () => {
currentTime.value = waveSurfer?.getCurrentTime() || 0;
});

// 播放或暂停时,更新按钮状态
waveSurfer.on('play', () => {
isPlaying.value = true;
});

waveSurfer.on('pause', () => {
isPlaying.value = false;
});
}
});

// 监听 audioFile 变化
watch(() => props.audioFile, (newFile) => {
if (waveSurfer) {
waveSurfer.load(newFile); // 如果文件路径改变,重新加载
}
});

// 播放或暂停音频
const togglePlayback = () => {
if (waveSurfer) {
if (isPlaying.value) {
waveSurfer.pause();
} else {
waveSurfer.play();
}
}
};

// 向左快进1秒
const skipBackward = () => {
if (waveSurfer) {
const newTime = Math.max(0, currentTime.value - 1);
waveSurfer.seekTo(newTime / duration.value);
currentTime.value = newTime;
}
};

// 向右快进1秒
const skipForward = () => {
if (waveSurfer) {
const newTime = Math.min(duration.value, currentTime.value + 1);
waveSurfer.seekTo(newTime / duration.value);
currentTime.value = newTime;
}
};
</script>

<style scoped>
</style>

步骤 3: 使用波形图组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="VerifyAudioView h-full flex justify-center items-center gap-8 p-12">
<AudioWaveform :audioFile="audioFile" v-if="audioFile" />
</div>
</template>

<script setup lang="ts">
import { ref } from "vue"'

import AudioWaveform from '../../components/AudioWaveform.vue';

const audioFile = ref<string | null>(null);

RagFlow

Mac 部署

难点:

  • 官方不维护arm64版本的镜像,需要自己去构筑

  • 构筑完成后如何让该镜像被正确的使用也是问题,官方底下以及所有能够搜到的大部分教程都是去使用docker-compose-macos.yml去启动,实际上官方这个macos的yml文件有问题,最后构筑出来的是amd64平台,应该使用docker-compose.yml进行启动

  • 需要的资源量多,需要去docker软件中将Disk容量拉到120GB以上

步骤:

首先克隆项目,然后进入到对应文件夹

1
2
git clone https://github.com/infiniflow/ragflow.git
cd ragflow/

修改env,MEM_LIMIT拉高,防止内存不足,建议拉到12GB以上

1
2
MEM_LIMIT=13073741824
MACOS=1

下载需要的依赖

1
2
pip3 install huggingface_hub nltk
python3 download_deps.py

通过下面的命令进行构筑米,注意构筑时间非常长

1
2
docker build --network=host -f Dockerfile.deps -t infiniflow/ragflow_deps .
docker build --network=host -f Dockerfile -t infiniflow/ragflow:v0.16.0 .

修改docker-compose.yml,image部分改为自己本地构筑好的镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
include:
- ./docker-compose-base.yml

services:
ragflow:
depends_on:
mysql:
condition: service_healthy
image: infiniflow/ragflow:v0.16.0-slim
platform: linux/arm64
container_name: ragflow-server
ports:
- ${SVR_HTTP_PORT}:9380
- 80:80
- 443:443
volumes:
- ./ragflow-logs:/ragflow/logs
- ./nginx/ragflow.conf:/etc/nginx/conf.d/ragflow.conf
- ./nginx/proxy.conf:/etc/nginx/proxy.conf
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
env_file: .env
environment:
- TZ=${TIMEZONE}
- HF_ENDPOINT=${HF_ENDPOINT}
- MACOS=${MACOS}
networks:
- ragflow
restart: on-failure

使用下面命令进行容器启动

1
docker compose -f docker/docker-compose.yml up

报错:

Bug1

1
failed to solve: infiniflow/ragflow_deps:latest: failed to resolve source metadata for docker.io/infiniflow/ragflow_deps:latest: no match for platform in manifest: not found

该报错在于使用docker-compose-macos.yml去进行启动,可以加上platform: linux/x86_64,避免报错但是最后启动时,rag-server启动会出错

Bug2

1
ImportError: /usr/lib/x86_64-linux-gnu/libodbc.so.2: file too short

该报错在于docker-compose-macos.yml正确启动后的报错,原因是官方的这个yml并没有正确构筑,在其dockerfile中对于arm64平台没做判断,最后平台不同导致无法启动

Bug3

1
{"code":100,"data":null,"message":"<NotFound '404: Not Found'>"}

该报错在于使用http://127.0.0.1:9380/去进行访问,实际上应该使用http://127.0.0.1进行访问

API使用

列举关键API,RagFlow的API有多个不同的ID,在实际返回中却没有对应的区别,导致需要猜测这个ID对应的属性

Header:

  • 'Authorization: Bearer <YOUR_API_KEY>'
  1. 列出数据集

    id为dataset_id

    1
    Get http://127.0.0.1/api/v1/datasets?id=be7f86d0f31711efb8b10242ac120006

    返回:

    此处的id为dataset_id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    {
    "code": 0,
    "data": [
    {
    "avatar": null,
    "chunk_count": 14,
    "chunk_method": "naive",
    "create_date": "Tue, 25 Feb 2025 09:27:57 GMT",
    "create_time": 1740446877761,
    "created_by": "93128efef31511efac840242ac120006",
    "description": null,
    "document_count": 1,
    "embedding_model": "",
    "id": "be7f86d0f31711efb8b10242ac120006",
    "language": "English",
    "name": "fake-news",
    "pagerank": 0,
    "parser_config": {
    "pages": [
    [
    1,
    1000000
    ]
    ]
    },
    "permission": "me",
    "similarity_threshold": 0.2,
    "status": "1",
    "tenant_id": "93128efef31511efac840242ac120006",
    "token_num": 2585,
    "update_date": "Tue, 25 Feb 2025 09:29:37 GMT",
    "update_time": 1740446977285,
    "vector_similarity_weight": 0.3
    }
    ]
    }
  2. 列出聊天助手

    id为chat_id

    1
    Get http://127.0.0.1/api/v1/chats?id=22c63b20f31811ef85c10242ac120006

    返回:

    此处的id为session_id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    {
    "code": 0,
    "data": [
    {
    "chat_id": "22c63b20f31811ef85c10242ac120006",
    "create_date": "Tue, 25 Feb 2025 09:31:13 GMT",
    "create_time": 1740447073578,
    "id": "1f9c8eac44c34b2c98dcfda139aea23f",
    "messages": [
    {
    "content": "你好! 我是你的助理,有什么可以帮到你的吗?",
    "role": "assistant"
    },
    {
    "content": "即食米饭能保存很久,加了大量防腐剂嘛",
    "doc_ids": [],
    "id": "d11a2bf6-8d6a-4c99-8ec0-53a6e77d8d4f",
    "role": "user"
    },
    {
    "content": "根据知识库中的信息,即食米饭能保存很久并不是因为加了大量防腐剂 ##0$$。其保质期长的关键在于制作过程中严格灭菌和密封包装 ##1$$。具体来说:\n\n1. **严格灭菌**:即食米饭在生产过程中会对原料进行杀菌,并在一次包装后进行最终灭菌,使得其内外几乎无菌,能够在常温下长时间保存。\n2. **密封包装**:真空密封包装避免了外界空气和细菌的进入,从根本上阻止了微生物的生长。\n\n这种工艺与罐头食品类似,无需添加防腐剂即可实现长期保存。因此,即食米饭的制作过程完全符合食品安全标准,消费者可以根据需求安心选购。\n\n**来源**: “科学辟谣”微信公众号",
    "created_at": 1740447087.4371278,
    "id": "d11a2bf6-8d6a-4c99-8ec0-53a6e77d8d4f",
    "reference": [
    {
    "content": "{\"1\": {\"measures\": \"对编造散布地震谣言扰乱公共秩序、造成恶劣社会影响的行为,公安机关将依法依规严肃查处。请广大网民擦亮眼睛,勿轻信传播非正规来源、未经证实的地震信息。\", \"date\": \"2025-01-24\"}}{\"2\": {\"headline\": \"即食米饭能保存很久,一定是加了大量防腐剂?\", \"field\": \"食品安全\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "a4e230032be5e6c4",
    "image_id": "",
    "positions": []
    },
    {
    "content": "{\"2\": {\"truth\": \"即食米饭保质期长,关键在于制作过程中严格灭菌和密封包装,与防腐剂无关。即食米饭在生产中,不仅会对原料进行杀菌,还会在一次包装后进行最终灭菌,这使得其内外都几乎无菌,能够在常温下长时间保存。此外,真空密封包装避免了外界空气和细菌的进入,从根本上阻止了微生物的生长。这种工艺与罐头食品类似,无需添加防腐剂即可实现长期保存。总之,即食米饭的制作过程完全符合食品安全标准,消费者可以根据需求安心选购。\", \"source\": \"“科学辟谣”微信公众号\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "e0d57fe3b5f1f16f",
    "image_id": "",
    "positions": []
    },
    {
    "content": "{\"2\": {\"measures\": \"建议消费者将其作为应急或便捷选择,平时仍应以新鲜米饭为主,均衡饮食才是健康之道。\", \"date\": \"2025-01-24\"}}{\"3\": {\"headline\": \"过年遇到这样的红包,务必警惕!\", \"field\": \"网络诈骗\", \"truth\": \"过年期间,几个朋友拉微信群发红包,好不热闹。但如果你突然被拉入一个陌生群聊,告知不仅有“福利”还能“赚钱”,你该怎么办?警方提示,这是典型的刷单返利类诈骗。\", \"source\": \"“熊猫反诈”微信公众号\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "c6ef9e51c38548b9",
    "image_id": "",
    "positions": []
    },
    {
    "content": "{\"7\": {\"source\": \"“南京网络辟谣”微信公众号\", \"measures\": \"\", \"date\": \"2025-01-22\"}}{\"8\": {\"headline\": \"抢票软件真的抢票更快?\", \"field\": \"科技\", \"truth\": \"春运首周,市面上涌现出大量“抢票软件”,号称“只要加钱就能抢到票”。实际上,当12306显示“无票”,有些抢票软件显示“有票”,这样的信息其实都是从12306爬取、修改加工而来的,目的是诱导旅客买长乘短、买短乘长,让旅客多花钱。抢票软件的“加速包”其实也是忽悠旅客的一个噱头,花钱购买加速包,抢票速度也不会提升。需要注意的是,12306平台从来没有和任何第三方平台机构合作,没有将票额分配给任何第三方平台发售,也没有和任何第三方平台进行数据连接。\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "ee5042a9f76c4e6c",
    "image_id": "",
    "positions": []
    }
    ],
    "role": "assistant"
    }
    ],
    "name": "即食米饭能保存很久,加了大量防腐剂嘛",
    "update_date": "Tue, 25 Feb 2025 09:31:27 GMT",
    "update_time": 1740447087437,
    "user_id": null
    }
    ]
    }
  3. 列出聊天助手的会话

    id为chat_id

    1
    Get http://127.0.0.1/api/v1/chats/22c63b20f31811ef85c10242ac120006/sessions

    返回:

    id为session_id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    {
    "code": 0,
    "data": [
    {
    "chat_id": "22c63b20f31811ef85c10242ac120006",
    "create_date": "Tue, 25 Feb 2025 09:31:13 GMT",
    "create_time": 1740447073578,
    "id": "1f9c8eac44c34b2c98dcfda139aea23f",
    "messages": [
    {
    "content": "你好! 我是你的助理,有什么可以帮到你的吗?",
    "role": "assistant"
    },
    {
    "content": "即食米饭能保存很久,加了大量防腐剂嘛",
    "doc_ids": [],
    "id": "d11a2bf6-8d6a-4c99-8ec0-53a6e77d8d4f",
    "role": "user"
    },
    {
    "content": "根据知识库中的信息,即食米饭能保存很久并不是因为加了大量防腐剂 ##0$$。其保质期长的关键在于制作过程中严格灭菌和密封包装 ##1$$。具体来说:\n\n1. **严格灭菌**:即食米饭在生产过程中会对原料进行杀菌,并在一次包装后进行最终灭菌,使得其内外几乎无菌,能够在常温下长时间保存。\n2. **密封包装**:真空密封包装避免了外界空气和细菌的进入,从根本上阻止了微生物的生长。\n\n这种工艺与罐头食品类似,无需添加防腐剂即可实现长期保存。因此,即食米饭的制作过程完全符合食品安全标准,消费者可以根据需求安心选购。\n\n**来源**: “科学辟谣”微信公众号",
    "created_at": 1740447087.4371278,
    "id": "d11a2bf6-8d6a-4c99-8ec0-53a6e77d8d4f",
    "reference": [
    {
    "content": "{\"1\": {\"measures\": \"对编造散布地震谣言扰乱公共秩序、造成恶劣社会影响的行为,公安机关将依法依规严肃查处。请广大网民擦亮眼睛,勿轻信传播非正规来源、未经证实的地震信息。\", \"date\": \"2025-01-24\"}}{\"2\": {\"headline\": \"即食米饭能保存很久,一定是加了大量防腐剂?\", \"field\": \"食品安全\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "a4e230032be5e6c4",
    "image_id": "",
    "positions": []
    },
    {
    "content": "{\"2\": {\"truth\": \"即食米饭保质期长,关键在于制作过程中严格灭菌和密封包装,与防腐剂无关。即食米饭在生产中,不仅会对原料进行杀菌,还会在一次包装后进行最终灭菌,这使得其内外都几乎无菌,能够在常温下长时间保存。此外,真空密封包装避免了外界空气和细菌的进入,从根本上阻止了微生物的生长。这种工艺与罐头食品类似,无需添加防腐剂即可实现长期保存。总之,即食米饭的制作过程完全符合食品安全标准,消费者可以根据需求安心选购。\", \"source\": \"“科学辟谣”微信公众号\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "e0d57fe3b5f1f16f",
    "image_id": "",
    "positions": []
    },
    {
    "content": "{\"2\": {\"measures\": \"建议消费者将其作为应急或便捷选择,平时仍应以新鲜米饭为主,均衡饮食才是健康之道。\", \"date\": \"2025-01-24\"}}{\"3\": {\"headline\": \"过年遇到这样的红包,务必警惕!\", \"field\": \"网络诈骗\", \"truth\": \"过年期间,几个朋友拉微信群发红包,好不热闹。但如果你突然被拉入一个陌生群聊,告知不仅有“福利”还能“赚钱”,你该怎么办?警方提示,这是典型的刷单返利类诈骗。\", \"source\": \"“熊猫反诈”微信公众号\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "c6ef9e51c38548b9",
    "image_id": "",
    "positions": []
    },
    {
    "content": "{\"7\": {\"source\": \"“南京网络辟谣”微信公众号\", \"measures\": \"\", \"date\": \"2025-01-22\"}}{\"8\": {\"headline\": \"抢票软件真的抢票更快?\", \"field\": \"科技\", \"truth\": \"春运首周,市面上涌现出大量“抢票软件”,号称“只要加钱就能抢到票”。实际上,当12306显示“无票”,有些抢票软件显示“有票”,这样的信息其实都是从12306爬取、修改加工而来的,目的是诱导旅客买长乘短、买短乘长,让旅客多花钱。抢票软件的“加速包”其实也是忽悠旅客的一个噱头,花钱购买加速包,抢票速度也不会提升。需要注意的是,12306平台从来没有和任何第三方平台机构合作,没有将票额分配给任何第三方平台发售,也没有和任何第三方平台进行数据连接。\"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "ee5042a9f76c4e6c",
    "image_id": "",
    "positions": []
    }
    ],
    "role": "assistant"
    }
    ],
    "name": "即食米饭能保存很久,加了大量防腐剂嘛",
    "update_date": "Tue, 25 Feb 2025 09:31:27 GMT",
    "update_time": 1740447087437,
    "user_id": null
    }
    ]
    }
  4. 与聊天助手交谈

    id为chat_id

    会话的 ID。如果未提供,将生成一个新会话

    1
    Get http://127.0.0.1/api/v1/chats/22c63b20f31811ef85c10242ac120006/completions

    Body

    id为session_id

    1
    2
    3
    4
    5
    {
    "question": "告诉我2025-01-24的虚假新闻",
    "stream": false,
    "session_id": "1f9c8eac44c34b2c98dcfda139aea23f"
    }

    返回:

    id为对话中该句的ID,目前没发现具体作用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    {
    "code": 0,
    "data": {
    "answer": "xxxx",
    "audio_binary": null,
    "created_at": 1740488049.0561419,
    "id": "cbcc0e18-34cd-4b25-b862-3b314100e24b",
    "prompt": "xxxx",
    "reference": {
    "chunks": [
    {
    "content": "xxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "a4e230032be5e6c4",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "c6ef9e51c38548b9",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "d708e17ccbfc894b",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "f499654c7ee42b70",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "ca747667448e60b2",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "8d54480f9aa2c954",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxx",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "4066c2fd517a8920",
    "image_id": "",
    "positions": []
    },
    {
    "content": "xxxx"}}",
    "dataset_id": "be7f86d0f31711efb8b10242ac120006",
    "document_id": "db911ec8f31711ef949e0242ac120006",
    "document_name": "fake_news.json",
    "id": "66c840574441e3a3",
    "image_id": "",
    "positions": []
    }
    ],
    "doc_aggs": [
    {
    "count": 8,
    "doc_id": "db911ec8f31711ef949e0242ac120006",
    "doc_name": "fake_news.json"
    }
    ],
    "total": 12
    },
    "session_id": "1f9c8eac44c34b2c98dcfda139aea23f"
    }
    }

解决httpx的Delete无法正确传参数的问题

参考:https://github.com/encode/httpx/discussions/1587

该用request进行请求,然后在第一个参数位置给出”DELETE”请求方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@router.delete("/delete_documents")
async def delete_documents(request: parseDocumentsRequest):
datasetsId = request.datasetsId
documentId = request.documentId

print(datasetsId)
print(documentId)
# 设置请求头,包含 Authorization header
headers = {
"Authorization": f"Bearer {RAGFLOW_API_KEY}"
}

json = {
"ids": [
documentId
]
}

async with httpx.AsyncClient() as client:
# 发送 Delete 请求
response = await client.request(
"DELETE",
f"http://localhost:80/api/v1/datasets/{datasetsId}/documents",
headers=headers,
json=json
)

# 确保请求成功
response.raise_for_status()

return response.json()

vue3 路由参数影响网络代理

由于/create-knowledge/:id路由是包含参数的,导致/api也被作为参数传递了,出现不返回后端参数而是返回页面的情况,同时code为307 temporary redirect

路由改为createWebHashHistory就行了

网页插件开发

网络请求

需要注意vite.config.ts里面的代理并不会起效,只需要把baseURL改为正确的后端地址就行,不需要进行转发

Vue3 defineModel 双向绑定

参考:https://cn.vuejs.org/guide/components/v-model

遇到返回不规范JSON的解决方式(JSON5)

参考:https://github.com/json5/json5/issues/240

使用JSON5,但是直接引用的话由于commendJs与ESM的区别,会报错无默认导出

1
2
3
4
5
6
7
8
9
10
import parse = require('../lib/parse')
import stringify = require('../lib/stringify')

interface JSON5 {
parse: typeof parse
stringify: typeof stringify
}

declare const JSON5: JSON5
export default JSON5

导入:

1
import JSON5 from 'json5';