Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
沈秋雨
/
weknora_ragas
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit
7ba212d1
...
7ba212d111e8d255f108a791b618391ba08a8ecc
authored
2026-04-23 09:47:44 +0800
by
沈秋雨
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
bugfix
1 parent
3cd77d9d
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
61 additions
and
2 deletions
README.md
TESTING_GUIDE.md
src/weknora_eval/report.py
README.md
View file @
7ba212d
...
...
@@ -43,6 +43,16 @@ cp .env.example .env
-
`RAGAS_HTTP_KEEPALIVE=false`
会让 RAGAS LLM 请求使用短连接,规避长流程中 async HTTP 连接清理问题
-
`RAGAS_TESTSET_TRANSFORMS=single_hop_entities`
会让
`prechunked`
只运行单跳 QA 所需的实体抽取 transform;设为
`default`
可回到 Ragas 默认 prechunked transforms
### 测试集生成模式
`TESTSET_RAGAS_MODE`
控制
`05_generate_testset.py`
如何用 Ragas 生成 QA:
-
`direct`
:工程兜底模式。脚本直接把 WeKnora chunks 构造成单跳场景,再调用 Ragas 的 single-hop sample generator 生成 QA。它跳过 Ragas 的文档 transform 和 scenario generation,最稳,但不是完整的 Ragas testset generation 流程。
-
`prechunked`
:Ragas 面向“已经切好 chunk”的标准入口,对应
`generate_with_chunks()`
。这里的 pre-chunked 指输入已经是 WeKnora 导出的 chunk,不需要 Ragas 再从原始文档切分。当前配置默认只启用
`single_hop_entities`
transform,并限制为 single-hop QA,避免不相关 chunk 被组合成 multi-hop 问题。
-
`langchain_docs`
:Ragas 面向 LangChain Document 的原始文档入口,对应
`generate_with_langchain_docs()`
。它可能触发标题、摘要、拆分等默认文档预处理,不适合作为本项目主路径,只保留给对比实验。
当前推荐先用
`direct`
跑通评测闭环;需要验证 Ragas 标准 pre-chunked 生成链路时,再切换到
`prechunked`
。
## 首轮 Pilot
把原始文件放到
`data/raw_docs/`
,脚本会按扩展名自动识别 PDF 和 XLSX。也兼容旧目录:
...
...
TESTING_GUIDE.md
View file @
7ba212d
...
...
@@ -290,7 +290,13 @@ RAGAS_HTTP_KEEPALIVE=false
RAGAS_TESTSET_TRANSFORMS
=
single_hop_entities
```
`direct`
模式会跳过 Ragas 默认的
`HeadlinesExtractor`
、
`SummaryExtractor`
、
`NERExtractor`
文档预处理链路,直接把 WeKnora chunks 组装成 Ragas KnowledgeGraph 并生成单跳 QA。
`prechunked`
和
`langchain_docs`
仅用于对比实验,遇到本地 vLLM 结构化输出不稳定时不建议使用。
`TESTSET_RAGAS_MODE`
支持三种模式:
-
`direct`
:工程兜底模式。脚本直接把 WeKnora chunks 构造成单跳场景,再调用 Ragas 的 single-hop sample generator 生成 QA。它跳过 Ragas 默认的
`HeadlinesExtractor`
、
`SummaryExtractor`
、
`NERExtractor`
、scenario generation 等中间步骤,最稳,但不是完整的 Ragas testset generation 流程。
-
`prechunked`
:Ragas 面向“已经切好 chunk”的标准入口,对应
`generate_with_chunks()`
。这里的 pre-chunked 指输入已经是 WeKnora 导出的 chunk,不需要 Ragas 再从原始文档切分。当前建议配合
`RAGAS_TESTSET_TRANSFORMS=single_hop_entities`
,只做单跳 QA 所需的实体抽取,并限制为 single-hop,避免多个不相关 chunk 被组合成 multi-hop 问题。
-
`langchain_docs`
:Ragas 面向 LangChain Document 的原始文档入口,对应
`generate_with_langchain_docs()`
。它可能触发标题、摘要、拆分等默认文档预处理,不适合作为本项目主路径,只保留给对比实验。
当前推荐先用
`direct`
跑通评测闭环;需要验证 Ragas 标准 pre-chunked 生成链路时,再切换到
`prechunked`
。
如果使用 Qwen thinking 模型,
`RAGAS_ENABLE_THINKING=false`
会只在 RAGAS 请求里附加
`chat_template_kwargs.enable_thinking=false`
,避免 RAGAS 的 JSON/Pydantic 结构化输出被
`Thinking Process`
前缀破坏;WeKnora 本身的检索问答链路不经过这些脚本,不会受影响。
...
...
src/weknora_eval/report.py
View file @
7ba212d
...
...
@@ -26,7 +26,12 @@ def retrieval_metrics(
for
row
in
samples
:
gold
=
set
(
row
.
get
(
"gold_chunk_ids"
)
or
[])
refs
=
row
.
get
(
"weknora_references"
)
or
[]
predicted
=
[
str
(
ref
.
get
(
"id"
))
for
ref
in
refs
if
ref
.
get
(
"id"
)]
predicted
=
[
chunk_id
for
ref
in
refs
for
chunk_id
in
[
_reference_chunk_id
(
ref
)]
if
chunk_id
]
for
k
in
ks
:
top_k
=
predicted
[:
k
]
hits
=
len
(
gold
.
intersection
(
top_k
))
...
...
@@ -53,10 +58,13 @@ def generate_summary_report(
*
,
scores_csv_path
:
str
=
"data/reports/ragas_scores.csv"
,
ragas_input_path
:
str
=
"data/runs/ragas_input.jsonl"
,
testset_path
:
str
=
"data/testsets/testset.reviewed.jsonl"
,
answers_path
:
str
=
"data/runs/weknora_answers.jsonl"
,
output_path
:
str
=
"data/reports/summary.md"
,
)
->
str
:
ragas_rows
=
read_jsonl
(
ragas_input_path
,
missing_ok
=
True
)
reviewed_rows
=
read_jsonl
(
testset_path
,
missing_ok
=
True
)
ragas_rows
=
_backfill_gold_chunks
(
ragas_rows
,
reviewed_rows
)
answer_rows
=
read_jsonl
(
answers_path
,
missing_ok
=
True
)
scores
=
pd
.
read_csv
(
scores_csv_path
)
if
Path
(
scores_csv_path
)
.
exists
()
else
pd
.
DataFrame
()
...
...
@@ -155,6 +163,41 @@ def _worst_rows(scores: pd.DataFrame, column: str, *, limit: int = 10) -> list[d
return
scores
.
sort_values
(
metric_column
,
ascending
=
True
)
.
head
(
limit
)
.
to_dict
(
orient
=
"records"
)
def
_backfill_gold_chunks
(
ragas_rows
:
list
[
dict
[
str
,
Any
]],
reviewed_rows
:
list
[
dict
[
str
,
Any
]],
)
->
list
[
dict
[
str
,
Any
]]:
reviewed_by_id
=
{
row
.
get
(
"sample_id"
):
row
for
row
in
reviewed_rows
if
row
.
get
(
"sample_id"
)
}
result
:
list
[
dict
[
str
,
Any
]]
=
[]
for
row
in
ragas_rows
:
if
row
.
get
(
"gold_chunk_ids"
):
result
.
append
(
row
)
continue
reviewed
=
reviewed_by_id
.
get
(
row
.
get
(
"sample_id"
))
or
{}
if
reviewed
.
get
(
"gold_chunk_ids"
):
row
=
{
**
row
,
"gold_chunk_ids"
:
reviewed
.
get
(
"gold_chunk_ids"
)}
result
.
append
(
row
)
return
result
def
_reference_chunk_id
(
reference
:
dict
[
str
,
Any
])
->
str
|
None
:
for
key
in
(
"id"
,
"chunk_id"
,
"chunkId"
):
value
=
reference
.
get
(
key
)
if
value
:
return
str
(
value
)
raw
=
reference
.
get
(
"raw"
)
if
isinstance
(
raw
,
dict
):
for
key
in
(
"id"
,
"chunk_id"
,
"chunkId"
):
value
=
raw
.
get
(
key
)
if
value
:
return
str
(
value
)
return
None
def
_metric_column
(
scores
:
pd
.
DataFrame
,
name
:
str
)
->
str
|
None
:
if
name
in
scores
.
columns
:
return
name
...
...
Please
register
or
sign in
to post a comment