问题背景
在一次AI回答采集项目中,需要每周生成一份报告,汇总模型响应时间、回答长度、评分等指标。手动从数据库导出数据、用Excel做图表、再排版,每次耗时约2小时,且容易遗漏指标或格式不一致。因此需要一套自动化报告生成系统,能够从数据库读取数据、聚合指标、生成图表并输出HTML报告。
环境说明
- Python 3.9+
- Jinja2 模板引擎
- Matplotlib 用于图表生成
- Pandas 用于数据聚合
- 数据存储在PostgreSQL中(以下为通用实现思路,具体表结构和字段请根据实际项目调整)
整体流程
报告生成分为四个阶段:
- 数据读取:从数据库查询原始采集记录。
- 指标聚合:按报告周期(日/周/月)计算平均响应时间、回答长度、评分等。
- 模板渲染:使用Jinja2将聚合结果和图表嵌入HTML模板。
- 输出归档:生成HTML文件并保存。
选择先聚合再渲染的原因是:聚合后的数据量小,模板渲染更快;不同周期(如日、周)对聚合的影响主要体现在SQL的GROUP BY粒度上,周报需要按周聚合,日报按天。
报告模板设计
使用Jinja2模板引擎,将报告结构抽象为模板,支持动态插入图表、表格和文本。模板分为三部分:
- 头部:报告标题、时间范围、生成时间
- 主体:指标卡片、趋势图、对比表格
- 尾部:备注、数据来源说明
示例模板片段(report_template.html):
html
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>
<h1>{{ title }}</h1>
<p>时间范围:{{ start_date }} ~ {{ end_date }}</p>
<p>生成时间:{{ generated_at }}</p>
<h2>核心指标</h2>
<div class="cards">
{% for metric in metrics %}
<div class="card">
<h3>{{ metric.name }}</h3>
<p>{{ metric.value }}</p>
</div>
{% endfor %}
</div>
<h2>趋势图</h2>
<img src="{{ trend_chart_path }}" alt="趋势图">
<h2>详细数据</h2>
<table>
<tr><th>日期</th><th>指标A</th><th>指标B</th></tr>
{% for row in detail_rows %}
<tr><td>{{ row.date }}</td><td>{{ row.metric_a }}</td><td>{{ row.metric_b }}</td></tr>
{% endfor %}
</table>
</body>
</html>
数据聚合
从数据库查询原始数据,使用Pandas进行聚合。以下代码为示例,实际使用时需替换数据库连接信息和表名。
python
import pandas as pd
from sqlalchemy import create_engine
def aggregate_data(start_date, end_date):
# 请替换为实际数据库连接信息
engine = create_engine('postgresql://your_user:your_password@your_host/your_db')
query = f"""
SELECT
DATE(created_at) as date,
AVG(response_time_ms) as avg_response_time,
AVG(LENGTH(answer_text)) as avg_answer_length,
AVG(score) as avg_score
FROM ai_responses
WHERE created_at >= '{start_date}' AND created_at < '{end_date}'
GROUP BY DATE(created_at)
ORDER BY date
"""
df = pd.read_sql(query, engine)
return df
可视化图表生成
使用Matplotlib生成趋势图,保存为图片嵌入报告。注意Matplotlib默认字体不支持中文,需指定中文字体。
python
import matplotlib.pyplot as plt
def generate_trend_chart(df, metric_col, output_path):
plt.figure(figsize=(10, 4))
plt.plot(df['date'], df[metric_col], marker='o')
plt.title(f'{metric_col} 趋势')
plt.xlabel('日期')
plt.ylabel(metric_col)
plt.grid(True)
plt.tight_layout()
plt.savefig(output_path)
plt.close()
报告生成主流程
python
from jinja2 import Environment, FileSystemLoader
import datetime
def generate_report(start_date, end_date):
# 1. 聚合数据
df = aggregate_data(start_date, end_date)
# 2. 计算核心指标
metrics = [
{'name': '平均响应时间(ms)', 'value': round(df['avg_response_time'].mean(), 2)},
{'name': '平均回答长度(字符)', 'value': round(df['avg_answer_length'].mean(), 2)},
{'name': '平均评分', 'value': round(df['avg_score'].mean(), 2)},
]
# 3. 生成趋势图
chart_path = '/tmp/trend.png'
generate_trend_chart(df, 'avg_response_time', chart_path)
# 4. 渲染模板
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('report_template.html')
html_content = template.render(
title='AI回答采集周报',
start_date=start_date,
end_date=end_date,
generated_at=datetime.datetime.now().strftime('%Y-%m-%d %H:%M'),
metrics=metrics,
trend_chart_path=chart_path,
detail_rows=df.to_dict('records')
)
# 5. 输出HTML文件
output_path = f'report_{start_date}_{end_date}.html'
with open(output_path, 'w') as f:
f.write(html_content)
return output_path
支持自定义指标
通过配置文件定义指标名称、SQL聚合表达式和单位,系统动态加载。
yaml
# metrics_config.yaml
metrics:
- name: 平均响应时间
sql: AVG(response_time_ms)
unit: ms
- name: 最大响应时间
sql: MAX(response_time_ms)
unit: ms
- name: 回答接受率
sql: SUM(CASE WHEN accepted=1 THEN 1 ELSE 0 END) * 1.0 / COUNT(*)
unit: '%'
代码中读取配置并动态生成SQL和图表。
验证结果
正常情况下,运行脚本后会生成一个HTML文件,打开可看到:
- 报告标题和时间范围
- 核心指标卡片(数值正确)
- 趋势图(折线图显示日期和指标值)
- 数据表格(与数据库查询结果一致)
常见问题与避坑
- 图表中文乱码 :Matplotlib默认字体不支持中文,需指定中文字体,如
plt.rcParams['font.sans-serif'] = ['SimHei']。 - 模板路径问题:Jinja2的FileSystemLoader需要正确设置模板目录,建议使用绝对路径或项目相对路径。
- 大数据量性能:如果数据量过大,聚合查询可能慢,可考虑预聚合表或使用数据库物化视图。
- 图表保存路径:生成图表时确保输出目录存在,否则保存失败。
总结
本文提供了一个基于Python的AI回答采集报告自动生成方案,核心思路是:用Pandas聚合数据,用Matplotlib生成图表,用Jinja2渲染HTML模板。方案支持通过YAML配置文件自定义指标,易于扩展。当前限制:仅输出HTML格式,如需PDF可借助wkhtmltopdf或WeasyPrint转换。适用于需要定期输出采集评估结果的场景,但具体数据库表结构和字段需根据实际项目调整。