新手进阶Python:办公看板集成AI智能助手+语音交互+自动化问答

大家好!我是CSDN的Python新手博主~ 上一篇我们完成了看板的交互式可视化升级与全终端适配,解决了数据分析效率与移动办公需求,但企业用户反馈三大核心痛点:① 操作仍需手动点击,查询数据/执行任务需逐层找功能,高频操作繁琐;② 无语音交互能力,办公时双手忙碌(如整理文件)无法操作电脑,无法语音查数据、发指令;③ 办公常见问题无自动化解答,新人上手需翻阅大量文档,运维人员反复解答同类问题,效率低下。今天就带来超落地的新手实战项目------办公看板集成AI智能助手+语音交互+自动化问答!

本次基于之前的"全终端交互式看板"代码,新增3大核心功能:① AI智能助手(基于大模型实现自然语言交互,支持文字查询数据、执行定时任务/同步操作,无需手动点击);② 语音交互(集成语音识别/合成,实现语音查数据、语音发指令、语音接收结果,解放双手);③ 自动化问答(搭建企业办公知识库,适配常见问题智能匹配、自动化回复,支持知识库手动/批量维护)。全程基于现有技术栈(Flask+MySQL+ECharts+Redis),新增自然语言解析模块、语音交互引擎、知识库问答工具,代码注释详细,新手只需配置大模型参数、导入知识库数据,跟着步骤复制操作就能成功,让看板实现"能听、会说、懂问答、能办事"的智能办公体验~

一、本次学习目标

  1. 掌握大模型自然语言解析技巧,实现自然语言到看板操作的转换,支持文字查询数据、执行自动化任务,新手也能快速配置交互规则;

  2. 学会语音交互开发,集成语音识别(SpeechRecognition)与文字转语音(TTS),实现语音查询、语音指令、语音播报结果,适配办公场景解放双手;

  3. 理解企业知识库搭建逻辑,实现常见问题的智能匹配、自动化问答,支持知识库的增删改查、批量导入导出,降低新人学习成本;

  4. 实现智能助手、语音交互、自动化问答与现有看板功能的闭环联动,语音/文字指令可直接触达数据查询、任务执行、问题解答;

  5. 适配企业办公实际场景,支持智能交互日志追溯、语音指令权限管控、知识库按角色分类查看,兼顾智能性与安全性。

二、前期准备

  1. 安装核心依赖库

安装核心依赖(语音识别/合成、大模型交互、知识库匹配)

pip3 install SpeechRecognition pyttsx3 openai fuzzywuzzy python-Levenshtein flask-cors -i https://pypi.tuna.tsinghua.edu.cn/simple

语音识别依赖(PyAudio,Windows/Linux/Mac安装方式不同,新手按需执行)

Windows

pip3 install pyaudio -i https://pypi.tuna.tsinghua.edu.cn/simple

Linux

apt-get install portaudio19-dev

pip3 install pyaudio

Mac

brew install portaudio

pip3 install pyaudio

确保已有依赖正常(Flask、Redis、ECharts等)

pip3 install --upgrade flask flask-login flask-sqlalchemy redis pymysql openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple

说明:语音识别基于SpeechRecognition+PyAudio实现(支持麦克风实时识别),文字转语音基于pyttsx3实现(离线可用,无需联网);AI智能助手基于OpenAI GPT实现自然语言解析,可替换为国内大模型(如文心一言、通义千问);知识库智能匹配基于fuzzywuzzy实现模糊匹配,适配口语化问题查询。

  1. 第三方服务与配置准备
  • 大模型配置:获取OpenAI API Key(或国内大模型API地址/密钥),配置交互提示词(限定办公看板操作范围)、响应格式(结构化JSON),设置请求超时时间与重试次数;

  • 语音交互配置:定义语音指令规则(如"查询今日订单量""触发ERP同步""打开销售数据图表"),设置语音识别语言(zh-CN)、TTS语音语速/音量,配置语音指令权限(仅允许有权限的用户执行高危语音指令);

  • 知识库配置:梳理企业办公常见问题(如"如何导出审计日志""如何配置定时任务""看板忘记密码怎么办"),按问题类型分类(操作类/故障类/配置类),准备问题-答案对照表,支持批量导入Excel;

  • 安全配置:在.env文件中补充大模型API密钥、语音指令白名单、知识库角色访问权限,开启智能交互/语音操作日志记录,确保操作可追溯;Redis缓存知识库数据,提升问答匹配速度。

  1. 数据库表优化与创建

-- 连接MySQL数据库(替换为你的数据库信息)

mysql -u office_user -p -h 47.108.xxx.xxx office_data

-- 创建企业知识库表(company_knowledge)

CREATE TABLE company_knowledge (

id INT AUTO_INCREMENT PRIMARY KEY,

question VARCHAR(500) NOT NULL COMMENT '问题(支持口语化)',

answer TEXT NOT NULL COMMENT '答案(支持富文本/链接)',

question_type ENUM('operation', 'fault', 'config', 'other') NOT NULL COMMENT '问题类型:操作/故障/配置/其他',

role_ids TEXT NOT NULL COMMENT '可查看的角色ID(JSON格式,[]表示所有角色)',

is_top TINYINT(1) DEFAULT 0 COMMENT '是否置顶(1-是,0-否)',

click_count INT DEFAULT 0 COMMENT '点击次数',

create_by INT NOT NULL COMMENT '创建人ID',

create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

KEY idx_question_type (question_type),

KEY idx_is_top (is_top),

FULLTEXT KEY idx_question (question) -- 全文索引,提升问题匹配速度

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业办公知识库表';

-- 创建智能交互日志表(ai_interact_log)

CREATE TABLE ai_interact_log (

id BIGINT AUTO_INCREMENT PRIMARY KEY,

user_id INT NOT NULL COMMENT '用户ID',

interact_type ENUM('text', 'voice') NOT NULL COMMENT '交互类型:文字/语音',

input_content VARCHAR(1000) NOT NULL COMMENT '输入内容(文字/语音转文字)',

output_content TEXT NOT NULL COMMENT '输出内容',

operate_action VARCHAR(100) NULL COMMENT '触发的看板操作(如query_data/run_task)',

operate_result ENUM('success', 'fail', 'no_operate') DEFAULT 'no_operate' COMMENT '操作执行结果',

ip_address VARCHAR(50) NOT NULL COMMENT '操作IP',

terminal_type ENUM('pc', 'mobile', 'pad') NOT NULL COMMENT '终端类型',

interact_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '交互时间',

KEY idx_user_id (user_id),

KEY idx_interact_type (interact_type),

KEY idx_interact_time (interact_time)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI智能助手交互日志表';

-- 创建问答记录统计表(qa_statistics)

CREATE TABLE qa_statistics (

id INT AUTO_INCREMENT PRIMARY KEY,

knowledge_id INT NULL COMMENT '关联知识库ID(NULL表示未匹配到)',

user_id INT NOT NULL COMMENT '提问用户ID',

question VARCHAR(500) NOT NULL COMMENT '用户提问内容',

match_score INT DEFAULT 0 COMMENT '问题匹配相似度(0-100)',

is_satisfied TINYINT(1) NULL COMMENT '是否满意(1-是,0-否,NULL表示未评价)',

ask_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '提问时间',

KEY idx_knowledge_id (knowledge_id),

KEY idx_ask_time (ask_time),

FOREIGN KEY (knowledge_id) REFERENCES company_knowledge(id) ON DELETE SET NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自动化问答记录统计表';

-- 初始化知识库示例数据

INSERT INTO company_knowledge (question, answer, question_type, role_ids, is_top, create_by)

VALUES (

'如何导出今日的销售订单数据',

'1. 进入看板【销售数据】页面;2. 筛选时间为【今日】;3. 点击页面右侧【导出】按钮,选择Excel格式即可导出。',

'operation',

'[]',

1,

1

);

INSERT INTO company_knowledge (question, answer, question_type, role_ids, is_top, create_by)

VALUES (

'ERP数据同步失败怎么办',

'1. 进入看板【系统管理】-【同步日志】查看失败原因;2. 常见原因:ERP接口密钥过期/网络不通/数据格式错误;3. 对应解决:更新密钥/检查网络/核对数据字段映射。',

'fault',

'[]',

1,

1

);

INSERT INTO company_knowledge (question, answer, question_type, role_ids, is_top, create_by)

VALUES (

'如何配置定时导出报表并邮件推送',

'1. 进入看板【报表管理】-【定制报表】;2. 选择需导出的报表,开启【定时导出】;3. 配置Cron表达式与接收人邮箱,保存即可。',

'config',

'[1]', -- 仅管理员可查看

0,

1

);

三、实战:AI智能助手+语音交互+自动化问答集成

  1. 第一步:搭建AI智能助手引擎,实现自然语言交互办事

-- coding: utf-8 --

ai_assistant.py AI智能助手引擎(自然语言交互)

import json

import time

from flask import Blueprint, request, jsonify, g

from flask_login import login_required, current_user

from models import db, AIInteractLog, Order, ERPSyncLog, ReportConfig

from audit_engine import audit_log, get_terminal_type

from fuzzywuzzy import fuzz

from dotenv import load_dotenv

import os

import redis

from openai import OpenAI

加载环境变量

load_dotenv()

ai_bp = Blueprint("ai_assistant", name)

初始化大模型客户端(OpenAI,可替换为国内大模型)

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo")

client = OpenAI(api_key=OPENAI_API_KEY)

初始化Redis(缓存大模型解析结果、高频数据,提升响应速度)

redis_client = redis.Redis(

host=os.getenv("REDIS_HOST", "localhost"),

port=int(os.getenv("REDIS_PORT", 6379)),

db=int(os.getenv("REDIS_DB", 1)),

decode_responses=True

)

看板操作映射(限定大模型可执行的操作范围,避免越权)

OPERATE_MAP = {

"query_data": ["查询", "查看", "统计"],

"run_sync": ["同步", "更新", "刷新"],

"run_task": ["执行", "触发", "启动"],

"export_data": ["导出", "下载"]

}

====================== 核心功能:大模型自然语言解析 ======================

def parse_natural_language(input_text):

"""

调用大模型解析自然语言,提取操作意图、参数

:param input_text: 用户输入的自然语言内容

:return: 结构化解析结果(dict)

"""

构建提示词(限定范围、指定格式,确保解析结果可直接使用)

prompt = f"""

你是企业办公看板的智能助手,仅能处理看板相关的自然语言请求,需完成以下任务:

  1. 提取用户的操作意图 ,仅能从【query_data/run_sync/run_task/export_data/no_operate】中选择,no_operate表示无具体操作(仅问答);

  2. 若操作意图为query_data,提取查询参数 :数据类型(订单/销售/同步日志)、时间范围(今日/昨日/近7天/本月)、筛选条件(部门/状态);

  3. 若操作意图为run_sync/run_task/export_data,提取执行参数 :目标对象(ERP/订单报表/销售数据)、执行方式(立即/定时);

  4. 若无法识别或非看板相关请求,操作意图为no_operate,参数为空;

  5. 严格按以下JSON格式返回,无多余文字,键名不可修改:

{{

"operate_intent": "操作意图",

"params": {{

"target": "目标对象",

"time_range": "时间范围",

"filter": "筛选条件"

}}

}}

复制代码
用户请求:{input_text}
"""

try:
    # 调用大模型API
    response = client.chat.completions.create(
        model=OPENAI_MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.1,  # 降低随机性,确保解析精准
        timeout=30
    )
    # 解析返回结果
    parse_result = json.loads(response.choices[0].message.content.strip())
    return parse_result
except Exception as e:
    # 解析失败,返回默认结果
    return {
        "operate_intent": "no_operate",
        "params": {"target": "", "time_range": "", "filter": ""},
        "error": f"解析失败:{str(e)}"
    }

====================== 核心功能:执行看板操作(根据解析结果) ======================

def execute_board_operate(operate_intent, params):

"""

根据自然语言解析结果,执行对应的看板操作

:param operate_intent: 操作意图

:param params: 执行参数

:return: 操作结果(str)、操作状态(success/fail/no_operate)

"""

if operate_intent == "no_operate":

return "暂无具体看板操作,可尝试查询数据/执行同步任务~", "no_operate"

复制代码
# 操作1:查询数据(订单/销售/同步日志)
if operate_intent == "query_data":
    target = params.get("target", "订单")
    time_range = params.get("time_range", "今日")
    # 时间范围映射(复用之前的TIME_RANGE_MAP)
    from chart_api import TIME_RANGE_MAP
    start_time = TIME_RANGE_MAP.get(time_range, TIME_RANGE_MAP["today"])
    
    try:
        if target in ["订单", "销售"]:
            # 查询订单/销售数据
            query = Order.query.filter(Order.create_time >= start_time)
            # 权限过滤
            if not current_user.has_perm("view_all_data"):
                user_dept_ids = [d.id for d in current_user.departments]
                query = query.filter(Order.dept_id.in_(user_dept_ids))
            data_count = query.count()
            data_amount = query.with_entities(db.func.sum(Order.amount)).scalar() or 0
            return f"【{time_range}{target}数据】共{data_count}条,总金额{data_amount:.2f}元~", "success"
        elif target == "同步日志":
            # 查询ERP同步日志
            log = ERPSyncLog.query.order_by(ERPSyncLog.start_time.desc()).first()
            if log:
                return f"【最新ERP同步日志】{log.start_time.strftime('%Y-%m-%d %H:%M')},状态:{log.status},成功{log.success_count}条,失败{log.fail_count}条~", "success"
            else:
                return "暂无ERP同步日志~", "success"
        else:
            return f"暂不支持查询{target}数据~", "fail"
    except Exception as e:
        return f"查询数据失败:{str(e)}", "fail"

# 操作2:执行同步(ERP/其他系统)
elif operate_intent == "run_sync":
    target = params.get("target", "ERP")
    if target != "ERP":
        return f"暂不支持{target}数据同步~", "fail"
    try:
        # 调用ERP同步函数(复用之前的erp_sync.py)
        from erp_sync import sync_erp_to_dashboard
        sync_result = sync_erp_to_dashboard("order", "increment", current_user.username)
        if sync_result["success"]:
            return f"ERP订单数据已立即同步,{sync_result['msg']}~", "success"
        else:
            return f"ERP同步失败:{sync_result['msg']}~", "fail"
    except Exception as e:
        return f"执行同步失败:{str(e)}", "fail"

# 操作3:执行定时任务/导出
elif operate_intent in ["run_task", "export_data"]:
    target = params.get("target", "")
    if not target:
        return "请指定需执行/导出的目标对象(如订单报表)~", "fail"
    try:
        # 匹配报表名称(模糊匹配)
        report = ReportConfig.query.filter(ReportConfig.report_name.like(f"%{target}%")).first()
        if not report:
            return f"未找到{target}相关报表~", "fail"
        if operate_intent == "export_data":
            # 调用报表导出函数(复用之前的report_export.py)
            from report_export import export_excel_by_template
            temp_path = export_excel_by_template(report)
            return f"{target}报表已立即导出,可前往【报表管理】下载~", "success"
        else:
            return f"{target}相关定时任务已立即执行~", "success"
    except Exception as e:
        return f"执行操作失败:{str(e)}", "fail"

# 未知操作
else:
    return "暂不支持该操作~", "no_operate"

====================== 核心功能:记录AI交互日志 ======================

def record_ai_log(interact_type, input_content, output_content, operate_action, operate_result):

"""记录AI智能助手交互日志"""

ai_log = AIInteractLog(

user_id=current_user.id,

interact_type=interact_type,

input_content=input_content[:1000], # 限制长度

output_content=output_content,

operate_action=operate_action,

operate_result=operate_result,

ip_address=request.remote_addr,

terminal_type=get_terminal_type()

)

db.session.add(ai_log)

db.session.commit()

====================== 接口:AI智能助手文字交互 ======================

@ai_bp.route("/ai/chat/text", methods=["POST"])

@login_required

@audit_log("query")

def ai_text_chat():

"""AI智能助手文字交互接口(核心)"""

data = request.get_json()

input_text = data.get("input_text", "").strip()

if not input_text:

return jsonify({"success": False, "error": "请输入交互内容~"})

复制代码
# 先从Redis缓存获取结果(高频问题,提升响应速度)
cache_key = f"ai_chat:{current_user.id}:{input_text[:50]}"
cache_result = redis_client.get(cache_key)
if cache_result:
    cache_data = json.loads(cache_result)
    # 记录日志
    record_ai_log("text", input_text, cache_data["output"], cache_data["intent"], cache_data["result"])
    return jsonify({"success": True, "output": cache_data["output"]})

# 1. 解析自然语言
parse_result = parse_natural_language(input_text)
operate_intent = parse_result.get("operate_intent", "no_operate")
params = parse_result.get("params", {})

# 2. 执行看板操作
output_content, operate_result = execute_board_operate(operate_intent, params)

# 3. 记录交互日志
record_ai_log("text", input_text, output_content, operate_intent, operate_result)

# 4. 存入Redis缓存(过期时间1小时,高频问题快速响应)
redis_client.setex(
    cache_key,
    3600,
    json.dumps({
        "output": output_content,
        "intent": operate_intent,
        "result": operate_result
    })
)

return jsonify({"success": True, "output": output_content})

====================== 接口:AI智能助手交互日志查询 ======================

@ai_bp.route("/ai/chat/log", methods=["GET"])

@login_required

@audit_log("query")

def get_ai_chat_log():

"""查询AI智能助手交互日志"""

page = int(request.args.get("page", 1))

page_size = int(request.args.get("page_size", 20))

interact_type = request.args.get("interact_type")

复制代码
# 构建查询条件(普通用户仅能查看自己的日志,管理员可查看所有)
query = AIInteractLog.query.order_by(AIInteractLog.interact_time.desc())
if not current_user.has_perm("view_all_data"):
    query = query.filter(AIInteractLog.user_id == current_user.id)
if interact_type:
    query = query.filter(AIInteractLog.interact_type == interact_type)

# 分页查询
pagination = query.paginate(page=page, per_page=page_size)
logs = pagination.items

# 格式化结果
log_list = []
for log in logs:
    log_list.append({
        "log_id": log.id,
        "username": log.user.username if log.user else "",
        "interact_type": {"text": "文字", "voice": "语音"}[log.interact_type],
        "input_content": log.input_content,
        "output_content": log.output_content,
        "operate_action": log.operate_action or "无",
        "operate_result": {"success": "成功", "fail": "失败", "no_operate": "无操作"}[log.operate_result],
        "terminal_type": {"pc": "电脑", "mobile": "手机", "pad": "平板"}[log.terminal_type],
        "interact_time": log.interact_time.strftime("%Y-%m-%d %H:%M:%S")
    })

return jsonify({
    "success": True,
    "data": log_list,
    "total": pagination.total,
    "page": page,
    "page_size": page_size
})

在app.py中新增以下内容

from ai_assistant import ai_bp

注册AI智能助手蓝图

app.register_blueprint(ai_bp, url_prefix="/api")

  1. 第二步:实现语音交互功能,解放双手语音操作

-- coding: utf-8 --

voice_interact.py 语音交互引擎(识别/合成/指令执行)

import json

import speech_recognition as sr

import pyttsx3

from flask import Blueprint, request, jsonify

from flask_login import login_required, current_user

from audit_engine import audit_log

from ai_assistant import parse_natural_language, execute_board_operate, record_ai_log

from dotenv import load_dotenv

import os

加载环境变量

load_dotenv()

voice_bp = Blueprint("voice", name)

====================== 初始化语音引擎(TTS/识别) ======================

1. 文字转语音(TTS)引擎初始化

tts_engine = pyttsx3.init()

配置TTS参数(语速/音量/语音)

tts_engine.setProperty("rate", int(os.getenv("TTS_RATE", 150))) # 语速,默认150

tts_engine.setProperty("volume", float(os.getenv("TTS_VOLUME", 0.8))) # 音量,0-1

选择语音(0=英文,1=中文,按需调整)

voices = tts_engine.getProperty("voices")

tts_engine.setProperty("voice", voices[1].id if len(voices) > 1 else voices[0].id)

2. 语音识别引擎初始化

r = sr.Recognizer()

语音识别语言(中文)

RECOG_LANG = os.getenv("RECOG_LANG", "zh-CN")

语音指令白名单(高危操作需权限)

VOICE_WHITELIST = json.loads(os.getenv("VOICE_WHITELIST", '["query_data", "no_operate"]'))

====================== 核心功能:语音识别(麦克风/前端音频) ======================

def voice_to_text(audio_data=None):

"""

语音转文字

:param audio_data: 前端传入的音频数据(None则使用麦克风,仅服务端本地可用)

:return: 识别后的文字(str)

"""

try:

if audio_data:

处理前端传入的音频数据(实战中需解析wav/mp3格式)

audio = sr.AudioData(audio_data, 16000, 2)

else:

服务端本地麦克风识别(测试用)

with sr.Microphone() as source:

r.adjust_for_ambient_noise(source) # 消除环境噪音

audio = r.listen(source, timeout=10, phrase_time_limit=20)

复制代码
    # 调用百度/谷歌语音识别(谷歌离线,百度需API,新手先用谷歌)
    # 谷歌离线识别
    text = r.recognize_google(audio, language=RECOG_LANG)
    return text, True
except sr.UnknownValueError:
    return "无法识别语音内容,请重新说~", False
except sr.RequestError as e:
    return f"语音识别服务异常:{str(e)}", False
except Exception as e:
    return f"语音识别失败:{str(e)}", False

====================== 核心功能:文字转语音(本地播报/返回音频) ======================

def text_to_voice(text, save_path=None):

"""

文字转语音

:param text: 需播报的文字

:param save_path: 音频保存路径(None则直接播报)

:return: 操作结果(bool)

"""

try:

tts_engine.say(text)

if save_path:

保存为音频文件(供前端播放)

tts_engine.save_to_file(text, save_path)

tts_engine.runAndWait()

return True

except Exception as e:

print(f"文字转语音失败:{str(e)}")

return False

====================== 核心功能:语音指令权限校验 ======================

def check_voice_perm(operate_intent):

"""

语音指令权限校验:高危操作(如删除/同步)需单独权限,仅白名单可执行

:param operate_intent: 操作意图

:return: 校验结果(bool)

"""

管理员拥有所有语音指令权限

if current_user.has_perm("view_all_data"):

return True

普通用户仅可执行白名单内的操作

return operate_intent in VOICE_WHITELIST

====================== 核心功能:语音交互主逻辑 ======================

def voice_interact_main(audio_data=None):

"""语音交互主逻辑:识别→解析→执行→播报"""

1. 语音转文字

input_text, recog_success = voice_to_text(audio_data)

if not recog_success:

return input_text, "no_operate", "fail"

复制代码
# 2. 解析自然语言
parse_result = parse_natural_language(input_text)
operate_intent = parse_result.get("operate_intent", "no_operate")
params = parse_result.get("params", {})

# 3. 语音指令权限校验
if not check_voice_perm(operate_intent):
    return "你无权限执行该语音指令,请使用文字操作~", operate_intent, "fail"

# 4. 执行看板操作
output_content, operate_result = execute_board_operate(operate_intent, params)

# 5. 文字转语音播报结果(本地)
text_to_voice(output_content)

return output_content, operate_intent, operate_result

====================== 接口:语音识别(前端传入音频,实战核心) ======================

@voice_bp.route("/voice/recognize", methods=["POST"])

@login_required

@audit_log("query")

def voice_recognize():

"""语音识别接口(接收前端音频数据,转文字)"""

if "audio" not in request.files:

return jsonify({"success": False, "error": "未上传音频文件~"})

复制代码
audio_file = request.files["audio"]
if audio_file.filename == "":
    return jsonify({"success": False, "error": "音频文件为空~"})

try:
    # 读取音频数据
    audio_data = audio_file.read()
    # 语音转文字
    input_text, recog_success = voice_to_text(audio_data)
    if not recog_success:
        return jsonify({"success": False, "output": input_text})
    # 调用AI助手解析执行
    output_content, operate_intent, operate_result = execute_board_operate(operate_intent, parse_natural_language(input_text)["params"])
    # 记录交互日志
    record_ai_log("voice", input_text, output_content, operate_intent, operate_result)
    # 返回结果(供前端TTS播放)
    return jsonify({"success": True, "input_text": input_text, "output": output_content})
except Exception as e:
    return jsonify({"success": False, "output": f"语音交互失败:{str(e)}"})

====================== 接口:本地语音交互(测试用,服务端麦克风) ======================

@voice_bp.route("/voice/chat/local", methods=["GET"])

@login_required

@audit_log("query")

def voice_chat_local():

"""本地语音交互(测试用,仅服务端本地运行可用)"""

try:

语音交互主逻辑

output_content, operate_intent, operate_result = voice_interact_main()

记录交互日志

record_ai_log("voice", "本地麦克风输入", output_content, operate_intent, operate_result)

return jsonify({"success": True, "output": output_content})

except Exception as e:

return jsonify({"success": False, "output": f"本地语音交互失败:{str(e)}"})

====================== 接口:文字转语音(前端调用,返回音频) ======================

@voice_bp.route("/voice/tts", methods=["POST"])

@login_required

@audit_log("query")

def voice_tts():

"""文字转语音,返回音频文件"""

data = request.get_json()

text = data.get("text", "").strip()

if not text:

return jsonify({"success": False, "error": "请输入需播报的文字~"})

复制代码
try:
    # 保存音频到临时路径
    temp_path = f"/tmp/tts_{current_user.id}_{int(time.time())}.wav"
    text_to_voice(text, save_path=temp_path)
    # 返回音频文件
    return send_file(
        temp_path,
        as_attachment=True,
        download_name="tts_audio.wav",
        mimetype="audio/wav"
    )
except Exception as e:
    return jsonify({"success": False, "error": f"文字转语音失败:{str(e)}"})
AI办公助手

×
点击麦克风/输入文字开始交互~
发送

  1. 第三步:开发企业知识库与自动化问答,实现常见问题智能回复

-- coding: utf-8 --

knowledge_base.py 企业知识库与自动化问答引擎

import json

import pandas as pd

from flask import Blueprint, request, jsonify, send_file

from flask_login import login_required, current_user

from models import db, CompanyKnowledge, QaStatistics

from audit_engine import audit_log

from fuzzywuzzy import fuzz, process

from dotenv import load_dotenv

import os

import redis

加载环境变量

load_dotenv()

kb_bp = Blueprint("knowledge_base", name)

初始化Redis(缓存知识库数据,提升匹配速度)

redis_client = redis.Redis(

host=os.getenv("REDIS_HOST", "localhost"),

port=int(os.getenv("REDIS_PORT", 6379)),

db=int(os.getenv("REDIS_DB", 2)),

decode_responses=True

)

知识库缓存键

KB_CACHE_KEY = "company_knowledge:all"

匹配相似度阈值(低于该值则认为未匹配到)

MATCH_THRESHOLD = int(os.getenv("MATCH_THRESHOLD", 70))

====================== 核心功能:加载知识库到Redis ======================

def load_kb_to_redis():

"""加载知识库数据到Redis,提升匹配速度"""

kbs = CompanyKnowledge.query.all()

kb_data = []

for kb in kbs:

解析角色ID

role_ids = json.loads(kb.role_ids)

kb_data.append({

"id": kb.id,

"question": kb.question,

"answer": kb.answer,

"question_type": kb.question_type,

"role_ids": role_ids,

"is_top": kb.is_top

})

存入Redis(永久缓存,更新知识库时刷新)

redis_client.set(KB_CACHE_KEY, json.dumps(kb_data))

return kb_data

====================== 核心功能:获取可查看的知识库数据 ======================

def get_available_kb():

"""获取当前用户可查看的知识库数据(按角色过滤)"""

从Redis获取知识库数据

kb_str = redis_client.get(KB_CACHE_KEY)

if not kb_str:

kb_data = load_kb_to_redis()

else:

kb_data = json.loads(kb_str)

管理员可查看所有,普通用户仅查看角色ID为空或包含自身角色的

user_role_id = current_user.role.id if current_user.role else 0

if current_user.has_perm("view_all_data"):

return kb_data

else:

return [

kb for kb in kb_data

if len(kb["role_ids"]) == 0 or user_role_id in kb["role_ids"]

]

====================== 核心功能:智能匹配问题(模糊匹配) ======================

def match_question(user_question):

"""

智能匹配知识库问题

:param user_question: 用户提问内容

:return: 匹配结果(dict):id/question/answer/match_score

"""

kb_data = get_available_kb()

if not kb_data:

return {"match_score": 0, "answer": "暂无知识库数据~"}

复制代码
# 提取问题列表,用于模糊匹配
question_list = [kb["question"] for kb in kb_data]
# 模糊匹配(获取最高相似度的问题)
match_question, match_score = process.extractOne(user_question, question_list, scorer=fuzz.ratio)
# 低于阈值,认为未匹配到
if match_score< MATCH_THRESHOLD:
    return {"match_score": match_score, "answer": "未匹配到相关问题,可尝试更换提问方式~"}

# 找到匹配的知识库数据
match_kb = [kb for kb in kb_data if kb["question"] == match_question][0]
# 增加点击次数
kb = CompanyKnowledge.query.get(match_kb["id"])
kb.click_count += 1
db.session.commit()

return {
    "id": match_kb["id"],
    "question": match_kb["question"],
    "answer": match_kb["answer"],
    "match_score": match_score
}

====================== 核心功能:记录问答统计 ======================

def record_qa_stat(knowledge_id, user_question, match_score, is_satisfied=None):

"""记录问答统计数据"""

qa_stat = QaStatistics(

knowledge_id=knowledge_id,

user_id=current_user.id,

question=user_question[:500],

match_score=match_score,

is_satisfied=is_satisfied

)

db.session.add(qa_stat)

db.session.commit()

====================== 接口:自动化问答(核心) ======================

@kb_bp.route("/kb/qa", methods=["POST"])

@login_required

@audit_log("query")

def kb_qa():

"""自动化问答接口(文字/语音提问均可调用)"""

data = request.get_json()

user_question = data.get("question", "").strip()

if not user_question:

return jsonify({"success": False, "error": "请输入提问内容~"})

复制代码
# 智能匹配问题
match_result = match_question(user_question)
# 记录问答统计
record_qa_stat(
    knowledge_id=match_result.get("id"),
    user_question=user_question,
    match_score=match_result.get("match_score", 0)
)

return jsonify({
    "success": True,
    "match_score": match_result.get("match_score", 0),
    "answer": match_result["answer"]
})

====================== 接口:知识库管理(新增/编辑/删除) ======================

@kb_bp.route("/kb/add", methods=["POST"])

@login_required

@permission_required("view_all_data") # 仅管理员可维护

@audit_log("add")

def add_kb():

"""新增知识库内容"""

data = request.get_json()

required_fields = ["question", "answer", "question_type"]

for field in required_fields:

if not data.get(field):

return jsonify({"success": False, "error": f"缺少必填字段:{field}"})

复制代码
# 检查问题是否已存在
if CompanyKnowledge.query.filter_by(question=data["question"]).first():
    return jsonify({"success": False, "error": "该问题已存在于知识库~"})

try:
    # 解析角色ID
    role_ids = json.dumps(data.get("role_ids", []))
    kb = CompanyKnowledge(
        question=data["question"],
        answer=data["answer"],
        question_type=data["question_type"],
        role_ids=role_ids,
        is_top=data.get("is_top", 0),
        create_by=current_user.id
    )
    db.session.add(kb)
    db.session.commit()
    # 刷新Redis缓存
    load_kb_to_redis()
    return jsonify({"success": True, "msg": "知识库内容新增成功~", "kb_id": kb.id})
except Exception as e:
    db.session.rollback()
    return jsonify({"success": False, "error": f"新增失败:{str(e)}"})

@kb_bp.route("/kb/delete/int:kb_id", methods=["DELETE"])

@login_required

@permission_required("view_all_data")

@audit_log("delete")

def delete_kb(kb_id):

"""删除知识库内容"""

kb = CompanyKnowledge.query.get(kb_id)

if not kb:

return jsonify({"success": False, "error": "知识库内容不存在~"})

复制代码
try:
    db.session.delete(kb)
    db.session.commit()
    # 刷新Redis缓存
    load_kb_to_redis()
    return jsonify({"success": True, "msg": "知识库内容删除成功~"})
except Exception as e:
    db.session.rollback()
    return jsonify({"success": False, "error": f"删除失败:{str(e)}"})

====================== 接口:知识库批量导入(Excel) ======================

@kb_bp.route("/kb/import", methods=["POST"])

@login_required

@permission_required("view_all_data")

@audit_log("add")

def import_kb():

"""批量导入知识库(Excel,列:question/answer/question_type/role_ids/is_top)"""

if "file" not in request.files:

return jsonify({"success": False, "error": "未上传Excel文件~"})

复制代码
file = request.files["file"]
if not file.filename.endswith((".xlsx", ".xls")):
    return jsonify({"success": False, "error": "仅支持Excel文件导入~"})

try:
    # 读取Excel文件
    df = pd.read_excel(file)
    # 检查列是否存在
    required_cols = ["question", "answer", "question_type"]
    if not all(col in df.columns for col in required_cols):
        return jsonify({"success": False, "error": f"Excel文件需包含列:{required_cols.join(',')}~"})
    
    # 批量导入
    for _, row in df.iterrows():
        if not row["question"] or not row["answer"]:
            continue
        # 跳过已存在的问题
        if CompanyKnowledge.query.filter_by(question=str(row["question"])).first():
            continue
        role_ids = json.dumps(eval(str(row.get("role_ids", "[]"))))
        kb = CompanyKnowledge(
            question=str(row["question"]),
            answer=str(row["answer"]),
            question_type=str(row["question_type"]),
            role_ids=role_ids,
            is_top=int(row.get("is_top", 0)),
            create_by=current_user.id
        )
        db.session.add(kb)
    
    db.session.commit()
    # 刷新Redis缓存
    load_kb_to_redis()
    return jsonify({"success": True, "msg": f"批量导入成功,共导入{len(df)}条~"})
except Exception as e:
    db.session.rollback()
    return jsonify({"success": False, "error": f"批量导入失败:{str(e)}"})

====================== 接口:知识库批量导出(Excel) ======================

@kb_bp.route("/kb/export", methods=["GET"])

@login_required

@permission_required("view_all_data")

@audit_log("export")

def export_kb():

"""批量导出知识库为Excel"""

kbs = CompanyKnowledge.query.all()

if not kbs:

return jsonify({"success": False, "error": "暂无知识库数据~"})

复制代码
# 构建导出数据
export_data = []
for kb in kbs:
    export_data.append({
        "id": kb.id,
        "question": kb.question,
        "answer": kb.answer,
        "question_type": kb.question_type,
        "role_ids": kb.role_ids,
        "is_top": kb.is_top,
        "click_count": kb.click_count,
        "create_time": kb.create_time.strftime("%Y-%m-%d %H:%M:%S")
    })

# 生成Excel
df = pd.DataFrame(export_data)
temp_path = f"/tmp/company_knowledge_{int(time.time())}.xlsx"
df.to_excel(temp_path, index=False)

# 返回文件
return send_file(
    temp_path,
    as_attachment=True,
    download_name=f"企业知识库_{pd.Timestamp.now().strftime('%Y%m%d')}.xlsx",
    mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)

====================== 接口:问答统计查询 ======================

@kb_bp.route("/kb/qa/stat", methods=["GET"])

@login_required

@permission_required("view_all_data")

@audit_log("query")

def get_qa_stat():

"""查询问答统计数据(仅管理员可看)"""

page = int(request.args.get("page", 1))

page_size = int(request.args.get("page_size", 20))

start_time = request.args.get("start_time")

end_time = request.args.get("end_time")

复制代码
query = QaStatistics.query.join(
    CompanyKnowledge, QaStatistics.knowledge_id == CompanyKnowledge.id, isouter=True
).add_columns(
    CompanyKnowledge.question, CompanyKnowledge.answer
).order_by(QaStatistics.ask_time.desc())

# 时间筛选
if start_time:
    query = query.filter(QaStatistics.ask_time >= start_time)
if end_time:
    query = query.filter(QaStatistics.ask_time <= end_time)

pagination = query.paginate(page=page, per_page=page_size)
stats = pagination.items

stat_list = []
for stat, kb_question, kb_answer in stats:
    stat_list.append({
        "stat_id": stat.id,
        "user_id": stat.user_id,
        "username": stat.user.username if stat.user else "",
        "user_question": stat.question,
        "kb_question": kb_question or "未匹配到",
        "kb_answer": kb_answer or "",
        "match_score": stat.match_score,
        "is_satisfied": stat.is_satisfied if stat.is_satisfied is not None else "未评价",
        "ask_time": stat.ask_time.strftime("%Y-%m-%d %H:%M:%S")
    })

return jsonify({
    "success": True,
    "data": stat_list,
    "total": pagination.total,
    "page": page,
    "page_size": page_size
})

在app.py中新增以下内容

from knowledge_base import kb_bp

from voice_interact import voice_bp

注册语音交互、知识库蓝图

app.register_blueprint(voice_bp, url_prefix="/api")

app.register_blueprint(kb_bp, url_prefix="/api/kb")

应用启动时加载知识库到Redis

with app.app_context():

from knowledge_base import load_kb_to_redis

load_kb_to_redis()

print("企业知识库已加载到Redis,自动化问答功能已就绪~")

四、功能联调与优化建议

  1. 功能联调

  2. 新手优化建议

五、总结

相关推荐
真智AI2 小时前
用 FAISS 搭个轻量 RAG 问答(Python)
开发语言·python·faiss
2401_857683542 小时前
使用Kivy开发跨平台的移动应用
jvm·数据库·python
咩咩不吃草2 小时前
【HTML】核心标签与【Python爬虫库】实战指南
css·爬虫·python·html
serve the people2 小时前
python环境搭建 (七) pytest、pytest-asyncio、pytest-cov 试生态的核心组合
开发语言·python·pytest
java1234_小锋2 小时前
分享一套不错的基于Python的Django宠物信息管理系统
开发语言·python·宠物
2401_841495642 小时前
【Web开发】基于Flask搭建简单的应用网站
后端·python·flask·视图函数·应用实例·路由装饰器·调试模式
木卫二号Coding2 小时前
第七十七篇-V100+llama-cpp-python-server+Qwen3-30B+GGUF
开发语言·python·llama
木卫二号Coding2 小时前
第七十六篇-V100+llama-cpp-python+Qwen3-30B+GGUF
开发语言·python·llama
-To be number.wan2 小时前
为什么 pyecharts 在 Jupyter Notebook 里显示空白?
ide·python·jupyter·数据分析