Gbone3176 commited on
Commit
9a069e7
·
verified ·
1 Parent(s): 4f046a5

Add complete Tr006 V2 Segment pipeline bundle

Browse files

Raw video to blue intervals, fixed80 V1, blue-clean V2 train input, exact ms-swift source, training, checkpoint merge, Docker inference, and local evaluation snapshots.

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. pipelines/Tr006_V2/README.md +129 -0
  2. pipelines/Tr006_V2/annotations/blue_screen/data/intervals.csv +324 -0
  3. pipelines/Tr006_V2/annotations/blue_screen/metadata/provenance.json +43 -0
  4. pipelines/Tr006_V2/annotations/blue_screen/metadata/summary.json +23 -0
  5. pipelines/Tr006_V2/annotations/blue_screen/metadata/validation.json +30 -0
  6. pipelines/Tr006_V2/annotations/blue_screen/metadata/videos.jsonl +0 -0
  7. pipelines/Tr006_V2/docker/segment-algorithm/.dockerignore +6 -0
  8. pipelines/Tr006_V2/docker/segment-algorithm/.gitignore +2 -0
  9. pipelines/Tr006_V2/docker/segment-algorithm/Dockerfile +102 -0
  10. pipelines/Tr006_V2/docker/segment-algorithm/do_build.sh +11 -0
  11. pipelines/Tr006_V2/docker/segment-algorithm/do_save.sh +50 -0
  12. pipelines/Tr006_V2/docker/segment-algorithm/do_test_run.sh +86 -0
  13. pipelines/Tr006_V2/docker/segment-algorithm/inference.py +393 -0
  14. pipelines/Tr006_V2/docker/segment-algorithm/requirements.txt +21 -0
  15. pipelines/Tr006_V2/docker/segment-algorithm/resources/README.md +43 -0
  16. pipelines/Tr006_V2/docker/segment-algorithm/resources/dummy_weights.pt +3 -0
  17. pipelines/Tr006_V2/docker/segment-algorithm/resources/model.py +20 -0
  18. pipelines/Tr006_V2/docker/segment-algorithm/resources/model_provenance.json +17 -0
  19. pipelines/Tr006_V2/docker/segment-algorithm/test/.gitignore +1 -0
  20. pipelines/Tr006_V2/expected_outputs/v1/provenance.json +59 -0
  21. pipelines/Tr006_V2/expected_outputs/v1/validation_report.json +11 -0
  22. pipelines/Tr006_V2/expected_outputs/v2/provenance.json +27 -0
  23. pipelines/Tr006_V2/expected_outputs/v2/validation_report.json +24 -0
  24. pipelines/Tr006_V2/manifest/pipeline_manifest.json +60 -0
  25. pipelines/Tr006_V2/manifest/pipeline_requirements.txt +15 -0
  26. pipelines/Tr006_V2/project/data_preprocessing/README.md +92 -0
  27. pipelines/Tr006_V2/project/data_preprocessing/process_blue_screen_dataset.py +294 -0
  28. pipelines/Tr006_V2/project/ms-swift/LICENSE +201 -0
  29. pipelines/Tr006_V2/project/ms-swift/MANIFEST.in +1 -0
  30. pipelines/Tr006_V2/project/ms-swift/requirements.txt +1 -0
  31. pipelines/Tr006_V2/project/ms-swift/requirements/framework.txt +39 -0
  32. pipelines/Tr006_V2/project/ms-swift/requirements/swanlab.txt +1 -0
  33. pipelines/Tr006_V2/project/ms-swift/setup.cfg +32 -0
  34. pipelines/Tr006_V2/project/ms-swift/setup.py +166 -0
  35. pipelines/Tr006_V2/project/ms-swift/swift/__init__.py +66 -0
  36. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/__init__.py +3 -0
  37. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/base.py +261 -0
  38. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/deepseek_v3_1.py +87 -0
  39. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/deepseek_v4.py +145 -0
  40. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/extra.py +36 -0
  41. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/gemma4.py +210 -0
  42. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/glm4.py +194 -0
  43. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/hermes.py +144 -0
  44. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/hy_v3.py +107 -0
  45. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/kimi_k25.py +119 -0
  46. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/kimi_k3.py +151 -0
  47. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/llama.py +74 -0
  48. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/mapping.py +63 -0
  49. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/minicpm5.py +114 -0
  50. pipelines/Tr006_V2/project/ms-swift/swift/agent_template/minimax_m2.py +171 -0
pipelines/Tr006_V2/README.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Tr006 V2 Segment pipeline bundle
2
+
3
+ 这个目录对应 Hugging Face 仓库 `Gbone3176/interval` 中的
4
+ `pipelines/Tr006_V2/`。权重位于同仓库的
5
+ `weights/Tr006_V2/checkpoint-6013/`。
6
+
7
+ ## 范围
8
+
9
+ 包含 Tr006 V2 实际使用的完整处理链:
10
+
11
+ 1. 原始 HeiCo 视频生成冻结蓝屏区间。
12
+ 2. 官方原始 Segment parquet 和完整视频生成 V1 fixed80 数据。
13
+ 3. 使用冻结蓝屏区间生成 V2 blue-clean fixed80 数据及 ms-swift `train.jsonl`。
14
+ 4. ms-swift 绝对时间戳模板、训练入口和实际 Tr006 配置。
15
+ 5. checkpoint-6013 LoRA merge。
16
+ 6. Segment Docker 的预处理、推理及答案后处理。
17
+ 7. 本地 vLLM 评测和时间容差复算脚本,仅用于本地核对;它们不是官方 evaluator。
18
+
19
+ 不包含官方原始视频、官方 parquet、base model 或合并后的模型。Farm04 必须使用与原服务器相同版本的官方原始数据和同一个 Qwen3.5-9B base model。
20
+
21
+ ## 原始数据目录契约
22
+
23
+ ```text
24
+ RAW_ROOT/
25
+ ├── heico-focus-vqa/
26
+ │ ├── data/segment/{train,test}.parquet
27
+ │ └── videos/*
28
+ └── lapchole-focus-vqa/
29
+ ├── data/segment/{train,test}.parquet
30
+ └── videos/*
31
+ ```
32
+
33
+ 先用 `manifest/pipeline_manifest.json` 中的 parquet SHA256 核对版本。
34
+
35
+ ## 从完全原始数据生成训练输入
36
+
37
+ ### 1. 可重复生成蓝屏区间
38
+
39
+ ```bash
40
+ python project/data_preprocessing/process_blue_screen_dataset.py \
41
+ --input-dir "$RAW_ROOT/heico-focus-vqa/videos" \
42
+ --output-dir "$WORK_ROOT/blue_screen_detected" \
43
+ --sample-seconds 1 \
44
+ --min-static-seconds 6 \
45
+ --blue-ratio 0.45 \
46
+ --pad-seconds 5 \
47
+ --coarse-size 96 \
48
+ --fine-size 160
49
+ ```
50
+
51
+ Tr006 的精确复现应使用 bundle 内冻结的
52
+ `annotations/blue_screen/data/intervals.csv`。重新检测用于验证原始到标注的完整链路;只有生成文件的 SHA256 与 manifest 一致时,才可替代冻结文件。
53
+
54
+ ### 2. 原始数据生成 V1 fixed80
55
+
56
+ ```bash
57
+ python project/scripts/preprocess_segmentqa_fixed_frames.py \
58
+ --data-root "$RAW_ROOT" \
59
+ --output-dir "$WORK_ROOT/orena_segmentqa_fixed80_420x224_absmeta_v1/clean" \
60
+ --splits train test \
61
+ --frames 80 \
62
+ --width 420 \
63
+ --height 224 \
64
+ --workers 8
65
+ ```
66
+
67
+ ### 3. V1 生成 Tr006 V2 训练数据
68
+
69
+ ```bash
70
+ python project/scripts/preprocess_segmentqa_blueclean_v2.py \
71
+ --data-root "$RAW_ROOT" \
72
+ --v1-dir "$WORK_ROOT/orena_segmentqa_fixed80_420x224_absmeta_v1/clean" \
73
+ --blue-intervals "$BUNDLE_ROOT/annotations/blue_screen/data/intervals.csv" \
74
+ --output-dir "$WORK_ROOT/orena_segmentqa_fixed80_420x224_absmeta_v2/clean" \
75
+ --splits train test \
76
+ --frames 80 \
77
+ --width 420 \
78
+ --height 224 \
79
+ --workers 8 \
80
+ --min-train-valid-frames 16
81
+ ```
82
+
83
+ 验收值:18,478 个 segment、1,215 个蓝屏重叠 segment、13,746 条训练问题、6,254 条测试问题、80 帧、420x224、绝对毫秒 PTS。最终训练入口是 V2 目录中的 `clean/train.jsonl`。
84
+
85
+ `project/` 保留原项目相对布局;其中已经包含 Tr006 实际使用的
86
+ `ms-swift/swift/template/templates/qwen.py`。如果改用另一个 ms-swift checkout,必须把该文件覆盖到 commit
87
+ `b66edaeba3c484c431d785f95a7fc81cffa530c0` 的对应位置,否则预计算绝对时间戳不会进入 Qwen3VL processor。
88
+
89
+ ## Merge checkpoint-6013
90
+
91
+ ```bash
92
+ python project/scripts/merge_lora_model.py \
93
+ --base "$BASE_MODEL" \
94
+ --adapter "$HF_ROOT/weights/Tr006_V2/checkpoint-6013" \
95
+ --output "$OFFICIAL_SEGMENT_DIR/resources/model" \
96
+ --max-shard-size 4GB
97
+ ```
98
+
99
+ 验收 `resources/model/merge_report.json`:
100
+
101
+ - `remaining_lora_modules_after_merge == 0`
102
+ - language model、visual、visual merger 的代表权重 delta 均非零
103
+ - 输出含完整 processor 配置和 safetensors index
104
+
105
+ ## 使用官方目录重新打包
106
+
107
+ 继续使用官方原始 `do_build.sh`、`do_test_run.sh` 和 `do_save.sh`。把 bundle 中以下三个文件覆盖到官方 Segment 目录:
108
+
109
+ ```bash
110
+ cp docker/segment-algorithm/inference.py "$OFFICIAL_SEGMENT_DIR/inference.py"
111
+ cp docker/segment-algorithm/requirements.txt "$OFFICIAL_SEGMENT_DIR/requirements.txt"
112
+ cp docker/segment-algorithm/Dockerfile "$OFFICIAL_SEGMENT_DIR/Dockerfile"
113
+ ```
114
+
115
+ 完成 merge 后执行:
116
+
117
+ ```bash
118
+ cd "$OFFICIAL_SEGMENT_DIR"
119
+ bash do_build.sh
120
+ bash do_test_run.sh
121
+ bash do_save.sh
122
+ ```
123
+
124
+ 不要使用当前线上版本的 `np.linspace` 推理预处理作为“已与 V2 对齐”的证据。必须先在公开样本上比较训练 V2 的采样 PTS、420x224 帧、processor 输入和 Docker 输入;`docker/segment-algorithm/inference.py` 是已提交版本的事实快照,保留它是为了复现和定位差异。
125
+
126
+ ## 本地评测边界
127
+
128
+ `project/scripts/eval_segmentqa_vllm.py` 和
129
+ `project/scripts/evaluate_segmentqa_predictions.py` 使用本地 `correct()` 和时间容差代理,不是官方十桶 ID/OOD evaluator,也没有运行官方 LLM judge。禁止把其分数直接当成 leaderboard score。
pipelines/Tr006_V2/annotations/blue_screen/data/intervals.csv ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ video_id,video_filename,interval_index,start_frame,end_frame,start_seconds,end_seconds,duration_seconds,start_time,end_time,frame_count,mean_blue_ratio,confidence,exact_repeated_hash
2
+ 0000,0000 - Heico - Prokto - 1.avi,1,5524,5938,220.96,237.56,16.6,00:03:40.960,00:03:57.560,415,1.0,high,00c9758b75e5aaadf2d346b15f90db76
3
+ 0000,0000 - Heico - Prokto - 1.avi,2,9296,9548,371.84,381.96,10.12,00:06:11.840,00:06:21.960,253,1.0,high,00c9758b75e5aaadf2d346b15f90db76
4
+ 0000,0000 - Heico - Prokto - 1.avi,3,10249,10494,409.96,419.8,9.84,00:06:49.960,00:06:59.800,246,1.0,high,00c9758b75e5aaadf2d346b15f90db76
5
+ 0000,0000 - Heico - Prokto - 1.avi,4,105966,106309,4238.64,4252.4,13.76,01:10:38.640,01:10:52.400,344,1.0,high,00c9758b75e5aaadf2d346b15f90db76
6
+ 0000,0000 - Heico - Prokto - 1.avi,5,153266,153973,6130.64,6158.96,28.32,01:42:10.640,01:42:38.960,708,1.0,high,00c9758b75e5aaadf2d346b15f90db76
7
+ 0000,0000 - Heico - Prokto - 1.avi,6,216719,217948,8668.76,8717.96,49.2,02:24:28.760,02:25:17.960,1230,1.0,high,00c9758b75e5aaadf2d346b15f90db76
8
+ 0000,0000 - Heico - Prokto - 1.avi,7,232574,311655,9302.96,12466.24,3163.28,02:35:02.960,03:27:46.240,79082,1.0,high,00c9758b75e5aaadf2d346b15f90db76
9
+ 0001,0001 - Heico - Prokto - 2.avi,1,110844,111882,4433.76,4475.32,41.56,01:13:53.760,01:14:35.320,1039,1.0,high,00c9758b75e5aaadf2d346b15f90db76
10
+ 0001,0001 - Heico - Prokto - 2.avi,2,112792,113563,4511.68,4542.56,30.88,01:15:11.680,01:15:42.560,772,1.0,high,00c9758b75e5aaadf2d346b15f90db76
11
+ 0001,0001 - Heico - Prokto - 2.avi,3,114462,115183,4578.48,4607.36,28.88,01:16:18.480,01:16:47.360,722,1.0,high,00c9758b75e5aaadf2d346b15f90db76
12
+ 0001,0001 - Heico - Prokto - 2.avi,4,137203,138843,5488.12,5553.76,65.64,01:31:28.120,01:32:33.760,1641,1.0,high,00c9758b75e5aaadf2d346b15f90db76
13
+ 0001,0001 - Heico - Prokto - 2.avi,5,147830,148183,5913.2,5927.36,14.16,01:38:33.200,01:38:47.360,354,1.0,high,00c9758b75e5aaadf2d346b15f90db76
14
+ 0001,0001 - Heico - Prokto - 2.avi,6,154539,155178,6181.56,6207.16,25.6,01:43:01.560,01:43:27.160,640,1.0,high,00c9758b75e5aaadf2d346b15f90db76
15
+ 0001,0001 - Heico - Prokto - 2.avi,7,222864,223263,8914.56,8930.56,16.0,02:28:34.560,02:28:50.560,400,1.0,high,00c9758b75e5aaadf2d346b15f90db76
16
+ 0001,0001 - Heico - Prokto - 2.avi,8,237013,316761,9480.52,12670.48,3189.96,02:38:00.520,03:31:10.480,79749,1.0,high,00c9758b75e5aaadf2d346b15f90db76
17
+ 0002,0002 - Heico - Prokto - 3.avi,1,5009,5212,200.36,208.52,8.16,00:03:20.360,00:03:28.520,204,1.0,high,00c9758b75e5aaadf2d346b15f90db76
18
+ 0002,0002 - Heico - Prokto - 3.avi,2,5326,5704,213.04,228.2,15.16,00:03:33.040,00:03:48.200,379,1.0,high,00c9758b75e5aaadf2d346b15f90db76
19
+ 0002,0002 - Heico - Prokto - 3.avi,3,26052,27664,1042.08,1106.6,64.52,00:17:22.080,00:18:26.600,1613,1.0,high,00c9758b75e5aaadf2d346b15f90db76
20
+ 0002,0002 - Heico - Prokto - 3.avi,4,115654,115924,4626.16,4637.0,10.84,01:17:06.160,01:17:17.000,271,1.0,high,00c9758b75e5aaadf2d346b15f90db76
21
+ 0002,0002 - Heico - Prokto - 3.avi,5,136596,136848,5463.84,5473.96,10.12,01:31:03.840,01:31:13.960,253,1.0,high,00c9758b75e5aaadf2d346b15f90db76
22
+ 0002,0002 - Heico - Prokto - 3.avi,6,144722,144905,5788.88,5796.24,7.36,01:36:28.880,01:36:36.240,184,1.0,high,00c9758b75e5aaadf2d346b15f90db76
23
+ 0002,0002 - Heico - Prokto - 3.avi,7,159926,217858,6397.04,8714.36,2317.32,01:46:37.040,02:25:14.360,57933,1.0,high,00c9758b75e5aaadf2d346b15f90db76
24
+ 0002,0002 - Heico - Prokto - 3.avi,8,227798,229000,9111.92,9160.04,48.12,02:31:51.920,02:32:40.040,1203,1.0,high,00c9758b75e5aaadf2d346b15f90db76
25
+ 0002,0002 - Heico - Prokto - 3.avi,9,248690,251825,9947.6,10073.04,125.44,02:45:47.600,02:47:53.040,3136,1.0,high,00c9758b75e5aaadf2d346b15f90db76
26
+ 0002,0002 - Heico - Prokto - 3.avi,10,252911,254146,10116.44,10165.88,49.44,02:48:36.440,02:49:25.880,1236,1.0,high,00c9758b75e5aaadf2d346b15f90db76
27
+ 0002,0002 - Heico - Prokto - 3.avi,11,255848,256281,10233.92,10251.28,17.36,02:50:33.920,02:50:51.280,434,1.0,high,00c9758b75e5aaadf2d346b15f90db76
28
+ 0003,0003 - Heico - Prokto - 4.avi,1,643,869,25.72,34.8,9.08,00:00:25.720,00:00:34.800,227,1.0,high,00c9758b75e5aaadf2d346b15f90db76
29
+ 0003,0003 - Heico - Prokto - 4.avi,2,1267,1562,50.68,62.52,11.84,00:00:50.680,00:01:02.520,296,1.0,high,00c9758b75e5aaadf2d346b15f90db76
30
+ 0003,0003 - Heico - Prokto - 4.avi,3,11885,12122,475.4,484.92,9.52,00:07:55.400,00:08:04.920,238,1.0,high,00c9758b75e5aaadf2d346b15f90db76
31
+ 0003,0003 - Heico - Prokto - 4.avi,4,82913,83622,3316.52,3344.92,28.4,00:55:16.520,00:55:44.920,710,1.0,high,00c9758b75e5aaadf2d346b15f90db76
32
+ 0003,0003 - Heico - Prokto - 4.avi,5,127912,128071,5116.48,5122.88,6.4,01:25:16.480,01:25:22.880,160,1.0,high,00c9758b75e5aaadf2d346b15f90db76
33
+ 0003,0003 - Heico - Prokto - 4.avi,6,141561,200111,5662.44,8004.48,2342.04,01:34:22.440,02:13:24.480,58551,1.0,high,00c9758b75e5aaadf2d346b15f90db76
34
+ 0003,0003 - Heico - Prokto - 4.avi,7,205346,206231,8213.84,8249.28,35.44,02:16:53.840,02:17:29.280,886,1.0,high,00c9758b75e5aaadf2d346b15f90db76
35
+ 0003,0003 - Heico - Prokto - 4.avi,8,233424,237433,9336.96,9497.36,160.4,02:35:36.960,02:38:17.360,4010,1.0,high,00c9758b75e5aaadf2d346b15f90db76
36
+ 0003,0003 - Heico - Prokto - 4.avi,9,254178,255182,10167.12,10207.32,40.2,02:49:27.120,02:50:07.320,1005,1.0,high,00c9758b75e5aaadf2d346b15f90db76
37
+ 0003,0003 - Heico - Prokto - 4.avi,10,255278,255922,10211.12,10236.92,25.8,02:50:11.120,02:50:36.920,645,1.0,high,00c9758b75e5aaadf2d346b15f90db76
38
+ 0004,0004 - Heico - Prokto - 5.avi,1,811,1005,32.44,40.24,7.8,00:00:32.440,00:00:40.240,195,1.0,high,00c9758b75e5aaadf2d346b15f90db76
39
+ 0004,0004 - Heico - Prokto - 5.avi,2,2507,2827,100.28,113.12,12.84,00:01:40.280,00:01:53.120,321,1.0,high,00c9758b75e5aaadf2d346b15f90db76
40
+ 0004,0004 - Heico - Prokto - 5.avi,3,2911,3179,116.44,127.2,10.76,00:01:56.440,00:02:07.200,269,1.0,high,00c9758b75e5aaadf2d346b15f90db76
41
+ 0004,0004 - Heico - Prokto - 5.avi,4,5065,5270,202.6,210.84,8.24,00:03:22.600,00:03:30.840,206,1.0,high,00c9758b75e5aaadf2d346b15f90db76
42
+ 0004,0004 - Heico - Prokto - 5.avi,5,5487,5670,219.48,226.84,7.36,00:03:39.480,00:03:46.840,184,1.0,high,00c9758b75e5aaadf2d346b15f90db76
43
+ 0004,0004 - Heico - Prokto - 5.avi,6,153423,153582,6136.92,6143.32,6.4,01:42:16.920,01:42:23.320,160,1.0,high,00c9758b75e5aaadf2d346b15f90db76
44
+ 0004,0004 - Heico - Prokto - 5.avi,7,166711,237718,6668.44,9508.76,2840.32,01:51:08.440,02:38:28.760,71008,1.0,high,00c9758b75e5aaadf2d346b15f90db76
45
+ 0004,0004 - Heico - Prokto - 5.avi,8,274728,274887,10989.12,10995.52,6.4,03:03:09.120,03:03:15.520,160,1.0,high,00c9758b75e5aaadf2d346b15f90db76
46
+ 0004,0004 - Heico - Prokto - 5.avi,9,279748,280329,11189.92,11213.2,23.28,03:06:29.920,03:06:53.200,582,1.0,high,00c9758b75e5aaadf2d346b15f90db76
47
+ 0004,0004 - Heico - Prokto - 5.avi,10,281024,281672,11240.96,11266.92,25.96,03:07:20.960,03:07:46.920,649,1.0,high,00c9758b75e5aaadf2d346b15f90db76
48
+ 0004,0004 - Heico - Prokto - 5.avi,11,295193,298502,11807.72,11940.12,132.4,03:16:47.720,03:19:00.120,3310,1.0,high,00c9758b75e5aaadf2d346b15f90db76
49
+ 0004,0004 - Heico - Prokto - 5.avi,12,299039,299984,11961.56,11999.4,37.84,03:19:21.560,03:19:59.400,946,1.0,high,00c9758b75e5aaadf2d346b15f90db76
50
+ 0004,0004 - Heico - Prokto - 5.avi,13,301545,302528,12061.8,12101.16,39.36,03:21:01.800,03:21:41.160,984,1.0,high,00c9758b75e5aaadf2d346b15f90db76
51
+ 0004,0004 - Heico - Prokto - 5.avi,14,304448,305829,12177.92,12233.2,55.28,03:22:57.920,03:23:53.200,1382,1.0,high,00c9758b75e5aaadf2d346b15f90db76
52
+ 0004,0004 - Heico - Prokto - 5.avi,15,306273,312447,12250.92,12497.92,247.0,03:24:10.920,03:28:17.920,6175,1.0,high,00c9758b75e5aaadf2d346b15f90db76
53
+ 0004,0004 - Heico - Prokto - 5.avi,16,313573,313830,12542.92,12553.24,10.32,03:29:02.920,03:29:13.240,258,1.0,high,00c9758b75e5aaadf2d346b15f90db76
54
+ 0004,0004 - Heico - Prokto - 5.avi,17,315328,315503,12613.12,12620.16,7.04,03:30:13.120,03:30:20.160,176,1.0,high,00c9758b75e5aaadf2d346b15f90db76
55
+ 0005,0005 - Heico - Prokto - 6.avi,1,63,977,2.52,39.12,36.6,00:00:02.520,00:00:39.120,915,1.0,high,00c9758b75e5aaadf2d346b15f90db76
56
+ 0005,0005 - Heico - Prokto - 6.avi,2,2600,2834,104.0,113.4,9.4,00:01:44.000,00:01:53.400,235,1.0,high,00c9758b75e5aaadf2d346b15f90db76
57
+ 0005,0005 - Heico - Prokto - 6.avi,3,88472,88932,3538.88,3557.32,18.44,00:58:58.880,00:59:17.320,461,1.0,high,00c9758b75e5aaadf2d346b15f90db76
58
+ 0005,0005 - Heico - Prokto - 6.avi,4,89225,89747,3569.0,3589.92,20.92,00:59:29.000,00:59:49.920,523,1.0,high,00c9758b75e5aaadf2d346b15f90db76
59
+ 0005,0005 - Heico - Prokto - 6.avi,5,89971,90170,3598.84,3606.84,8.0,00:59:58.840,01:00:06.840,200,1.0,high,00c9758b75e5aaadf2d346b15f90db76
60
+ 0005,0005 - Heico - Prokto - 6.avi,6,117330,166097,4693.2,6643.92,1950.72,01:18:13.200,01:50:43.920,48768,1.0,high,00c9758b75e5aaadf2d346b15f90db76
61
+ 0005,0005 - Heico - Prokto - 6.avi,7,168608,168844,6744.32,6753.8,9.48,01:52:24.320,01:52:33.800,237,1.0,high,00c9758b75e5aaadf2d346b15f90db76
62
+ 0005,0005 - Heico - Prokto - 6.avi,8,184972,185273,7398.88,7410.96,12.08,02:03:18.880,02:03:30.960,302,1.0,high,00c9758b75e5aaadf2d346b15f90db76
63
+ 0005,0005 - Heico - Prokto - 6.avi,9,199269,199539,7970.76,7981.6,10.84,02:12:50.760,02:13:01.600,271,1.0,high,00c9758b75e5aaadf2d346b15f90db76
64
+ 0006,0006 - Heico - Prokto - 7.avi,1,54370,54766,2174.8,2190.68,15.88,00:36:14.800,00:36:30.680,397,1.0,high,00c9758b75e5aaadf2d346b15f90db76
65
+ 0006,0006 - Heico - Prokto - 7.avi,2,68517,68799,2740.68,2752.0,11.32,00:45:40.680,00:45:52.000,283,1.0,high,00c9758b75e5aaadf2d346b15f90db76
66
+ 0006,0006 - Heico - Prokto - 7.avi,3,131005,192543,5240.2,7701.76,2461.56,01:27:20.200,02:08:21.760,61539,1.0,high,00c9758b75e5aaadf2d346b15f90db76
67
+ 0006,0006 - Heico - Prokto - 7.avi,4,192597,192916,7703.88,7716.68,12.8,02:08:23.880,02:08:36.680,320,1.0,high,00c9758b75e5aaadf2d346b15f90db76
68
+ 0006,0006 - Heico - Prokto - 7.avi,5,196217,197106,7848.68,7884.28,35.6,02:10:48.680,02:11:24.280,890,1.0,high,00c9758b75e5aaadf2d346b15f90db76
69
+ 0006,0006 - Heico - Prokto - 7.avi,6,198352,198542,7934.08,7941.72,7.64,02:12:14.080,02:12:21.720,191,1.0,high,00c9758b75e5aaadf2d346b15f90db76
70
+ 0006,0006 - Heico - Prokto - 7.avi,7,204030,204189,8161.2,8167.6,6.4,02:16:01.200,02:16:07.600,160,1.0,high,00c9758b75e5aaadf2d346b15f90db76
71
+ 0006,0006 - Heico - Prokto - 7.avi,8,222489,222898,8899.56,8915.96,16.4,02:28:19.560,02:28:35.960,410,1.0,high,00c9758b75e5aaadf2d346b15f90db76
72
+ 0006,0006 - Heico - Prokto - 7.avi,9,239305,239671,9572.2,9586.88,14.68,02:39:32.200,02:39:46.880,367,1.0,high,00c9758b75e5aaadf2d346b15f90db76
73
+ 0006,0006 - Heico - Prokto - 7.avi,10,241199,241485,9647.96,9659.44,11.48,02:40:47.960,02:40:59.440,287,1.0,high,00c9758b75e5aaadf2d346b15f90db76
74
+ 0006,0006 - Heico - Prokto - 7.avi,11,247032,252252,9881.28,10090.12,208.84,02:44:41.280,02:48:10.120,5221,1.0,high,00c9758b75e5aaadf2d346b15f90db76
75
+ 0007,0007 - Heico - Prokto - 8.avi,1,2119,2344,84.76,93.8,9.04,00:01:24.760,00:01:33.800,226,1.0,high,00c9758b75e5aaadf2d346b15f90db76
76
+ 0007,0007 - Heico - Prokto - 8.avi,2,2520,2930,100.8,117.24,16.44,00:01:40.800,00:01:57.240,411,1.0,high,00c9758b75e5aaadf2d346b15f90db76
77
+ 0007,0007 - Heico - Prokto - 8.avi,3,77279,77433,3091.16,3097.36,6.2,00:51:31.160,00:51:37.360,155,1.0,high,00c9758b75e5aaadf2d346b15f90db76
78
+ 0007,0007 - Heico - Prokto - 8.avi,4,125114,126789,5004.56,5071.6,67.04,01:23:24.560,01:24:31.600,1676,1.0,high,00c9758b75e5aaadf2d346b15f90db76
79
+ 0007,0007 - Heico - Prokto - 8.avi,5,150927,209117,6037.08,8364.72,2327.64,01:40:37.080,02:19:24.720,58191,1.0,high,00c9758b75e5aaadf2d346b15f90db76
80
+ 0007,0007 - Heico - Prokto - 8.avi,6,209372,209595,8374.88,8383.84,8.96,02:19:34.880,02:19:43.840,224,1.0,high,00c9758b75e5aaadf2d346b15f90db76
81
+ 0007,0007 - Heico - Prokto - 8.avi,7,237291,237838,9491.64,9513.56,21.92,02:38:11.640,02:38:33.560,548,1.0,high,00c9758b75e5aaadf2d346b15f90db76
82
+ 0007,0007 - Heico - Prokto - 8.avi,8,238028,238568,9521.12,9542.76,21.64,02:38:41.120,02:39:02.760,541,1.0,high,00c9758b75e5aaadf2d346b15f90db76
83
+ 0007,0007 - Heico - Prokto - 8.avi,9,238736,238982,9549.44,9559.32,9.88,02:39:09.440,02:39:19.320,247,1.0,high,00c9758b75e5aaadf2d346b15f90db76
84
+ 0007,0007 - Heico - Prokto - 8.avi,10,252643,254287,10105.72,10171.52,65.8,02:48:25.720,02:49:31.520,1645,1.0,high,00c9758b75e5aaadf2d346b15f90db76
85
+ 0008,0008 - Heico - Prokto - 9.avi,1,2459,2677,98.36,107.12,8.76,00:01:38.360,00:01:47.120,219,1.0,high,00c9758b75e5aaadf2d346b15f90db76
86
+ 0008,0008 - Heico - Prokto - 9.avi,2,2871,3130,114.84,125.24,10.4,00:01:54.840,00:02:05.240,260,1.0,high,00c9758b75e5aaadf2d346b15f90db76
87
+ 0008,0008 - Heico - Prokto - 9.avi,3,5009,5173,200.36,206.96,6.6,00:03:20.360,00:03:26.960,165,1.0,high,00c9758b75e5aaadf2d346b15f90db76
88
+ 0008,0008 - Heico - Prokto - 9.avi,4,5444,5697,217.76,227.92,10.16,00:03:37.760,00:03:47.920,254,1.0,high,00c9758b75e5aaadf2d346b15f90db76
89
+ 0008,0008 - Heico - Prokto - 9.avi,5,166621,237773,6664.84,9510.96,2846.12,01:51:04.840,02:38:30.960,71153,1.0,high,00c9758b75e5aaadf2d346b15f90db76
90
+ 0008,0008 - Heico - Prokto - 9.avi,6,238250,238399,9530.0,9536.0,6.0,02:38:50.000,02:38:56.000,150,1.0,high,00c9758b75e5aaadf2d346b15f90db76
91
+ 0008,0008 - Heico - Prokto - 9.avi,7,279716,280051,11188.64,11202.08,13.44,03:06:28.640,03:06:42.080,336,1.0,high,00c9758b75e5aaadf2d346b15f90db76
92
+ 0008,0008 - Heico - Prokto - 9.avi,8,280996,281540,11239.84,11261.64,21.8,03:07:19.840,03:07:41.640,545,1.0,high,00c9758b75e5aaadf2d346b15f90db76
93
+ 0008,0008 - Heico - Prokto - 9.avi,9,295157,298333,11806.28,11933.36,127.08,03:16:46.280,03:18:53.360,3177,1.0,high,00c9758b75e5aaadf2d346b15f90db76
94
+ 0008,0008 - Heico - Prokto - 9.avi,10,299481,299873,11979.24,11994.96,15.72,03:19:39.240,03:19:54.960,393,1.0,high,00c9758b75e5aaadf2d346b15f90db76
95
+ 0008,0008 - Heico - Prokto - 9.avi,11,301505,302399,12060.2,12096.0,35.8,03:21:00.200,03:21:36.000,895,1.0,high,00c9758b75e5aaadf2d346b15f90db76
96
+ 0008,0008 - Heico - Prokto - 9.avi,12,304394,305695,12175.76,12227.84,52.08,03:22:55.760,03:23:47.840,1302,1.0,high,00c9758b75e5aaadf2d346b15f90db76
97
+ 0008,0008 - Heico - Prokto - 9.avi,13,306196,310438,12247.84,12417.56,169.72,03:24:07.840,03:26:57.560,4243,1.0,high,00c9758b75e5aaadf2d346b15f90db76
98
+ 0008,0008 - Heico - Prokto - 9.avi,14,313476,313750,12539.04,12550.04,11.0,03:28:59.040,03:29:10.040,275,1.0,high,00c9758b75e5aaadf2d346b15f90db76
99
+ 0008,0008 - Heico - Prokto - 9.avi,15,315281,315430,12611.24,12617.24,6.0,03:30:11.240,03:30:17.240,150,1.0,high,00c9758b75e5aaadf2d346b15f90db76
100
+ 0009,0009 - Heico - Prokto - 10.avi,1,7060,8007,282.4,320.32,37.92,00:04:42.400,00:05:20.320,948,1.0,high,00c9758b75e5aaadf2d346b15f90db76
101
+ 0009,0009 - Heico - Prokto - 10.avi,2,8244,8518,329.76,340.76,11.0,00:05:29.760,00:05:40.760,275,1.0,high,00c9758b75e5aaadf2d346b15f90db76
102
+ 0009,0009 - Heico - Prokto - 10.avi,3,10905,11723,436.2,468.96,32.76,00:07:16.200,00:07:48.960,819,1.0,high,00c9758b75e5aaadf2d346b15f90db76
103
+ 0009,0009 - Heico - Prokto - 10.avi,4,12679,12928,507.16,517.16,10.0,00:08:27.160,00:08:37.160,250,1.0,high,00c9758b75e5aaadf2d346b15f90db76
104
+ 0009,0009 - Heico - Prokto - 10.avi,5,13404,13908,536.16,556.36,20.2,00:08:56.160,00:09:16.360,505,1.0,high,00c9758b75e5aaadf2d346b15f90db76
105
+ 0009,0009 - Heico - Prokto - 10.avi,6,53068,58670,2122.72,2346.84,224.12,00:35:22.720,00:39:06.840,5603,1.0,high,00c9758b75e5aaadf2d346b15f90db76
106
+ 0009,0009 - Heico - Prokto - 10.avi,7,63976,65220,2559.04,2608.84,49.8,00:42:39.040,00:43:28.840,1245,1.0,high,00c9758b75e5aaadf2d346b15f90db76
107
+ 0009,0009 - Heico - Prokto - 10.avi,8,65405,65853,2616.2,2634.16,17.96,00:43:36.200,00:43:54.160,449,1.0,high,00c9758b75e5aaadf2d346b15f90db76
108
+ 0009,0009 - Heico - Prokto - 10.avi,9,66390,67030,2655.6,2681.24,25.64,00:44:15.600,00:44:41.240,641,1.0,high,00c9758b75e5aaadf2d346b15f90db76
109
+ 0009,0009 - Heico - Prokto - 10.avi,10,85249,86243,3409.96,3449.76,39.8,00:56:49.960,00:57:29.760,995,1.0,high,00c9758b75e5aaadf2d346b15f90db76
110
+ 0009,0009 - Heico - Prokto - 10.avi,11,91754,92359,3670.16,3694.4,24.24,01:01:10.160,01:01:34.400,606,1.0,high,00c9758b75e5aaadf2d346b15f90db76
111
+ 0009,0009 - Heico - Prokto - 10.avi,12,92636,93048,3705.44,3721.96,16.52,01:01:45.440,01:02:01.960,413,1.0,high,00c9758b75e5aaadf2d346b15f90db76
112
+ 0009,0009 - Heico - Prokto - 10.avi,13,94012,94245,3760.48,3769.84,9.36,01:02:40.480,01:02:49.840,234,1.0,high,00c9758b75e5aaadf2d346b15f90db76
113
+ 0009,0009 - Heico - Prokto - 10.avi,14,125101,126017,5004.04,5040.72,36.68,01:23:24.040,01:24:00.720,917,1.0,high,00c9758b75e5aaadf2d346b15f90db76
114
+ 0009,0009 - Heico - Prokto - 10.avi,15,126104,126615,5044.16,5064.64,20.48,01:24:04.160,01:24:24.640,512,1.0,high,00c9758b75e5aaadf2d346b15f90db76
115
+ 0009,0009 - Heico - Prokto - 10.avi,16,145892,146607,5835.68,5864.32,28.64,01:37:15.680,01:37:44.320,716,1.0,high,00c9758b75e5aaadf2d346b15f90db76
116
+ 0009,0009 - Heico - Prokto - 10.avi,17,147498,148638,5899.92,5945.56,45.64,01:38:19.920,01:39:05.560,1141,1.0,high,00c9758b75e5aaadf2d346b15f90db76
117
+ 0009,0009 - Heico - Prokto - 10.avi,18,151144,153672,6045.76,6146.92,101.16,01:40:45.760,01:42:26.920,2529,1.0,high,00c9758b75e5aaadf2d346b15f90db76
118
+ 0009,0009 - Heico - Prokto - 10.avi,19,153819,154897,6152.76,6195.92,43.16,01:42:32.760,01:43:15.920,1079,1.0,high,00c9758b75e5aaadf2d346b15f90db76
119
+ 0009,0009 - Heico - Prokto - 10.avi,20,156864,157369,6274.56,6294.8,20.24,01:44:34.560,01:44:54.800,506,1.0,high,00c9758b75e5aaadf2d346b15f90db76
120
+ 0009,0009 - Heico - Prokto - 10.avi,21,158007,158420,6320.28,6336.84,16.56,01:45:20.280,01:45:36.840,414,1.0,high,00c9758b75e5aaadf2d346b15f90db76
121
+ 0009,0009 - Heico - Prokto - 10.avi,22,171517,171713,6860.68,6868.56,7.88,01:54:20.680,01:54:28.560,197,1.0,high,00c9758b75e5aaadf2d346b15f90db76
122
+ 0009,0009 - Heico - Prokto - 10.avi,23,185776,187204,7431.04,7488.2,57.16,02:03:51.040,02:04:48.200,1429,1.0,high,00c9758b75e5aaadf2d346b15f90db76
123
+ 0009,0009 - Heico - Prokto - 10.avi,24,187469,187733,7498.76,7509.36,10.6,02:04:58.760,02:05:09.360,265,1.0,high,00c9758b75e5aaadf2d346b15f90db76
124
+ 0009,0009 - Heico - Prokto - 10.avi,25,206430,207747,8257.2,8309.92,52.72,02:17:37.200,02:18:29.920,1318,1.0,high,00c9758b75e5aaadf2d346b15f90db76
125
+ 0009,0009 - Heico - Prokto - 10.avi,26,207944,208182,8317.76,8327.32,9.56,02:18:37.760,02:18:47.320,239,1.0,high,00c9758b75e5aaadf2d346b15f90db76
126
+ 0009,0009 - Heico - Prokto - 10.avi,27,214536,214878,8581.44,8595.16,13.72,02:23:01.440,02:23:15.160,343,1.0,high,00c9758b75e5aaadf2d346b15f90db76
127
+ 0009,0009 - Heico - Prokto - 10.avi,28,214972,215438,8598.88,8617.56,18.68,02:23:18.880,02:23:37.560,467,1.0,high,00c9758b75e5aaadf2d346b15f90db76
128
+ 0009,0009 - Heico - Prokto - 10.avi,29,216309,216908,8652.36,8676.36,24.0,02:24:12.360,02:24:36.360,600,1.0,high,00c9758b75e5aaadf2d346b15f90db76
129
+ 0009,0009 - Heico - Prokto - 10.avi,30,219259,219518,8770.36,8780.76,10.4,02:26:10.360,02:26:20.760,260,1.0,high,00c9758b75e5aaadf2d346b15f90db76
130
+ 0009,0009 - Heico - Prokto - 10.avi,31,223627,223803,8945.08,8952.16,7.08,02:29:05.080,02:29:12.160,177,1.0,high,00c9758b75e5aaadf2d346b15f90db76
131
+ 0009,0009 - Heico - Prokto - 10.avi,32,229414,229663,9176.56,9186.56,10.0,02:32:56.560,02:33:06.560,250,1.0,high,00c9758b75e5aaadf2d346b15f90db76
132
+ 0009,0009 - Heico - Prokto - 10.avi,33,280676,283232,11227.04,11329.32,102.28,03:07:07.040,03:08:49.320,2557,1.0,high,00c9758b75e5aaadf2d346b15f90db76
133
+ 0009,0009 - Heico - Prokto - 10.avi,34,308970,373914,12358.8,14956.6,2597.8,03:25:58.800,04:09:16.600,64945,1.0,high,00c9758b75e5aaadf2d346b15f90db76
134
+ 0009,0009 - Heico - Prokto - 10.avi,35,374164,374543,14966.56,14981.76,15.2,04:09:26.560,04:09:41.760,380,1.0,high,00c9758b75e5aaadf2d346b15f90db76
135
+ 0009,0009 - Heico - Prokto - 10.avi,36,387421,388107,15496.84,15524.32,27.48,04:18:16.840,04:18:44.320,687,1.0,high,00c9758b75e5aaadf2d346b15f90db76
136
+ 0009,0009 - Heico - Prokto - 10.avi,37,404192,404443,16167.68,16177.76,10.08,04:29:27.680,04:29:37.760,252,1.0,high,00c9758b75e5aaadf2d346b15f90db76
137
+ 0009,0009 - Heico - Prokto - 10.avi,38,407105,407310,16284.2,16292.44,8.24,04:31:24.200,04:31:32.440,206,1.0,high,00c9758b75e5aaadf2d346b15f90db76
138
+ 0009,0009 - Heico - Prokto - 10.avi,39,432003,441298,17280.12,17651.96,371.84,04:48:00.120,04:54:11.960,9296,1.0,high,00c9758b75e5aaadf2d346b15f90db76
139
+ 0009,0009 - Heico - Prokto - 10.avi,40,442866,443022,17714.64,17720.92,6.28,04:55:14.640,04:55:20.920,157,1.0,high,00c9758b75e5aaadf2d346b15f90db76
140
+ 0010,0010 - Heico - Rektum - 1.avi,1,41235,41710,1649.4,1668.44,19.04,00:27:29.400,00:27:48.440,476,1.0,high,00c9758b75e5aaadf2d346b15f90db76
141
+ 0010,0010 - Heico - Rektum - 1.avi,2,49196,50101,1967.84,2004.08,36.24,00:32:47.840,00:33:24.080,906,1.0,high,00c9758b75e5aaadf2d346b15f90db76
142
+ 0010,0010 - Heico - Rektum - 1.avi,3,157223,158078,6288.92,6323.16,34.24,01:44:48.920,01:45:23.160,856,1.0,high,00c9758b75e5aaadf2d346b15f90db76
143
+ 0010,0010 - Heico - Rektum - 1.avi,4,160177,161891,6407.08,6475.68,68.6,01:46:47.080,01:47:55.680,1715,1.0,high,00c9758b75e5aaadf2d346b15f90db76
144
+ 0010,0010 - Heico - Rektum - 1.avi,5,165333,165802,6613.32,6632.12,18.8,01:50:13.320,01:50:32.120,470,1.0,high,00c9758b75e5aaadf2d346b15f90db76
145
+ 0010,0010 - Heico - Rektum - 1.avi,6,265363,265733,10614.52,10629.36,14.84,02:56:54.520,02:57:09.360,371,1.0,high,00c9758b75e5aaadf2d346b15f90db76
146
+ 0010,0010 - Heico - Rektum - 1.avi,7,265907,336405,10636.28,13456.24,2819.96,02:57:16.280,03:44:16.240,70499,1.0,high,00c9758b75e5aaadf2d346b15f90db76
147
+ 0010,0010 - Heico - Rektum - 1.avi,8,354902,355116,14196.08,14204.68,8.6,03:56:36.080,03:56:44.680,215,1.0,high,00c9758b75e5aaadf2d346b15f90db76
148
+ 0010,0010 - Heico - Rektum - 1.avi,9,371415,371767,14856.6,14870.72,14.12,04:07:36.600,04:07:50.720,353,1.0,high,00c9758b75e5aaadf2d346b15f90db76
149
+ 0010,0010 - Heico - Rektum - 1.avi,10,373541,373981,14941.64,14959.28,17.64,04:09:01.640,04:09:19.280,441,1.0,high,00c9758b75e5aaadf2d346b15f90db76
150
+ 0010,0010 - Heico - Rektum - 1.avi,11,376427,376726,15057.08,15069.08,12.0,04:10:57.080,04:11:09.080,300,1.0,high,00c9758b75e5aaadf2d346b15f90db76
151
+ 0010,0010 - Heico - Rektum - 1.avi,12,397827,398219,15913.08,15928.8,15.72,04:25:13.080,04:25:28.800,393,1.0,high,00c9758b75e5aaadf2d346b15f90db76
152
+ 0010,0010 - Heico - Rektum - 1.avi,13,421164,429147,16846.56,17165.92,319.36,04:40:46.560,04:46:05.920,7984,1.0,high,00c9758b75e5aaadf2d346b15f90db76
153
+ 0011,0011 - Heico - Rektum - 2.avi,1,7043,7277,281.72,291.12,9.4,00:04:41.720,00:04:51.120,235,1.0,high,00c9758b75e5aaadf2d346b15f90db76
154
+ 0011,0011 - Heico - Rektum - 2.avi,2,176925,177260,7077.0,7090.44,13.44,01:57:57.000,01:58:10.440,336,1.0,high,00c9758b75e5aaadf2d346b15f90db76
155
+ 0011,0011 - Heico - Rektum - 2.avi,3,183595,232058,7343.8,9282.36,1938.56,02:02:23.800,02:34:42.360,48464,1.0,high,00c9758b75e5aaadf2d346b15f90db76
156
+ 0011,0011 - Heico - Rektum - 2.avi,4,239127,240057,9565.08,9602.32,37.24,02:39:25.080,02:40:02.320,931,1.0,high,00c9758b75e5aaadf2d346b15f90db76
157
+ 0011,0011 - Heico - Rektum - 2.avi,5,240627,241173,9625.08,9646.96,21.88,02:40:25.080,02:40:46.960,547,1.0,high,00c9758b75e5aaadf2d346b15f90db76
158
+ 0011,0011 - Heico - Rektum - 2.avi,6,247248,247415,9889.92,9896.64,6.72,02:44:49.920,02:44:56.640,168,1.0,high,00c9758b75e5aaadf2d346b15f90db76
159
+ 0011,0011 - Heico - Rektum - 2.avi,7,251653,254734,10066.12,10189.4,123.28,02:47:46.120,02:49:49.400,3082,1.0,high,00c9758b75e5aaadf2d346b15f90db76
160
+ 0011,0011 - Heico - Rektum - 2.avi,8,256223,256422,10248.92,10256.92,8.0,02:50:48.920,02:50:56.920,200,1.0,high,00c9758b75e5aaadf2d346b15f90db76
161
+ 0011,0011 - Heico - Rektum - 2.avi,9,266223,266459,10648.92,10658.4,9.48,02:57:28.920,02:57:38.400,237,1.0,high,00c9758b75e5aaadf2d346b15f90db76
162
+ 0011,0011 - Heico - Rektum - 2.avi,10,309058,309278,12362.32,12371.16,8.84,03:26:02.320,03:26:11.160,221,1.0,high,00c9758b75e5aaadf2d346b15f90db76
163
+ 0011,0011 - Heico - Rektum - 2.avi,11,330794,331069,13231.76,13242.8,11.04,03:40:31.760,03:40:42.800,276,1.0,high,00c9758b75e5aaadf2d346b15f90db76
164
+ 0012,0012 - Heico - Rektum - 3.avi,1,18110,18492,724.4,739.72,15.32,00:12:04.400,00:12:19.720,383,1.0,high,00c9758b75e5aaadf2d346b15f90db76
165
+ 0012,0012 - Heico - Rektum - 3.avi,2,150983,191811,6039.32,7672.48,1633.16,01:40:39.320,02:07:52.480,40829,1.0,high,00c9758b75e5aaadf2d346b15f90db76
166
+ 0012,0012 - Heico - Rektum - 3.avi,3,211660,211895,8466.4,8475.84,9.44,02:21:06.400,02:21:15.840,236,1.0,high,00c9758b75e5aaadf2d346b15f90db76
167
+ 0013,0013 - Heico - Rektum - 4.avi,1,8189,8713,327.56,348.56,21.0,00:05:27.560,00:05:48.560,525,1.0,high,00c9758b75e5aaadf2d346b15f90db76
168
+ 0013,0013 - Heico - Rektum - 4.avi,2,9174,9696,366.96,387.88,20.92,00:06:06.960,00:06:27.880,523,1.0,high,00c9758b75e5aaadf2d346b15f90db76
169
+ 0013,0013 - Heico - Rektum - 4.avi,3,12253,13148,490.12,525.96,35.84,00:08:10.120,00:08:45.960,896,1.0,high,00c9758b75e5aaadf2d346b15f90db76
170
+ 0013,0013 - Heico - Rektum - 4.avi,4,16396,16578,655.84,663.16,7.32,00:10:55.840,00:11:03.160,183,1.0,high,00c9758b75e5aaadf2d346b15f90db76
171
+ 0013,0013 - Heico - Rektum - 4.avi,5,164401,164911,6576.04,6596.48,20.44,01:49:36.040,01:49:56.480,511,1.0,high,00c9758b75e5aaadf2d346b15f90db76
172
+ 0013,0013 - Heico - Rektum - 4.avi,6,168791,208270,6751.64,8330.84,1579.2,01:52:31.640,02:18:50.840,39480,1.0,high,00c9758b75e5aaadf2d346b15f90db76
173
+ 0014,0014 - Heico - Rektum - 5.avi,1,1061,1842,42.44,73.72,31.28,00:00:42.440,00:01:13.720,782,1.0,high,00c9758b75e5aaadf2d346b15f90db76
174
+ 0014,0014 - Heico - Rektum - 5.avi,2,7887,8186,315.48,327.48,12.0,00:05:15.480,00:05:27.480,300,1.0,high,00c9758b75e5aaadf2d346b15f90db76
175
+ 0014,0014 - Heico - Rektum - 5.avi,3,9287,9578,371.48,383.16,11.68,00:06:11.480,00:06:23.160,292,1.0,high,00c9758b75e5aaadf2d346b15f90db76
176
+ 0014,0014 - Heico - Rektum - 5.avi,4,12362,12576,494.48,503.08,8.6,00:08:14.480,00:08:23.080,215,1.0,high,00c9758b75e5aaadf2d346b15f90db76
177
+ 0014,0014 - Heico - Rektum - 5.avi,5,12932,13344,517.28,533.8,16.52,00:08:37.280,00:08:53.800,413,1.0,high,00c9758b75e5aaadf2d346b15f90db76
178
+ 0014,0014 - Heico - Rektum - 5.avi,6,14932,15101,597.28,604.08,6.8,00:09:57.280,00:10:04.080,170,1.0,high,00c9758b75e5aaadf2d346b15f90db76
179
+ 0014,0014 - Heico - Rektum - 5.avi,7,20627,20781,825.08,831.28,6.2,00:13:45.080,00:13:51.280,155,1.0,high,00c9758b75e5aaadf2d346b15f90db76
180
+ 0014,0014 - Heico - Rektum - 5.avi,8,46852,47131,1874.08,1885.28,11.2,00:31:14.080,00:31:25.280,280,1.0,high,00c9758b75e5aaadf2d346b15f90db76
181
+ 0014,0014 - Heico - Rektum - 5.avi,9,83957,84631,3358.28,3385.28,27.0,00:55:58.280,00:56:25.280,675,1.0,high,00c9758b75e5aaadf2d346b15f90db76
182
+ 0014,0014 - Heico - Rektum - 5.avi,10,152532,152808,6101.28,6112.36,11.08,01:41:41.280,01:41:52.360,277,1.0,high,00c9758b75e5aaadf2d346b15f90db76
183
+ 0014,0014 - Heico - Rektum - 5.avi,11,159772,159947,6390.88,6397.92,7.04,01:46:30.880,01:46:37.920,176,1.0,high,00c9758b75e5aaadf2d346b15f90db76
184
+ 0014,0014 - Heico - Rektum - 5.avi,12,161457,161771,6458.28,6470.88,12.6,01:47:38.280,01:47:50.880,315,1.0,high,00c9758b75e5aaadf2d346b15f90db76
185
+ 0014,0014 - Heico - Rektum - 5.avi,13,164407,164931,6576.28,6597.28,21.0,01:49:36.280,01:49:57.280,525,1.0,high,00c9758b75e5aaadf2d346b15f90db76
186
+ 0014,0014 - Heico - Rektum - 5.avi,14,165100,165406,6604.0,6616.28,12.28,01:50:04.000,01:50:16.280,307,1.0,high,00c9758b75e5aaadf2d346b15f90db76
187
+ 0014,0014 - Heico - Rektum - 5.avi,15,170881,171031,6835.24,6841.28,6.04,01:53:55.240,01:54:01.280,151,1.0,high,00c9758b75e5aaadf2d346b15f90db76
188
+ 0014,0014 - Heico - Rektum - 5.avi,16,214275,256687,8571.0,10267.52,1696.52,02:22:51.000,02:51:07.520,42413,1.0,high,00c9758b75e5aaadf2d346b15f90db76
189
+ 0014,0014 - Heico - Rektum - 5.avi,17,269272,270063,10770.88,10802.56,31.68,02:59:30.880,03:00:02.560,792,1.0,high,00c9758b75e5aaadf2d346b15f90db76
190
+ 0014,0014 - Heico - Rektum - 5.avi,18,286533,300706,11461.32,12028.28,566.96,03:11:01.320,03:20:28.280,14174,1.0,high,00c9758b75e5aaadf2d346b15f90db76
191
+ 0015,0015 - Heico - Rektum - 6.avi,1,98,871,3.92,34.88,30.96,00:00:03.920,00:00:34.880,774,1.0,high,00c9758b75e5aaadf2d346b15f90db76
192
+ 0015,0015 - Heico - Rektum - 6.avi,2,1030,1304,41.2,52.2,11.0,00:00:41.200,00:00:52.200,275,1.0,high,00c9758b75e5aaadf2d346b15f90db76
193
+ 0015,0015 - Heico - Rektum - 6.avi,3,4210,4569,168.4,182.8,14.4,00:02:48.400,00:03:02.800,360,1.0,high,00c9758b75e5aaadf2d346b15f90db76
194
+ 0015,0015 - Heico - Rektum - 6.avi,4,24926,25082,997.04,1003.32,6.28,00:16:37.040,00:16:43.320,157,1.0,high,00c9758b75e5aaadf2d346b15f90db76
195
+ 0015,0015 - Heico - Rektum - 6.avi,5,25652,26954,1026.08,1078.2,52.12,00:17:06.080,00:17:58.200,1303,1.0,high,00c9758b75e5aaadf2d346b15f90db76
196
+ 0015,0015 - Heico - Rektum - 6.avi,6,71242,71463,2849.68,2858.56,8.88,00:47:29.680,00:47:38.560,222,1.0,high,00c9758b75e5aaadf2d346b15f90db76
197
+ 0015,0015 - Heico - Rektum - 6.avi,7,72854,73042,2914.16,2921.72,7.56,00:48:34.160,00:48:41.720,189,1.0,high,00c9758b75e5aaadf2d346b15f90db76
198
+ 0015,0015 - Heico - Rektum - 6.avi,8,118161,118453,4726.44,4738.16,11.72,01:18:46.440,01:18:58.160,293,1.0,high,00c9758b75e5aaadf2d346b15f90db76
199
+ 0015,0015 - Heico - Rektum - 6.avi,9,154118,154421,6164.72,6176.88,12.16,01:42:44.720,01:42:56.880,304,1.0,high,00c9758b75e5aaadf2d346b15f90db76
200
+ 0015,0015 - Heico - Rektum - 6.avi,10,225395,225584,9015.8,9023.4,7.6,02:30:15.800,02:30:23.400,190,1.0,high,00c9758b75e5aaadf2d346b15f90db76
201
+ 0015,0015 - Heico - Rektum - 6.avi,11,238226,238394,9529.04,9535.8,6.76,02:38:49.040,02:38:55.800,169,1.0,high,00c9758b75e5aaadf2d346b15f90db76
202
+ 0015,0015 - Heico - Rektum - 6.avi,12,239171,239361,9566.84,9574.48,7.64,02:39:26.840,02:39:34.480,191,1.0,high,00c9758b75e5aaadf2d346b15f90db76
203
+ 0015,0015 - Heico - Rektum - 6.avi,13,252669,252901,10106.76,10116.08,9.32,02:48:26.760,02:48:36.080,233,1.0,high,00c9758b75e5aaadf2d346b15f90db76
204
+ 0015,0015 - Heico - Rektum - 6.avi,14,254945,369078,10197.8,14763.16,4565.36,02:49:57.800,04:06:03.160,114134,1.0,high,00c9758b75e5aaadf2d346b15f90db76
205
+ 0015,0015 - Heico - Rektum - 6.avi,15,380814,381016,15232.56,15240.68,8.12,04:13:52.560,04:14:00.680,203,1.0,high,00c9758b75e5aaadf2d346b15f90db76
206
+ 0016,0016 - Heico - Rektum - 7.avi,1,286,504,11.44,20.2,8.76,00:00:11.440,00:00:20.200,219,1.0,high,00c9758b75e5aaadf2d346b15f90db76
207
+ 0016,0016 - Heico - Rektum - 7.avi,2,1535,2679,61.4,107.2,45.8,00:01:01.400,00:01:47.200,1145,1.0,high,00c9758b75e5aaadf2d346b15f90db76
208
+ 0016,0016 - Heico - Rektum - 7.avi,3,3366,3528,134.64,141.16,6.52,00:02:14.640,00:02:21.160,163,1.0,high,00c9758b75e5aaadf2d346b15f90db76
209
+ 0016,0016 - Heico - Rektum - 7.avi,4,76469,77327,3058.76,3093.12,34.36,00:50:58.760,00:51:33.120,859,1.0,high,00c9758b75e5aaadf2d346b15f90db76
210
+ 0016,0016 - Heico - Rektum - 7.avi,5,111617,111991,4464.68,4479.68,15.0,01:14:24.680,01:14:39.680,375,1.0,high,00c9758b75e5aaadf2d346b15f90db76
211
+ 0016,0016 - Heico - Rektum - 7.avi,6,176683,226219,7067.32,9048.8,1981.48,01:57:47.320,02:30:48.800,49537,1.0,high,00c9758b75e5aaadf2d346b15f90db76
212
+ 0016,0016 - Heico - Rektum - 7.avi,7,230857,231447,9234.28,9257.92,23.64,02:33:54.280,02:34:17.920,591,1.0,high,00c9758b75e5aaadf2d346b15f90db76
213
+ 0016,0016 - Heico - Rektum - 7.avi,8,245796,246039,9831.84,9841.6,9.76,02:43:51.840,02:44:01.600,244,1.0,high,00c9758b75e5aaadf2d346b15f90db76
214
+ 0016,0016 - Heico - Rektum - 7.avi,9,256669,257081,10266.76,10283.28,16.52,02:51:06.760,02:51:23.280,413,1.0,high,00c9758b75e5aaadf2d346b15f90db76
215
+ 0016,0016 - Heico - Rektum - 7.avi,10,262393,262789,10495.72,10511.6,15.88,02:54:55.720,02:55:11.600,397,1.0,high,00c9758b75e5aaadf2d346b15f90db76
216
+ 0016,0016 - Heico - Rektum - 7.avi,11,273830,274150,10953.2,10966.04,12.84,03:02:33.200,03:02:46.040,321,1.0,high,00c9758b75e5aaadf2d346b15f90db76
217
+ 0016,0016 - Heico - Rektum - 7.avi,12,275856,276843,11034.24,11073.76,39.52,03:03:54.240,03:04:33.760,988,1.0,high,00c9758b75e5aaadf2d346b15f90db76
218
+ 0016,0016 - Heico - Rektum - 7.avi,13,306912,317030,12276.48,12681.24,404.76,03:24:36.480,03:31:21.240,10119,1.0,high,00c9758b75e5aaadf2d346b15f90db76
219
+ 0016,0016 - Heico - Rektum - 7.avi,14,325605,337482,13024.2,13499.32,475.12,03:37:04.200,03:44:59.320,11878,1.0,high,00c9758b75e5aaadf2d346b15f90db76
220
+ 0016,0016 - Heico - Rektum - 7.avi,15,337785,349126,13511.4,13965.08,453.68,03:45:11.400,03:52:45.080,11342,1.0,high,00c9758b75e5aaadf2d346b15f90db76
221
+ 0016,0016 - Heico - Rektum - 7.avi,16,380708,381065,15228.32,15242.64,14.32,04:13:48.320,04:14:02.640,358,1.0,high,00c9758b75e5aaadf2d346b15f90db76
222
+ 0017,0017 - Heico - Rektum - 8.avi,1,3448,3630,137.92,145.24,7.32,00:02:17.920,00:02:25.240,183,1.0,high,00c9758b75e5aaadf2d346b15f90db76
223
+ 0017,0017 - Heico - Rektum - 8.avi,2,3746,4105,149.84,164.24,14.4,00:02:29.840,00:02:44.240,360,1.0,high,00c9758b75e5aaadf2d346b15f90db76
224
+ 0017,0017 - Heico - Rektum - 8.avi,3,5012,5189,200.48,207.6,7.12,00:03:20.480,00:03:27.600,178,1.0,high,00c9758b75e5aaadf2d346b15f90db76
225
+ 0017,0017 - Heico - Rektum - 8.avi,4,10783,11192,431.32,447.72,16.4,00:07:11.320,00:07:27.720,410,1.0,high,00c9758b75e5aaadf2d346b15f90db76
226
+ 0017,0017 - Heico - Rektum - 8.avi,5,143141,185332,5725.64,7413.32,1687.68,01:35:25.640,02:03:33.320,42192,1.0,high,00c9758b75e5aaadf2d346b15f90db76
227
+ 0017,0017 - Heico - Rektum - 8.avi,6,210088,210262,8403.52,8410.52,7.0,02:20:03.520,02:20:10.520,175,1.0,high,00c9758b75e5aaadf2d346b15f90db76
228
+ 0018,0018 - Heico - Rektum - 9.avi,1,4605,6290,184.2,251.64,67.44,00:03:04.200,00:04:11.640,1686,1.0,high,00c9758b75e5aaadf2d346b15f90db76
229
+ 0018,0018 - Heico - Rektum - 9.avi,2,196494,196665,7859.76,7866.64,6.88,02:10:59.760,02:11:06.640,172,1.0,high,00c9758b75e5aaadf2d346b15f90db76
230
+ 0018,0018 - Heico - Rektum - 9.avi,3,207338,260298,8293.52,10411.96,2118.44,02:18:13.520,02:53:31.960,52961,1.0,high,00c9758b75e5aaadf2d346b15f90db76
231
+ 0018,0018 - Heico - Rektum - 9.avi,4,267668,268079,10706.72,10723.2,16.48,02:58:26.720,02:58:43.200,412,1.0,high,00c9758b75e5aaadf2d346b15f90db76
232
+ 0018,0018 - Heico - Rektum - 9.avi,5,269857,270216,10794.28,10808.68,14.4,02:59:54.280,03:00:08.680,360,1.0,high,00c9758b75e5aaadf2d346b15f90db76
233
+ 0018,0018 - Heico - Rektum - 9.avi,6,345674,346337,13826.96,13853.52,26.56,03:50:26.960,03:50:53.520,664,1.0,high,00c9758b75e5aaadf2d346b15f90db76
234
+ 0019,0019 - Heico - Rektum - 10.avi,1,88458,88727,3538.32,3549.12,10.8,00:58:58.320,00:59:09.120,270,1.0,high,00c9758b75e5aaadf2d346b15f90db76
235
+ 0019,0019 - Heico - Rektum - 10.avi,2,101634,155868,4065.36,6234.76,2169.4,01:07:45.360,01:43:54.760,54235,1.0,high,00c9758b75e5aaadf2d346b15f90db76
236
+ 0019,0019 - Heico - Rektum - 10.avi,3,157074,157625,6282.96,6305.04,22.08,01:44:42.960,01:45:05.040,552,1.0,high,00c9758b75e5aaadf2d346b15f90db76
237
+ 0020,0020 - Heico - Sigma - 1.avi,1,76741,120650,3069.64,4826.04,1756.4,00:51:09.640,01:20:26.040,43910,1.0,high,00c9758b75e5aaadf2d346b15f90db76
238
+ 0021,0021 - Heico - Sigma - 2.avi,1,1403,1602,56.12,64.12,8.0,00:00:56.120,00:01:04.120,200,1.0,high,00c9758b75e5aaadf2d346b15f90db76
239
+ 0021,0021 - Heico - Sigma - 2.avi,2,75202,129434,3008.08,5177.4,2169.32,00:50:08.080,01:26:17.400,54233,1.0,high,00c9758b75e5aaadf2d346b15f90db76
240
+ 0021,0021 - Heico - Sigma - 2.avi,3,142160,142314,5686.4,5692.6,6.2,01:34:46.400,01:34:52.600,155,1.0,high,00c9758b75e5aaadf2d346b15f90db76
241
+ 0021,0021 - Heico - Sigma - 2.avi,4,145460,145669,5818.4,5826.8,8.4,01:36:58.400,01:37:06.800,210,1.0,high,00c9758b75e5aaadf2d346b15f90db76
242
+ 0022,0022 - Heico - Sigma - 3.avi,1,4196,4772,167.84,190.92,23.08,00:02:47.840,00:03:10.920,577,1.0,high,00c9758b75e5aaadf2d346b15f90db76
243
+ 0022,0022 - Heico - Sigma - 3.avi,2,58606,59151,2344.24,2366.08,21.84,00:39:04.240,00:39:26.080,546,1.0,high,00c9758b75e5aaadf2d346b15f90db76
244
+ 0022,0022 - Heico - Sigma - 3.avi,3,108376,109561,4335.04,4382.48,47.44,01:12:15.040,01:13:02.480,1186,1.0,high,00c9758b75e5aaadf2d346b15f90db76
245
+ 0022,0022 - Heico - Sigma - 3.avi,4,150566,151065,6022.64,6042.64,20.0,01:40:22.640,01:40:42.640,500,1.0,high,00c9758b75e5aaadf2d346b15f90db76
246
+ 0022,0022 - Heico - Sigma - 3.avi,5,213564,213819,8542.56,8552.8,10.24,02:22:22.560,02:22:32.800,256,1.0,high,00c9758b75e5aaadf2d346b15f90db76
247
+ 0022,0022 - Heico - Sigma - 3.avi,6,213927,214506,8557.08,8580.28,23.2,02:22:37.080,02:23:00.280,580,1.0,high,00c9758b75e5aaadf2d346b15f90db76
248
+ 0022,0022 - Heico - Sigma - 3.avi,7,235981,292296,9439.24,11691.88,2252.64,02:37:19.240,03:14:51.880,56316,1.0,high,00c9758b75e5aaadf2d346b15f90db76
249
+ 0022,0022 - Heico - Sigma - 3.avi,8,308248,308950,12329.92,12358.04,28.12,03:25:29.920,03:25:58.040,703,1.0,high,00c9758b75e5aaadf2d346b15f90db76
250
+ 0022,0022 - Heico - Sigma - 3.avi,9,313811,314209,12552.44,12568.4,15.96,03:29:12.440,03:29:28.400,399,1.0,high,00c9758b75e5aaadf2d346b15f90db76
251
+ 0022,0022 - Heico - Sigma - 3.avi,10,331328,332053,13253.12,13282.16,29.04,03:40:53.120,03:41:22.160,726,1.0,high,00c9758b75e5aaadf2d346b15f90db76
252
+ 0023,0023 - Heico - Sigma - 4.avi,1,22393,22586,895.72,903.48,7.76,00:14:55.720,00:15:03.480,194,1.0,high,00c9758b75e5aaadf2d346b15f90db76
253
+ 0023,0023 - Heico - Sigma - 4.avi,2,23387,23797,935.48,951.92,16.44,00:15:35.480,00:15:51.920,411,1.0,high,00c9758b75e5aaadf2d346b15f90db76
254
+ 0023,0023 - Heico - Sigma - 4.avi,3,58098,58324,2323.92,2333.0,9.08,00:38:43.920,00:38:53.000,227,1.0,high,00c9758b75e5aaadf2d346b15f90db76
255
+ 0023,0023 - Heico - Sigma - 4.avi,4,89007,89196,3560.28,3567.88,7.6,00:59:20.280,00:59:27.880,190,1.0,high,00c9758b75e5aaadf2d346b15f90db76
256
+ 0023,0023 - Heico - Sigma - 4.avi,5,135563,136671,5422.52,5466.88,44.36,01:30:22.520,01:31:06.880,1109,1.0,high,00c9758b75e5aaadf2d346b15f90db76
257
+ 0023,0023 - Heico - Sigma - 4.avi,6,136822,188918,5472.88,7556.76,2083.88,01:31:12.880,02:05:56.760,52097,1.0,high,00c9758b75e5aaadf2d346b15f90db76
258
+ 0023,0023 - Heico - Sigma - 4.avi,7,189491,189698,7579.64,7587.96,8.32,02:06:19.640,02:06:27.960,208,1.0,high,00c9758b75e5aaadf2d346b15f90db76
259
+ 0023,0023 - Heico - Sigma - 4.avi,8,216348,216559,8653.92,8662.4,8.48,02:24:13.920,02:24:22.400,212,1.0,high,00c9758b75e5aaadf2d346b15f90db76
260
+ 0024,0024 - Heico - Sigma - 5.avi,1,3523,3791,140.92,151.68,10.76,00:02:20.920,00:02:31.680,269,1.0,high,00c9758b75e5aaadf2d346b15f90db76
261
+ 0024,0024 - Heico - Sigma - 5.avi,2,49240,49671,1969.6,1986.88,17.28,00:32:49.600,00:33:06.880,432,1.0,high,00c9758b75e5aaadf2d346b15f90db76
262
+ 0024,0024 - Heico - Sigma - 5.avi,3,72121,129067,2884.84,5162.72,2277.88,00:48:04.840,01:26:02.720,56947,1.0,high,00c9758b75e5aaadf2d346b15f90db76
263
+ 0025,0025 - Heico - Sigma - 6.avi,1,769,971,30.76,38.88,8.12,00:00:30.760,00:00:38.880,203,1.0,high,00c9758b75e5aaadf2d346b15f90db76
264
+ 0025,0025 - Heico - Sigma - 6.avi,2,5809,5986,232.36,239.48,7.12,00:03:52.360,00:03:59.480,178,1.0,high,00c9758b75e5aaadf2d346b15f90db76
265
+ 0025,0025 - Heico - Sigma - 6.avi,3,53622,54258,2144.88,2170.36,25.48,00:35:44.880,00:36:10.360,637,1.0,high,00c9758b75e5aaadf2d346b15f90db76
266
+ 0025,0025 - Heico - Sigma - 6.avi,4,96792,96976,3871.68,3879.08,7.4,01:04:31.680,01:04:39.080,185,1.0,high,00c9758b75e5aaadf2d346b15f90db76
267
+ 0025,0025 - Heico - Sigma - 6.avi,5,119669,218770,4786.76,8750.84,3964.08,01:19:46.760,02:25:50.840,99102,1.0,high,00c9758b75e5aaadf2d346b15f90db76
268
+ 0025,0025 - Heico - Sigma - 6.avi,6,228355,230160,9134.2,9206.44,72.24,02:32:14.200,02:33:26.440,1806,1.0,high,00c9758b75e5aaadf2d346b15f90db76
269
+ 0025,0025 - Heico - Sigma - 6.avi,7,250996,252051,10039.84,10082.08,42.24,02:47:19.840,02:48:02.080,1056,1.0,high,00c9758b75e5aaadf2d346b15f90db76
270
+ 0025,0025 - Heico - Sigma - 6.avi,8,254176,254633,10167.04,10185.36,18.32,02:49:27.040,02:49:45.360,458,1.0,high,00c9758b75e5aaadf2d346b15f90db76
271
+ 0025,0025 - Heico - Sigma - 6.avi,9,294134,294341,11765.36,11773.68,8.32,03:16:05.360,03:16:13.680,208,1.0,high,00c9758b75e5aaadf2d346b15f90db76
272
+ 0025,0025 - Heico - Sigma - 6.avi,10,305932,306158,12237.28,12246.36,9.08,03:23:57.280,03:24:06.360,227,1.0,high,00c9758b75e5aaadf2d346b15f90db76
273
+ 0026,0026 - Heico - Sigma - 7.avi,1,104407,104647,4176.28,4185.92,9.64,01:09:36.280,01:09:45.920,241,1.0,high,00c9758b75e5aaadf2d346b15f90db76
274
+ 0026,0026 - Heico - Sigma - 7.avi,2,106008,106558,4240.32,4262.36,22.04,01:10:40.320,01:11:02.360,551,1.0,high,00c9758b75e5aaadf2d346b15f90db76
275
+ 0026,0026 - Heico - Sigma - 7.avi,3,135166,135550,5406.64,5422.04,15.4,01:30:06.640,01:30:22.040,385,1.0,high,00c9758b75e5aaadf2d346b15f90db76
276
+ 0026,0026 - Heico - Sigma - 7.avi,4,162447,162857,6497.88,6514.32,16.44,01:48:17.880,01:48:34.320,411,1.0,high,00c9758b75e5aaadf2d346b15f90db76
277
+ 0026,0026 - Heico - Sigma - 7.avi,5,179005,180062,7160.2,7202.52,42.32,01:59:20.200,02:00:02.520,1058,1.0,high,00c9758b75e5aaadf2d346b15f90db76
278
+ 0026,0026 - Heico - Sigma - 7.avi,6,184200,224505,7368.0,8980.24,1612.24,02:02:48.000,02:29:40.240,40306,1.0,high,00c9758b75e5aaadf2d346b15f90db76
279
+ 0026,0026 - Heico - Sigma - 7.avi,7,236957,238289,9478.28,9531.6,53.32,02:37:58.280,02:38:51.600,1333,1.0,high,00c9758b75e5aaadf2d346b15f90db76
280
+ 0027,0027 - Heico - Sigma - 8.avi,1,1424,3592,56.96,143.72,86.76,00:00:56.960,00:02:23.720,2169,1.0,high,00c9758b75e5aaadf2d346b15f90db76
281
+ 0027,0027 - Heico - Sigma - 8.avi,2,5305,5598,212.2,223.96,11.76,00:03:32.200,00:03:43.960,294,1.0,high,00c9758b75e5aaadf2d346b15f90db76
282
+ 0027,0027 - Heico - Sigma - 8.avi,3,34580,34741,1383.2,1389.68,6.48,00:23:03.200,00:23:09.680,162,1.0,high,00c9758b75e5aaadf2d346b15f90db76
283
+ 0027,0027 - Heico - Sigma - 8.avi,4,35894,36166,1435.76,1446.68,10.92,00:23:55.760,00:24:06.680,273,1.0,high,00c9758b75e5aaadf2d346b15f90db76
284
+ 0027,0027 - Heico - Sigma - 8.avi,5,76803,119032,3072.12,4761.32,1689.2,00:51:12.120,01:19:21.320,42230,1.0,high,00c9758b75e5aaadf2d346b15f90db76
285
+ 0027,0027 - Heico - Sigma - 8.avi,6,136180,136435,5447.2,5457.44,10.24,01:30:47.200,01:30:57.440,256,1.0,high,00c9758b75e5aaadf2d346b15f90db76
286
+ 0027,0027 - Heico - Sigma - 8.avi,7,136515,136760,5460.6,5470.44,9.84,01:31:00.600,01:31:10.440,246,1.0,high,00c9758b75e5aaadf2d346b15f90db76
287
+ 0027,0027 - Heico - Sigma - 8.avi,8,136865,137086,5474.6,5483.48,8.88,01:31:14.600,01:31:23.480,222,1.0,high,00c9758b75e5aaadf2d346b15f90db76
288
+ 0027,0027 - Heico - Sigma - 8.avi,9,138468,138680,5538.72,5547.24,8.52,01:32:18.720,01:32:27.240,213,1.0,high,00c9758b75e5aaadf2d346b15f90db76
289
+ 0027,0027 - Heico - Sigma - 8.avi,10,154029,154743,6161.16,6189.76,28.6,01:42:41.160,01:43:09.760,715,1.0,high,00c9758b75e5aaadf2d346b15f90db76
290
+ 0027,0027 - Heico - Sigma - 8.avi,11,155735,155951,6229.4,6238.08,8.68,01:43:49.400,01:43:58.080,217,1.0,high,00c9758b75e5aaadf2d346b15f90db76
291
+ 0027,0027 - Heico - Sigma - 8.avi,12,170430,171242,6817.2,6849.72,32.52,01:53:37.200,01:54:09.720,813,1.0,high,00c9758b75e5aaadf2d346b15f90db76
292
+ 0027,0027 - Heico - Sigma - 8.avi,13,171760,173589,6870.4,6943.6,73.2,01:54:30.400,01:55:43.600,1830,1.0,high,00c9758b75e5aaadf2d346b15f90db76
293
+ 0027,0027 - Heico - Sigma - 8.avi,14,175565,176292,7022.6,7051.72,29.12,01:57:02.600,01:57:31.720,728,1.0,high,00c9758b75e5aaadf2d346b15f90db76
294
+ 0027,0027 - Heico - Sigma - 8.avi,15,188701,189011,7548.04,7560.48,12.44,02:05:48.040,02:06:00.480,311,1.0,high,00c9758b75e5aaadf2d346b15f90db76
295
+ 0027,0027 - Heico - Sigma - 8.avi,16,193088,193299,7723.52,7732.0,8.48,02:08:43.520,02:08:52.000,212,1.0,high,00c9758b75e5aaadf2d346b15f90db76
296
+ 0027,0027 - Heico - Sigma - 8.avi,17,193457,193676,7738.28,7747.08,8.8,02:08:58.280,02:09:07.080,220,1.0,high,00c9758b75e5aaadf2d346b15f90db76
297
+ 0027,0027 - Heico - Sigma - 8.avi,18,204905,205339,8196.2,8213.6,17.4,02:16:36.200,02:16:53.600,435,1.0,high,00c9758b75e5aaadf2d346b15f90db76
298
+ 0028,0028 - Heico - Sigma - 9.avi,1,2902,4698,116.08,187.96,71.88,00:01:56.080,00:03:07.960,1797,1.0,high,00c9758b75e5aaadf2d346b15f90db76
299
+ 0028,0028 - Heico - Sigma - 9.avi,2,4812,5000,192.48,200.04,7.56,00:03:12.480,00:03:20.040,189,1.0,high,00c9758b75e5aaadf2d346b15f90db76
300
+ 0028,0028 - Heico - Sigma - 9.avi,3,5332,5945,213.28,237.84,24.56,00:03:33.280,00:03:57.840,614,1.0,high,00c9758b75e5aaadf2d346b15f90db76
301
+ 0028,0028 - Heico - Sigma - 9.avi,4,6263,6645,250.52,265.84,15.32,00:04:10.520,00:04:25.840,383,1.0,high,00c9758b75e5aaadf2d346b15f90db76
302
+ 0028,0028 - Heico - Sigma - 9.avi,5,81620,82270,3264.8,3290.84,26.04,00:54:24.800,00:54:50.840,651,1.0,high,00c9758b75e5aaadf2d346b15f90db76
303
+ 0028,0028 - Heico - Sigma - 9.avi,6,83668,84298,3346.72,3371.96,25.24,00:55:46.720,00:56:11.960,631,1.0,high,00c9758b75e5aaadf2d346b15f90db76
304
+ 0028,0028 - Heico - Sigma - 9.avi,7,91175,92008,3647.0,3680.36,33.36,01:00:47.000,01:01:20.360,834,1.0,high,00c9758b75e5aaadf2d346b15f90db76
305
+ 0028,0028 - Heico - Sigma - 9.avi,8,98941,99826,3957.64,3993.08,35.44,01:05:57.640,01:06:33.080,886,1.0,high,00c9758b75e5aaadf2d346b15f90db76
306
+ 0028,0028 - Heico - Sigma - 9.avi,9,99972,100157,3998.88,4006.32,7.44,01:06:38.880,01:06:46.320,186,1.0,high,00c9758b75e5aaadf2d346b15f90db76
307
+ 0028,0028 - Heico - Sigma - 9.avi,10,106430,107644,4257.2,4305.8,48.6,01:10:57.200,01:11:45.800,1215,1.0,high,00c9758b75e5aaadf2d346b15f90db76
308
+ 0028,0028 - Heico - Sigma - 9.avi,11,108850,109555,4354.0,4382.24,28.24,01:12:34.000,01:13:02.240,706,1.0,high,00c9758b75e5aaadf2d346b15f90db76
309
+ 0028,0028 - Heico - Sigma - 9.avi,12,111863,113239,4474.52,4529.6,55.08,01:14:34.520,01:15:29.600,1377,1.0,high,00c9758b75e5aaadf2d346b15f90db76
310
+ 0028,0028 - Heico - Sigma - 9.avi,13,113515,114657,4540.6,4586.32,45.72,01:15:40.600,01:16:26.320,1143,1.0,high,00c9758b75e5aaadf2d346b15f90db76
311
+ 0028,0028 - Heico - Sigma - 9.avi,14,129364,146585,5174.56,5863.44,688.88,01:26:14.560,01:37:43.440,17222,1.0,high,00c9758b75e5aaadf2d346b15f90db76
312
+ 0028,0028 - Heico - Sigma - 9.avi,15,157535,157730,6301.4,6309.24,7.84,01:45:01.400,01:45:09.240,196,1.0,high,00c9758b75e5aaadf2d346b15f90db76
313
+ 0028,0028 - Heico - Sigma - 9.avi,16,164683,165551,6587.32,6622.08,34.76,01:49:47.320,01:50:22.080,869,1.0,high,00c9758b75e5aaadf2d346b15f90db76
314
+ 0028,0028 - Heico - Sigma - 9.avi,17,178354,178553,7134.16,7142.16,8.0,01:58:54.160,01:59:02.160,200,1.0,high,00c9758b75e5aaadf2d346b15f90db76
315
+ 0028,0028 - Heico - Sigma - 9.avi,18,183895,184094,7355.8,7363.8,8.0,02:02:35.800,02:02:43.800,200,1.0,high,00c9758b75e5aaadf2d346b15f90db76
316
+ 0028,0028 - Heico - Sigma - 9.avi,19,188618,234529,7544.72,9381.2,1836.48,02:05:44.720,02:36:21.200,45912,1.0,high,00c9758b75e5aaadf2d346b15f90db76
317
+ 0028,0028 - Heico - Sigma - 9.avi,20,284965,286101,11398.6,11444.08,45.48,03:09:58.600,03:10:44.080,1137,1.0,high,00c9758b75e5aaadf2d346b15f90db76
318
+ 0028,0028 - Heico - Sigma - 9.avi,21,315904,316225,12636.16,12649.04,12.88,03:30:36.160,03:30:49.040,322,1.0,high,00c9758b75e5aaadf2d346b15f90db76
319
+ 0028,0028 - Heico - Sigma - 9.avi,22,319567,364856,12782.68,14594.28,1811.6,03:33:02.680,04:03:14.280,45290,1.0,high,00c9758b75e5aaadf2d346b15f90db76
320
+ 0028,0028 - Heico - Sigma - 9.avi,23,381302,381504,15252.08,15260.2,8.12,04:14:12.080,04:14:20.200,203,1.0,high,00c9758b75e5aaadf2d346b15f90db76
321
+ 0029,0029 - Heico - Sigma - 10.avi,1,446,620,17.84,24.84,7.0,00:00:17.840,00:00:24.840,175,1.0,high,00c9758b75e5aaadf2d346b15f90db76
322
+ 0029,0029 - Heico - Sigma - 10.avi,2,1218,1586,48.72,63.48,14.76,00:00:48.720,00:01:03.480,369,1.0,high,00c9758b75e5aaadf2d346b15f90db76
323
+ 0029,0029 - Heico - Sigma - 10.avi,3,168562,217347,6742.48,8693.92,1951.44,01:52:22.480,02:24:53.920,48786,1.0,high,00c9758b75e5aaadf2d346b15f90db76
324
+ 0029,0029 - Heico - Sigma - 10.avi,4,222032,222368,8881.28,8894.76,13.48,02:28:01.280,02:28:14.760,337,1.0,high,00c9758b75e5aaadf2d346b15f90db76
pipelines/Tr006_V2/annotations/blue_screen/metadata/provenance.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_dataset": "heico-focus-vqa",
3
+ "source_videos_included": false,
4
+ "run_id": "debug015",
5
+ "run_name": "blue_screen_coarse_to_fine_20260825",
6
+ "task_type": "debug",
7
+ "status": "completed",
8
+ "purpose": "Detect frozen blue-screen intervals in all 30 HeiCo endoscopy videos with 1-second coarse hashing and native-FPS exact boundary refinement.",
9
+ "code_commit": "a23500eae96b97f4b9aff3d00d08c3b8e7482aba",
10
+ "entrypoint": "data_preprocessing/process_blue_screen_dataset.py",
11
+ "parameters": {
12
+ "sample_seconds": 1.0,
13
+ "min_static_seconds": 6.0,
14
+ "blue_ratio": 0.45,
15
+ "pad_seconds": 5.0,
16
+ "coarse_size": 96,
17
+ "fine_size": 160,
18
+ "fine_resolution": "native FPS (25 FPS for inspected dataset)"
19
+ },
20
+ "precision": "uint8 decoded frames",
21
+ "started_at": "2026-08-25T00:22:36+08:00",
22
+ "finished_at": "2026-08-25T02:54:37+08:00",
23
+ "results_summary": {
24
+ "video_count": 30,
25
+ "completed_video_count": 30,
26
+ "failed_video_count": 0,
27
+ "total_frames": 8681449,
28
+ "total_decoded_frames": 8681449,
29
+ "interval_count": 323,
30
+ "total_video_seconds": 347257.96,
31
+ "total_blue_screen_seconds": 82209.52,
32
+ "elapsed_seconds": 9119.6,
33
+ "validation": "PASS"
34
+ },
35
+ "issues": [],
36
+ "acceptance": {
37
+ "all_30_videos_accounted_for": true,
38
+ "no_source_video_modification": true,
39
+ "frame_level_boundaries": true,
40
+ "complete_log": true,
41
+ "aggregate_counts_match_per_video_results": true
42
+ }
43
+ }
pipelines/Tr006_V2/annotations/blue_screen/metadata/summary.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "completed",
3
+ "started_at": "2026-08-25T00:22:37.492532+08:00",
4
+ "finished_at": "2026-08-25T02:54:37.111581+08:00",
5
+ "elapsed_seconds": 9119.599936511368,
6
+ "host": "farm02",
7
+ "config": {
8
+ "sample_seconds": 1.0,
9
+ "min_static_seconds": 6.0,
10
+ "blue_ratio": 0.45,
11
+ "pad_seconds": 5.0,
12
+ "coarse_size": 96,
13
+ "fine_size": 160,
14
+ "definition": "exact repeated decoded ROI frames with blue ratio above threshold"
15
+ },
16
+ "video_count": 30,
17
+ "completed_video_count": 30,
18
+ "failed_video_count": 0,
19
+ "videos_with_blue_screen": 30,
20
+ "interval_count": 323,
21
+ "total_video_seconds": 347257.9600000001,
22
+ "total_blue_screen_seconds": 82209.52000000003
23
+ }
pipelines/Tr006_V2/annotations/blue_screen/metadata/validation.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "PASS",
3
+ "validated_at": "2026-08-25T02:54:37+08:00",
4
+ "source_video_count": 30,
5
+ "per_video_json_count": 30,
6
+ "videos_jsonl_count": 30,
7
+ "completed_video_count": 30,
8
+ "failed_video_count": 0,
9
+ "interval_count": 323,
10
+ "csv_interval_count": 323,
11
+ "evidence_jpeg_count": 1291,
12
+ "total_frames": 8681449,
13
+ "total_decoded_frames": 8681449,
14
+ "total_video_seconds": 347257.96,
15
+ "total_blue_screen_seconds": 82209.52,
16
+ "checks": {
17
+ "source_paths_exactly_covered": true,
18
+ "all_videos_completed": true,
19
+ "all_frames_decoded": true,
20
+ "intervals_ordered_non_overlapping_and_in_bounds": true,
21
+ "interval_duration_matches_frame_bounds": true,
22
+ "all_intervals_meet_hash_and_blue_criteria": true,
23
+ "aggregate_csv_matches_per_video_results": true,
24
+ "all_non_boundary_evidence_frames_present": true,
25
+ "run_log_exit_code_zero": true
26
+ },
27
+ "notes": [
28
+ "Video 0022 has one interval ending at the final source frame, so an after-boundary evidence frame does not exist by definition."
29
+ ]
30
+ }
pipelines/Tr006_V2/annotations/blue_screen/metadata/videos.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
pipelines/Tr006_V2/docker/segment-algorithm/.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Keep the build context small. do_save.sh writes multi-gigabyte *.tar.gz
2
+ # archives into this folder, and without this file Docker would send them
3
+ # (and the sample batch) to the daemon on every build.
4
+ *.tar.gz
5
+ test/
6
+ __pycache__/
pipelines/Tr006_V2/docker/segment-algorithm/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.tar.gz
2
+ __pycache__/
pipelines/Tr006_V2/docker/segment-algorithm/Dockerfile ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM --platform=linux/amd64 pytorch/pytorch:2.11.0-cuda12.8-cudnn9-runtime AS segment-algorithm-amd64
2
+
3
+ # torch 2.11.0 + torchvision are pre-installed in the base image (CUDA 12.8).
4
+ # requirements.txt only lists additional dependencies.
5
+ #
6
+ # This base image is the recommended default, not a hard requirement. It is chosen
7
+ # because it is the ONE PyTorch build that runs on both evaluation GPUs unmodified:
8
+ # an NVIDIA RTX PRO 6000 Blackwell (compute capability sm_120) and an NVIDIA L40S
9
+ # (sm_89). Its CUDA 12.8 kernels cover sm_75/80/86/90/100/120 — Blackwell natively
10
+ # and the L40S via its sm_86 kernels (a CUDA binary runs on any later minor
11
+ # revision of the same major capability). So one image serves whichever GPU you
12
+ # select on the platform, with no rebuild.
13
+ #
14
+ # If you change it, know the two limits it steers around: CUDA 12.4/12.6 wheels
15
+ # stop at sm_90 and abort on Blackwell ("not compatible with the current PyTorch
16
+ # installation"); CUDA 13.0 wheels cover both GPUs but need a host driver >= 580 —
17
+ # the RTX PRO 6000 fleet has it (595), the L40S fleet does not. If your submission
18
+ # targets ONLY the RTX PRO 6000 (offered on the SEGMENT and PROCEDURE tracks), a
19
+ # newer CUDA/torch is a valid choice — see the guard below for the one line to relax.
20
+
21
+ ENV PYTHONUNBUFFERED=1
22
+
23
+ # ── System packages ───────────────────────────────────────────────────────────
24
+ # The slim -runtime base image ships neither the shared libraries that
25
+ # opencv-python (a dependency of orena-focus) needs at import time, nor any
26
+ # compiler toolchain. Install system packages HERE, while the build still runs
27
+ # as root — everything below runs as the unprivileged 'user'. If one of your
28
+ # pip dependencies compiles from source (e.g. flash-attn), add build-essential.
29
+ RUN apt-get update \
30
+ && apt-get install -y --no-install-recommends \
31
+ libgl1 \
32
+ libglib2.0-0 \
33
+ libxcb1 \
34
+ && rm -rf /var/lib/apt/lists/*
35
+
36
+ RUN groupadd -r user && useradd -m --no-log-init -r -g user user
37
+ USER user
38
+
39
+ WORKDIR /opt/app
40
+
41
+ COPY --chown=user:user requirements.txt /opt/app/
42
+
43
+ # If one of your dependencies compiles CUDA extensions during the build (e.g.
44
+ # flash-attn from source), uncomment this so it emits kernels for both evaluation
45
+ # GPUs rather than only for the card in your build machine:
46
+ # ENV TORCH_CUDA_ARCH_LIST="8.0;8.6;8.9;9.0;12.0+PTX"
47
+
48
+ # --break-system-packages: this base image's Python is Ubuntu's system Python,
49
+ # which is marked externally-managed (PEP 668), so pip refuses to install without
50
+ # it. Nothing is actually broken — `--user` still installs into the unprivileged
51
+ # user's own ~/.local, leaving the system packages untouched.
52
+ RUN python -m pip install \
53
+ --user \
54
+ --break-system-packages \
55
+ --no-cache-dir \
56
+ --no-color \
57
+ --requirement /opt/app/requirements.txt
58
+
59
+ # ── Guard: is torch still the recommended CUDA 12.8 build? ────────────────────
60
+ # This enforces the recommendation above so an ACCIDENTAL swap cannot reach the
61
+ # platform unnoticed. If one of your dependencies needs a torch version this image
62
+ # does not have, pip quietly replaces the CUDA 12.8 build with the default PyPI
63
+ # wheel (built against CUDA 13.0). That wheel needs a host driver >= 580.65.06; the
64
+ # L40S fleet runs 570.211.01, so it cannot start there. Failing the build now beats
65
+ # failing every job after you submit.
66
+ #
67
+ # Both halves matter. The CUDA version is what the driver floor depends on, and it
68
+ # is NOT visible in the architecture list: the CUDA 13.0 wheel advertises the very
69
+ # same sm_ list as the 12.8 one. The arch list is still checked because it is what
70
+ # an older CUDA 12.4/12.6 wheel gets wrong. get_arch_list() returns [] with no GPU
71
+ # visible — a docker build never has one — so the flags compiled into the wheel are
72
+ # read directly.
73
+ #
74
+ # Targeting the RTX PRO 6000 only, and want a newer CUDA? Then this default no
75
+ # longer applies to you: drop the `cuda.startswith('12.8')` assert below (keep the
76
+ # sm_120 arch assert, so a build with no Blackwell kernels is still caught).
77
+ RUN python -c "import torch; \
78
+ flags = torch._C._cuda_getArchFlags() or ''; \
79
+ cuda = torch.version.cuda or ''; \
80
+ print('torch', torch.__version__, '| cuda', cuda, '|', flags); \
81
+ assert cuda.startswith('12.8'), 'torch was replaced by a CUDA ' + cuda + ' build; the L40S driver cannot run it'; \
82
+ assert 'sm_120' in flags and 'sm_86' in flags, 'torch build misses a required GPU architecture: ' + repr(flags)"
83
+
84
+ # ── Model definition + weights ────────────────────────────────────────────────
85
+ # resources/ holds the model definition (model.py) and a small committed dummy
86
+ # checkpoint (dummy_weights.pt). For a real submission, replace dummy_weights.pt
87
+ # with your trained checkpoint.
88
+ #
89
+ # ECR layer-size limit: a single image layer must not exceed 50 GB, and each
90
+ # COPY instruction produces exactly one layer. A checkpoint large enough to push
91
+ # one COPY past that limit must be split into chunks and copied with several COPY
92
+ # instructions (one layer each), then reassembled at runtime — e.g. pre-split
93
+ # with `split -b 45G weights.pt resources/weights.part-` and:
94
+ # COPY --chown=user:user resources/weights.part-aa /opt/app/resources/
95
+ # COPY --chown=user:user resources/weights.part-ab /opt/app/resources/
96
+ # COPY --chown=user:user resources/weights.part-ac /opt/app/resources/
97
+ # reassembling with `cat resources/weights.part-* > weights.pt` before load.
98
+ COPY --chown=user:user resources/ /opt/app/resources/
99
+
100
+ COPY --chown=user:user inference.py /opt/app/
101
+
102
+ ENTRYPOINT ["python", "inference.py"]
pipelines/Tr006_V2/docker/segment-algorithm/do_build.sh ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6
+ DOCKER_IMAGE_TAG="segment-algorithm"
7
+
8
+ docker build \
9
+ --platform=linux/amd64 \
10
+ --tag "$DOCKER_IMAGE_TAG" \
11
+ "$SCRIPT_DIR" 2>&1
pipelines/Tr006_V2/docker/segment-algorithm/do_save.sh ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -eo pipefail
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6
+
7
+ DOCKER_IMAGE_TAG="segment-algorithm"
8
+
9
+ echo "=+= (Re)build the container"
10
+ source "${SCRIPT_DIR}/do_build.sh"
11
+
12
+ build_timestamp=$( docker inspect --format='{{ .Created }}' "$DOCKER_IMAGE_TAG")
13
+
14
+ if [ -z "$build_timestamp" ]; then
15
+ echo "Error: Failed to retrieve build information for container $DOCKER_IMAGE_TAG"
16
+ exit 1
17
+ fi
18
+
19
+ formatted_build_info=$(echo $build_timestamp | sed -E 's/(.*)T(.*)\..*Z/\1_\2/' | sed 's/[-,:]/-/g')
20
+
21
+ output_filename="${SCRIPT_DIR}/${DOCKER_IMAGE_TAG}_${formatted_build_info}.tar.gz"
22
+
23
+ echo "==+=="
24
+ echo "Saving the container image as ${output_filename}. This can take a while."
25
+ echo ""
26
+
27
+ # Use pigz (parallel gzip) if available — much faster than gzip on a large image.
28
+ if command -v pigz >/dev/null 2>&1; then
29
+ compressor=(pigz -c)
30
+ else
31
+ compressor=(gzip -c)
32
+ fi
33
+
34
+ # Show a progress bar with pv if available, sized by the image's uncompressed size.
35
+ image_size=$(docker image inspect "$DOCKER_IMAGE_TAG" --format '{{.Size}}' 2>/dev/null || true)
36
+
37
+ if command -v pv >/dev/null 2>&1; then
38
+ if [[ "$image_size" =~ ^[0-9]+$ && "$image_size" -gt 0 ]]; then
39
+ docker save "$DOCKER_IMAGE_TAG" \
40
+ | pv --size "$image_size" --progress --timer --eta --rate --bytes \
41
+ | "${compressor[@]}" > "$output_filename"
42
+ else
43
+ docker save "$DOCKER_IMAGE_TAG" | pv --timer --rate --bytes | "${compressor[@]}" > "$output_filename"
44
+ fi
45
+ else
46
+ echo "Tip: install 'pv' (e.g. sudo apt-get install pv) for a progress bar."
47
+ docker save "$DOCKER_IMAGE_TAG" | "${compressor[@]}" > "$output_filename"
48
+ fi
49
+ echo "Container image saved as ${output_filename}"
50
+ echo "==+=="
pipelines/Tr006_V2/docker/segment-algorithm/do_test_run.sh ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6
+ DOCKER_IMAGE_TAG="segment-algorithm"
7
+
8
+ DOCKER_NOOP_VOLUME="${DOCKER_IMAGE_TAG}-volume"
9
+
10
+ INPUT_DIR="${SCRIPT_DIR}/test/input"
11
+ OUTPUT_DIR="${SCRIPT_DIR}/test/output"
12
+
13
+ # Use the GPU when Docker's NVIDIA runtime is available; fall back to CPU
14
+ # otherwise (the dummy runs fine on CPU — the platform always provides a GPU).
15
+ GPU_FLAGS=()
16
+ if docker info --format '{{range $k, $v := .Runtimes}}{{$k}} {{end}}' 2>/dev/null | grep -qw nvidia; then
17
+ GPU_FLAGS=(--gpus all)
18
+ else
19
+ echo "=+= No NVIDIA container runtime detected - running on CPU"
20
+ fi
21
+
22
+ echo "=+= (Re)build the container"
23
+ source "${SCRIPT_DIR}/do_build.sh"
24
+
25
+ cleanup() {
26
+ echo "=+= Cleaning permissions ..."
27
+ docker run --rm \
28
+ --platform=linux/amd64 \
29
+ --quiet \
30
+ --volume "$OUTPUT_DIR":/output \
31
+ --entrypoint /bin/sh \
32
+ $DOCKER_IMAGE_TAG \
33
+ -c "chmod -R -f o+rwX /output/* || true"
34
+
35
+ docker volume rm "$DOCKER_NOOP_VOLUME" > /dev/null
36
+ }
37
+
38
+ chmod -R -f o+rX "$INPUT_DIR"
39
+
40
+ if [ -d "${OUTPUT_DIR}/interface_1" ]; then
41
+ chmod -f o+rwX "${OUTPUT_DIR}/interface_1"
42
+
43
+ echo "=+= Cleaning up any earlier output"
44
+ docker run --rm \
45
+ --platform=linux/amd64 \
46
+ --quiet \
47
+ --volume "${OUTPUT_DIR}/interface_1":/output \
48
+ --entrypoint /bin/sh \
49
+ $DOCKER_IMAGE_TAG \
50
+ -c "rm -rf /output/* || true"
51
+ else
52
+ mkdir -p -m o+rwX "${OUTPUT_DIR}/interface_1"
53
+ fi
54
+
55
+ docker volume create "$DOCKER_NOOP_VOLUME" > /dev/null
56
+
57
+ trap cleanup EXIT
58
+
59
+ run_docker_forward_pass() {
60
+ local interface_dir="$1"
61
+
62
+ echo "=+= Doing a forward pass on ${interface_dir}"
63
+
64
+ ## Note the extra arguments that are passed here:
65
+ # '--network none'
66
+ # entails there is no internet connection
67
+ # '--volume <NAME>:/tmp'
68
+ # is added because on the ORena FOCUS Challenge platform this directory cannot be used to store permanent files
69
+ # '--shm-size' is left at Docker's default (64 MB). A PyTorch DataLoader with
70
+ # num_workers > 0 needs more shared memory — if you use one, add e.g.
71
+ # '--shm-size=2g' here.
72
+ docker run --rm \
73
+ --platform=linux/amd64 \
74
+ --network none \
75
+ "${GPU_FLAGS[@]}" \
76
+ --volume "${INPUT_DIR}/${interface_dir}":/input:ro \
77
+ --volume "${OUTPUT_DIR}/${interface_dir}":/output \
78
+ --volume "$DOCKER_NOOP_VOLUME":/tmp \
79
+ "$DOCKER_IMAGE_TAG"
80
+
81
+ echo "=+= Wrote results to ${OUTPUT_DIR}/${interface_dir}"
82
+ }
83
+
84
+ run_docker_forward_pass "interface_1"
85
+
86
+ echo "=+= Save this image for uploading via ./do_save.sh"
pipelines/Tr006_V2/docker/segment-algorithm/inference.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Dummy algorithm for the ORena SAVE FOCUS challenge — SEGMENT track.
3
+
4
+ Replace the marked blocks with your model. Every spot to change carries a ``TODO``.
5
+
6
+ What one run receives
7
+ ---------------------
8
+ A single container run answers a **batch of questions**.
9
+ Inputs are mounted read-only at ``/input``:
10
+
11
+ request.json a LIST of focus.Request objects — one per question
12
+ plain/<qID>.mp4 the video clip for question <qID>
13
+ overlayed/<qID>.mp4 the same clip with an HH:MM:SS timestamp burned in
14
+ FO_definitions.json the foreign-object class definitions, as plain text
15
+ batch.json a convenience index of the batch (qIDs and layout)
16
+
17
+ and one file is written to ``/output``:
18
+
19
+ answer.json a LIST of focus.Response objects — one per question
20
+
21
+ ``request.json`` is authoritative: iterate it, and answer every ``qID`` it lists.
22
+
23
+ Each question carries its own clip
24
+ ----------------------------------
25
+ Every question has a **separate video file, already trimmed to that question's
26
+ window**, and no two questions in a batch come from the same source video. Your
27
+ answer to one question can therefore never depend on another question's footage.
28
+
29
+ Two consequences for your pipeline:
30
+
31
+ * ``request.start_time`` / ``end_time`` refer to the **original, untrimmed
32
+ procedure video** — the same timeline shown in the overlay variant. The clip you
33
+ are given has already been cut to that window, so decode the clip from its own
34
+ beginning; do **not** seek to ``start_time`` inside it. ``request.duration``
35
+ still tells you how long the window is.
36
+ * There is nothing to share between questions — no common video to cache or encode
37
+ once. Each clip is decoded on its own.
38
+
39
+ Both variants show the same footage. Use the plain clip, the overlay, or both.
40
+
41
+ Clip encoding
42
+ -------------
43
+ Clips are H.264 MP4 at exactly **5 fps** (one frame every 0.2 s), height-normalised
44
+ to at most 576 px. Widths follow the source's aspect ratio (e.g., 1024x576, 720x576 or
45
+ 640x360) — do not assume a fixed resolution. A
46
+ keyframe every 5 s keeps seeking cheap. The overlay clock shows the source
47
+ timeline's second, floored.
48
+
49
+ Load your model once
50
+ --------------------
51
+ Model loading, imports and warm-up happen **once per container run**, not once per
52
+ question. The latency budget is built around that, so keep model loading outside
53
+ the per-question loop — as this file does. This is the single most important thing
54
+ to get right for speed.
55
+
56
+ The latency budget
57
+ ------------------
58
+ The platform measures the execution time of the whole run itself; the ``latency``
59
+ field you write into each Response is informational and is not used for scoring.
60
+
61
+ The budget is pooled across the batch. For a batch of B questions:
62
+
63
+ allowed = 120 s + B x 15 s
64
+ ^setup ^per-question processing (the SEGMENT budget)
65
+
66
+ Going over does not fail the run outright. Instead, a share of your questions is
67
+ **forfeited** (scored incorrect), growing with the overrun: a small overrun costs a
68
+ few questions, and exceeding the budget by 20% forfeits the whole batch.
69
+
70
+ The forfeited questions are selected for you — not necessarily the slow ones — so an
71
+ overrun costs you questions you would have answered correctly. If you are running out
72
+ of budget, answer the rest quickly rather than overrun.
73
+
74
+ Keep going when a question fails
75
+ --------------------------------
76
+ One bad question should not cost you the batch. Catch errors per question, still
77
+ emit a Response for that ``qID``, and carry on — as the loop below does. A question
78
+ with no answer is simply scored incorrect.
79
+
80
+ Frame sampling
81
+ --------------
82
+ The clip is decoded with `decord <https://pypi.org/project/decord/>`_ using a single
83
+ batched read of exactly 80 evenly spaced frame indices. The pixels come from the
84
+ trimmed clip, while their timestamps are reconstructed on the original timeline
85
+ from ``Request.start_time``. Decoding and processor work count towards latency.
86
+
87
+ Trying it out
88
+ -------------
89
+ A sample batch (``test/input/interface_1/``) ships with the template. This package
90
+ stores the merged Tr006 checkpoint under ``resources/model``; runtime inference is
91
+ fully local and does not download model files.
92
+
93
+ ./do_test_run.sh # build the image and run it on the sample batch
94
+ ./do_save.sh # build and save a .tar.gz for upload
95
+ """
96
+
97
+ import json
98
+ import logging
99
+ import sys
100
+ import time
101
+ from pathlib import Path
102
+
103
+ import numpy as np
104
+ import torch
105
+
106
+ # decord must be imported AFTER torch: importing it first breaks CUDA
107
+ # initialisation (a known decord issue — torch.cuda init then fails with
108
+ # "random_device could not be read" or a segfault).
109
+ import decord
110
+ from focus import Request, Response, load_requests, save_items
111
+ from transformers import AutoProcessor, Qwen3_5ForConditionalGeneration
112
+
113
+ logging.basicConfig(
114
+ stream=sys.stdout,
115
+ level=logging.DEBUG,
116
+ format="%(asctime)s [%(levelname)s] %(message)s",
117
+ datefmt="%Y-%m-%d %H:%M:%S",
118
+ )
119
+ log = logging.getLogger(__name__)
120
+
121
+ RESOURCES_PATH = Path(__file__).parent / "resources"
122
+ MODEL_PATH = RESOURCES_PATH / "model"
123
+
124
+ INPUT_PATH = Path("/input")
125
+ OUTPUT_PATH = Path("/output")
126
+ N_FRAMES = 80
127
+ MAX_NEW_TOKENS = 64
128
+ USE_OVERLAY = False # The trained Tr006 V2 model used plain frames + metadata.
129
+ VIDEO_DIR = INPUT_PATH / ("overlayed" if USE_OVERLAY else "plain")
130
+
131
+ # Keep this prompt byte-for-byte aligned with the Tr006 V2 fixed80 training
132
+ # manifest. The official package supplies FO_definitions separately; it is
133
+ # deliberately not concatenated to the model prompt because the training rows
134
+ # used this system message and the question only.
135
+ SYSTEM_PROMPT = (
136
+ "You are a surgical video analysis assistant. You are shown a short clip from a laparoscopic procedure and asked a single question about it.\n\n"
137
+ "The clip is given as a sequence of frames in chronological order. Each frame is preceded by its timestamp in the source video, written as "
138
+ "<SECONDS seconds> -- a frame marked <1234.5 seconds> was taken 1234.5 seconds (00:20:34) after the video began. Consecutive frames may be several seconds "
139
+ "apart, so an event can happen between two of them.\n\n"
140
+ "A foreign object (FO) is any object fully introduced into the patient's body\n"
141
+ "cavity during surgery that must be retrieved or accounted for. Importantly,\n"
142
+ "standard surgical instruments that remain connected to the external environment\n"
143
+ "(e.g., graspers, scissors, trocars, staplers, cameras) are not considered foreign\n"
144
+ "objects. Furthermore, we exclude detachable parts of surgical instruments,\n"
145
+ "particularly anvil components of staplers.\n\n"
146
+ "The foreign object classes are exactly: Sponge, Clip, Specimen Bag, Silicone Loop, External Drain, Needle, Gallstone, Specimen, Mesh, Absorbable Hemostatic Agent.\n\n"
147
+ "Reply with the answer and nothing else -- no reasoning, no preamble, no\n"
148
+ "explanation, no restating the question. A single short line.\n\n"
149
+ "Rules for the answer:\n"
150
+ "- Write the value only. No sentence, no explanation, no units, no trailing\n"
151
+ " period, and never repeat the question.\n"
152
+ "- Asks yes or no -> write exactly: yes or no\n"
153
+ "- Asks how many / for a count -> write digits only, e.g. 0 or 1 or 2.\n"
154
+ "- Asks which foreign object class(es) -> write class names exactly as spelled\n"
155
+ " in the list above, comma-separated (e.g. Clip, Sponge), or exactly: none\n"
156
+ " Never answer with a generic description such as \"surgical instrument\".\n"
157
+ "- Asks when something happens -> write hh:mm:ss, read off the frame timestamps.\n"
158
+ " Those timestamps count from the START OF THE VIDEO, not from the start of this\n"
159
+ " clip, so <1234.5 seconds> is 00:20:34. Convert carefully: 3600 seconds is one\n"
160
+ " hour, 60 seconds is one minute. If the event falls between two frames, give\n"
161
+ " your best estimate between their timestamps rather than snapping to one.\n"
162
+ " For several time points, separate them with commas: 00:09:19, 00:12:44\n"
163
+ "- Asks for a percentage or a share -> write the number only, e.g. 40\n"
164
+ "- Lists options to choose from -> copy exactly one of those options, verbatim.\n"
165
+ "- Anything else -> a short phrase, at most a few words.\n\n"
166
+ "If you are unsure, still commit to your single best answer in the required\n"
167
+ "form. An empty, hedged, or explanatory answer is scored as wrong.\n"
168
+ )
169
+
170
+
171
+ def log_environment(device: torch.device) -> None:
172
+ """Report the CUDA stack this container ended up with — once, at startup.
173
+
174
+ A torch build carrying no kernels for the GPU it was handed is the classic
175
+ reason a run dies on the platform, and it stays invisible until the first
176
+ CUDA call. Printing this makes a failed job diagnosable from the logs alone.
177
+ Keep it: it costs milliseconds, once per run.
178
+ """
179
+ log.info("--- Environment ---")
180
+ log.info(" torch : %s (built against CUDA %s)", torch.__version__, torch.version.cuda)
181
+
182
+ # get_arch_list() is empty when no GPU is visible; the architecture flags
183
+ # compiled into the wheel can be read either way.
184
+ arch = torch.cuda.get_arch_list() or (torch._C._cuda_getArchFlags() or "").split()
185
+ log.info(" torch kernels : %s", " ".join(arch) or "unknown")
186
+
187
+ driver_file = Path("/proc/driver/nvidia/version")
188
+ log.info(
189
+ " host driver : %s",
190
+ driver_file.read_text().strip().splitlines()[0]
191
+ if driver_file.exists()
192
+ else "no NVIDIA driver visible",
193
+ )
194
+
195
+ if device.type != "cuda":
196
+ log.warning(" No GPU visible — running on CPU. The platform always provides one.")
197
+ return
198
+
199
+ capability = torch.cuda.get_device_capability(0)
200
+ free, total = torch.cuda.mem_get_info(0)
201
+ log.info(" GPU : %s", torch.cuda.get_device_name(0))
202
+ log.info(" capability : %s (sm_%d%d)", capability, *capability)
203
+ log.info(" VRAM : %.1f GiB free of %.1f GiB", free / 1024**3, total / 1024**3)
204
+
205
+
206
+ def clip_path_for(req: Request) -> Path:
207
+ """The video clip belonging to this question.
208
+
209
+ Every question has exactly one clip, named after its ``qID`` and already cut to
210
+ that question's ``[start_time, end_time]`` window.
211
+ """
212
+ return VIDEO_DIR / f"{req.qID}.mp4"
213
+
214
+
215
+ def your_preprocessing(video_path: Path, req: Request) -> tuple[np.ndarray, dict]:
216
+ """Decode exactly ``N_FRAMES`` and attach original-timeline millisecond PTS.
217
+
218
+ Official clips are already trimmed and encoded at 5fps. The local frame
219
+ indices are therefore sampled across the whole clip, while ``frames_indices``
220
+ are rebuilt from Request.start_time so Qwen's processor emits absolute
221
+ ``<... seconds>`` anchors, matching the Tr006 fixed80 training contract.
222
+ """
223
+ log.debug("Decoding %s (size on disk: %d bytes)", video_path, video_path.stat().st_size)
224
+
225
+ vr = decord.VideoReader(str(video_path), ctx=decord.cpu(0), num_threads=1)
226
+ base_fps = float(vr.get_avg_fps())
227
+ n_frames = len(vr)
228
+ if n_frames <= 0 or base_fps <= 0:
229
+ raise RuntimeError(f"invalid video stream: n_frames={n_frames}, fps={base_fps}")
230
+ # np.linspace + round mirrors the fixed-frame preprocessing and supports
231
+ # short clips by repeating the nearest decoded frame.
232
+ indices = np.linspace(0, n_frames - 1, N_FRAMES).round().astype(np.int64)
233
+ log.debug("Video: %d frames @ %.3f fps; sampling %d across %.2f s", n_frames, base_fps, N_FRAMES, n_frames / base_fps)
234
+ frames = vr.get_batch(indices).asnumpy() # (T, H, W, 3) uint8 RGB — one batched decode
235
+ del vr
236
+
237
+ # Qwen3's processor averages timestamps per temporal patch (2 frames), so
238
+ # metadata must have one entry per decoded frame and use the absolute source
239
+ # timeline. The request endpoint is in seconds; the training manifest used
240
+ # the same millisecond grid and ``precomputed_absolute_pts`` backend.
241
+ absolute_ms = np.rint((float(req.start_time) + indices / base_fps) * 1000.0).astype(np.int64)
242
+ metadata = {
243
+ "fps": 1000,
244
+ "frames_indices": absolute_ms.tolist(),
245
+ "total_num_frames": int(absolute_ms.max()) + 1,
246
+ "width": int(frames.shape[2]),
247
+ "height": int(frames.shape[1]),
248
+ "duration": (int(absolute_ms.max()) + 1) / 1000.0,
249
+ "video_backend": "precomputed_absolute_pts",
250
+ }
251
+ return frames, metadata
252
+
253
+
254
+ def answer_one(model, processor, req: Request) -> str:
255
+ """Run the merged Qwen3.5 checkpoint on one official request."""
256
+ frames, metadata = your_preprocessing(clip_path_for(req), req)
257
+ messages = [
258
+ {"role": "system", "content": SYSTEM_PROMPT},
259
+ {"role": "user", "content": [{"type": "video"}, {"type": "text", "text": req.question}]},
260
+ ]
261
+ text = processor.apply_chat_template(
262
+ messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
263
+ )
264
+ inputs = processor(
265
+ text=[text],
266
+ videos=[frames],
267
+ video_metadata=[metadata],
268
+ do_sample_frames=False,
269
+ return_tensors="pt",
270
+ )
271
+ model_device = next(model.parameters()).device
272
+ inputs = inputs.to(model_device)
273
+ with torch.inference_mode():
274
+ generated = model.generate(**inputs, max_new_tokens=MAX_NEW_TOKENS, do_sample=False)
275
+ new_tokens = generated[:, inputs["input_ids"].shape[1] :]
276
+ answer = processor.batch_decode(
277
+ new_tokens, skip_special_tokens=True, clean_up_tokenization_spaces=False
278
+ )[0].strip()
279
+ # The official evaluator consumes the content verbatim; only remove the
280
+ # optional reasoning wrapper if a checkpoint emits it despite disabled
281
+ # thinking, preserving all answer text otherwise.
282
+ if "</think>" in answer:
283
+ answer = answer.split("</think>", 1)[1].strip()
284
+ return answer
285
+
286
+
287
+ def run() -> int:
288
+ t_start = time.monotonic()
289
+ log.info("=== ORena SAVE FOCUS — inference start ===")
290
+
291
+ # ── Device ────────────────────────────────────────────────────────────────
292
+ # On the platform you get one GPU, chosen by you when you create the algorithm:
293
+ # an NVIDIA L40S (48 GB VRAM, 4 vCPUs, 32 GiB RAM) or an NVIDIA RTX PRO 6000
294
+ # Blackwell (96 GB VRAM, 8 vCPUs, 64 GiB RAM). This image runs on both — see
295
+ # the hardware table in the README.
296
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
297
+ log.info("Device: %s", device)
298
+ log_environment(device)
299
+
300
+ # ── Inputs ─────────────────────────────────────────────────────────────────
301
+ log.info("--- Loading inputs ---")
302
+ for name in ("batch.json", "request.json", "FO_definitions.json"):
303
+ p = INPUT_PATH / name
304
+ status = f"{p.stat().st_size} bytes" if p.exists() else "MISSING"
305
+ log.info(" %-24s %s", name, status)
306
+ log.info(" %-24s %s", f"{VIDEO_DIR.name}/", f"{len(list(VIDEO_DIR.glob('*.mp4')))} clip(s)" if VIDEO_DIR.is_dir() else "MISSING")
307
+
308
+ # request.json holds the whole batch: answer every question it lists.
309
+ requests = load_requests(INPUT_PATH / "request.json")
310
+ if not requests:
311
+ log.error("request.json contains no requests")
312
+ return 1
313
+ log.info("Batch of %d question(s)", len(requests))
314
+
315
+ # FO_definitions.json holds plain text, JSON-encoded; json.loads() gives the
316
+ # text back. The definitions are the same for every question.
317
+ fo_definitions = json.loads((INPUT_PATH / "FO_definitions.json").read_text())
318
+ log.info("FO definitions: %d chars", len(fo_definitions))
319
+
320
+ # ── Model — loaded ONCE for the whole batch ────────────────────────────────
321
+ # Keep this outside the per-question loop. Loading the model is paid once per
322
+ # run, and the latency budget grants a setup allowance to cover it.
323
+ log.info("--- Loading model (once for the batch) ---")
324
+ if not MODEL_PATH.exists():
325
+ log.error("Merged model directory not found: %s", MODEL_PATH)
326
+ return 1
327
+ log.info("Merged model path: %s", MODEL_PATH)
328
+ processor = AutoProcessor.from_pretrained(MODEL_PATH, local_files_only=True)
329
+ model = Qwen3_5ForConditionalGeneration.from_pretrained(
330
+ MODEL_PATH,
331
+ torch_dtype=torch.bfloat16,
332
+ device_map="auto",
333
+ low_cpu_mem_usage=True,
334
+ local_files_only=True,
335
+ )
336
+ model.eval()
337
+ n_params = sum(p.numel() for p in model.parameters())
338
+ log.info("Model loaded and set to eval mode: %d parameters (setup %.2f s)", n_params, time.monotonic() - t_start)
339
+
340
+ if device.type == "cuda":
341
+ allocated = torch.cuda.memory_allocated(0) / 1024**2
342
+ reserved = torch.cuda.memory_reserved(0) / 1024**2
343
+ log.info(" VRAM after model load: %.1f MiB allocated / %.1f MiB reserved", allocated, reserved)
344
+
345
+ # ── Inference — one pass per question ──────────────────────────────────────
346
+ log.info("--- Running inference over %d question(s) ---", len(requests))
347
+ responses = []
348
+ n_failed = 0
349
+ t_batch = time.monotonic()
350
+
351
+ for i, req in enumerate(requests, start=1):
352
+ log.info(
353
+ "[%d/%d] qID=%s videoID=%s window=[%.2f, %.2f] s (%.2f s)",
354
+ i, len(requests), req.qID, req.videoID, req.start_time, req.end_time, req.duration,
355
+ )
356
+ t0 = time.monotonic()
357
+ try:
358
+ answer = answer_one(model, processor, req)
359
+ except Exception:
360
+ # Keep going: one failed question should not cost you the rest of the
361
+ # batch. An empty answer is simply scored incorrect.
362
+ n_failed += 1
363
+ log.exception("[%d/%d] qID=%s failed; emitting empty answer", i, len(requests), req.qID)
364
+ answer = ""
365
+ latency = time.monotonic() - t0
366
+
367
+ # Informational only. The platform times the run itself and scores against
368
+ # that measurement — see "The latency budget" in the module docstring.
369
+ responses.append(Response(qID=req.qID, content=answer, latency=latency))
370
+ log.info("[%d/%d] qID=%s answered in %.4f s: %r", i, len(requests), req.qID, latency, answer)
371
+
372
+ batch_seconds = time.monotonic() - t_batch
373
+ log.info(
374
+ "Inference complete: %d answered (%d failed) in %.2f s (%.3f s/question)",
375
+ len(responses), n_failed, batch_seconds, batch_seconds / max(len(responses), 1),
376
+ )
377
+
378
+ if device.type == "cuda":
379
+ allocated = torch.cuda.memory_allocated(0) / 1024**2
380
+ log.debug(" VRAM after inference: %.1f MiB allocated", allocated)
381
+
382
+ # ── Output ─────────────────────────────────────────────────────────────────
383
+ log.info("--- Writing output ---")
384
+ OUTPUT_PATH.mkdir(parents=True, exist_ok=True)
385
+ output_path = OUTPUT_PATH / "answer.json"
386
+ save_items(responses, output_path)
387
+ log.info("Wrote %d response(s) to %s", len(responses), output_path)
388
+ log.info("=== inference done in %.2f s total ===", time.monotonic() - t_start)
389
+ return 0
390
+
391
+
392
+ if __name__ == "__main__":
393
+ raise SystemExit(run())
pipelines/Tr006_V2/docker/segment-algorithm/requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pins the torch already in the base image — the recommended CUDA 12.8 build (see
2
+ # the Dockerfile for why it is the default). Nothing is downloaded: pip treats the
3
+ # preinstalled build as satisfying this. Its only job is to turn "a dependency of
4
+ # mine wants a different torch" into an immediate, readable pip error naming that
5
+ # dependency, rather than a silent swap to the CUDA 13.0 wheel that cannot run on
6
+ # the L40S. Changing torch is allowed — if you do, edit this pin knowingly, and the
7
+ # Dockerfile's guard will tell you whether the result still suits the GPU you target.
8
+ torch==2.11.0
9
+
10
+ orena-focus>=0.3.4
11
+
12
+ # Direct imports of inference.py. They also arrive as transitive dependencies
13
+ # of orena-focus, but anything imported directly is declared explicitly.
14
+ decord>=0.6.0
15
+ numpy>=1.26.0
16
+
17
+ # Qwen3.5 support (including Qwen3_5ForConditionalGeneration and the
18
+ # timestamp-aware Qwen3VLProcessor) landed after the 4.x line. The merged
19
+ # checkpoint was produced and verified with this exact version.
20
+ transformers==5.15.0
21
+ accelerate>=1.10.1
pipelines/Tr006_V2/docker/segment-algorithm/resources/README.md ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # `resources/` — your model lives here
2
+
3
+ Everything in this folder is **copied into the container image** at build time and
4
+ is available at inference. This is where your trained model belongs.
5
+
6
+ | File | What it is | Replace it? |
7
+ |:--|:--|:--|
8
+ | `model.py` | the model definition (`DummyModel`, a trivial CNN) | **yes** — with your architecture |
9
+ | `dummy_weights.pt` | a tiny checkpoint so the template runs out of the box | **yes** — with your trained weights |
10
+
11
+ `inference.py` imports from here:
12
+
13
+ ```python
14
+ from resources.model import DummyModel
15
+
16
+ WEIGHTS_PATH = RESOURCES_PATH / "dummy_weights.pt"
17
+ ```
18
+
19
+ Keep that wiring, or point it at whatever you name your own files.
20
+
21
+ ## Things to get right
22
+
23
+ **There is no internet at inference time.** The container runs with networking
24
+ disabled, so nothing can be downloaded at runtime — no model hubs, no checkpoints,
25
+ no tokenizer files, no fonts. Everything your model touches must be inside the
26
+ image. If you use a library that lazily fetches weights on first call (many
27
+ `transformers`, `timm`, and `open_clip` entry points do), pre-download during the
28
+ Docker build and load from a local path instead.
29
+
30
+ **Load your model once.** One container run answers a whole batch of questions, so
31
+ the checkpoint is loaded once and reused across every question in that batch. Keep
32
+ model loading outside the per-question loop in `inference.py` — that is where the
33
+ setup allowance in the latency budget comes from.
34
+
35
+ **Large checkpoints need splitting.** A single image layer cannot exceed **50 GB**,
36
+ and each Docker `COPY` is one layer. If your weights are bigger than that, split
37
+ them into chunks, copy them with several `COPY` instructions, and reassemble at
38
+ runtime — see the comment in the `Dockerfile`.
39
+
40
+ **Big weights make big images.** The whole image is pulled before your code starts.
41
+ That pull is infrastructure overhead and is not charged against your latency
42
+ budget, but it does slow every submission cycle, so keep the image only as large as
43
+ it needs to be.
pipelines/Tr006_V2/docker/segment-algorithm/resources/dummy_weights.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:907b28b86b4d3262750ad946ab0384a40887e239b5be4027a53592817206dc2a
3
+ size 26973
pipelines/Tr006_V2/docker/segment-algorithm/resources/model.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+
3
+
4
+ class DummyModel(nn.Module):
5
+ """Minimal CNN for verifying GPU execution. Replace with your real model."""
6
+
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.features = nn.Sequential(
10
+ nn.Conv2d(3, 16, kernel_size=5, padding=1),
11
+ nn.ReLU(inplace=True),
12
+ nn.AdaptiveAvgPool2d(8),
13
+ nn.Conv2d(16, 32, kernel_size=3, padding=1),
14
+ nn.ReLU(inplace=True),
15
+ nn.AdaptiveAvgPool2d(1),
16
+ )
17
+ self.classifier = nn.Linear(32, 2)
18
+
19
+ def forward(self, x):
20
+ return self.classifier(self.features(x).flatten(1))
pipelines/Tr006_V2/docker/segment-algorithm/resources/model_provenance.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_family": "Qwen3.5-9B",
3
+ "weight_form": "merged_lora",
4
+ "lora_merged": true,
5
+ "source_base": "/cpfs01/projects-HDD/cfff-906dc71fafda_HDD/gbw_21307130160/cache/modelscope/models/Qwen--Qwen3.5-9B/snapshots/master",
6
+ "source_adapter_checkpoint": "/cpfs01/projects-HDD/cfff-906dc71fafda_HDD/gbw_21307130160/challenges/FARM-ORENA/experiments/Qwen3.5-9B/Tr/Tr006_offline_fixed80_absmeta_v2_lora64_bs4_10ep_20260828/results/checkpoint_archive/checkpoint-6013",
7
+ "source_step": 6013,
8
+ "source_epoch": 7.0,
9
+ "merge_report": "model/merge_report.json",
10
+ "merge_validation": {
11
+ "remaining_lora_modules_after_merge": 0,
12
+ "adapted_components": ["language_model", "visual", "visual_merger"]
13
+ },
14
+ "runtime_model_class": "Qwen3_5ForConditionalGeneration",
15
+ "runtime_processor_class": "Qwen3VLProcessor",
16
+ "runtime_transformers": "5.15.0"
17
+ }
pipelines/Tr006_V2/docker/segment-algorithm/test/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ output/
pipelines/Tr006_V2/expected_outputs/v1/provenance.json ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "fixed_frames_v1",
3
+ "created_at_utc": "2026-08-26T23:09:17.066162+00:00",
4
+ "command": "/cpfs01/projects-HDD/cfff-906dc71fafda_HDD/gbw_21307130160/challenges/FARM-ORENA/scripts/preprocess_segmentqa_fixed_frames.py --data-root /cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge --output-dir /cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/derived/orena_segmentqa_fixed80_420x224_absmeta_v1/clean --splits train test --frames 80 --width 420 --height 224 --workers 8 --resume",
5
+ "inputs": {
6
+ "heico-focus-vqa": {
7
+ "train": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/heico-focus-vqa/data/segment/train.parquet",
8
+ "test": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/heico-focus-vqa/data/segment/test.parquet",
9
+ "videos": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/heico-focus-vqa/videos"
10
+ },
11
+ "lapchole-focus-vqa": {
12
+ "train": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/lapchole-focus-vqa/data/segment/train.parquet",
13
+ "test": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/lapchole-focus-vqa/data/segment/test.parquet",
14
+ "videos": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/lapchole-focus-vqa/videos"
15
+ }
16
+ },
17
+ "splits": [
18
+ "train",
19
+ "test"
20
+ ],
21
+ "question_count": 20000,
22
+ "unique_segment_count": 18478,
23
+ "failed_count": 0,
24
+ "frame_count": 80,
25
+ "sampling_rule": "uniform PTS bin centers in [start,end), nearest real source PTS",
26
+ "resize": "proportional letterbox 420x224",
27
+ "encoding": {
28
+ "container": "mp4",
29
+ "codec": "H.264/libx264",
30
+ "audio": "none",
31
+ "pix_fmt": "yuv420p"
32
+ },
33
+ "metadata_contract": {
34
+ "fps": 1000,
35
+ "frames_indices": "round(abs_pts*1000)",
36
+ "total_num_frames": "max(frames_indices)+1",
37
+ "width": 420,
38
+ "height": 224,
39
+ "duration": "total_num_frames/fps",
40
+ "video_backend": "precomputed_absolute_pts"
41
+ },
42
+ "system_prompt_source": {
43
+ "author_repo_commit": "3fc0850f9f76261c529e75c78ad2272558ff9e4e",
44
+ "source": "https://github.com/AzJ24/Orena/blob/3fc0850f9f76261c529e75c78ad2272558ff9e4e/orena_sft/prompts.py",
45
+ "function": "build_system_prompt",
46
+ "parameters": {
47
+ "include_definitions": false,
48
+ "style": "direct",
49
+ "track": "segment"
50
+ },
51
+ "foreign_objects_source": "orena-focus 0.3.4 wheel: focus/foreign_objects.py"
52
+ },
53
+ "system_prompt": "You are a surgical video analysis assistant. You are shown a short clip from a laparoscopic procedure and asked a single question about it.\n\nThe clip is given as a sequence of frames in chronological order. Each frame is preceded by its timestamp in the source video, written as <SECONDS seconds> -- a frame marked <1234.5 seconds> was taken 1234.5 seconds (00:20:34) after the video began. Consecutive frames may be several seconds apart, so an event can happen between two of them.\n\nA foreign object (FO) is any object fully introduced into the patient's body\ncavity during surgery that must be retrieved or accounted for. Importantly,\nstandard surgical instruments that remain connected to the external environment\n(e.g., graspers, scissors, trocars, staplers, cameras) are not considered foreign\nobjects. Furthermore, we exclude detachable parts of surgical instruments,\nparticularly anvil components of staplers.\n\nThe foreign object classes are exactly: Sponge, Clip, Specimen Bag, Silicone Loop, External Drain, Needle, Gallstone, Specimen, Mesh, Absorbable Hemostatic Agent.\n\nReply with the answer and nothing else -- no reasoning, no preamble, no\nexplanation, no restating the question. A single short line.\n\nRules for the answer:\n- Write the value only. No sentence, no explanation, no units, no trailing\n period, and never repeat the question.\n- Asks yes or no -> write exactly: yes or no\n- Asks how many / for a count -> write digits only, e.g. 0 or 1 or 2.\n- Asks which foreign object class(es) -> write class names exactly as spelled\n in the list above, comma-separated (e.g. Clip, Sponge), or exactly: none\n Never answer with a generic description such as \"surgical instrument\".\n- Asks when something happens -> write hh:mm:ss, read off the frame timestamps.\n Those timestamps count from the START OF THE VIDEO, not from the start of this\n clip, so <1234.5 seconds> is 00:20:34. Convert carefully: 3600 seconds is one\n hour, 60 seconds is one minute. If the event falls between two frames, give\n your best estimate between their timestamps rather than snapping to one.\n For several time points, separate them with commas: 00:09:19, 00:12:44\n- Asks for a percentage or a share -> write the number only, e.g. 40\n- Lists options to choose from -> copy exactly one of those options, verbatim.\n- Anything else -> a short phrase, at most a few words.\n\nIf you are unsure, still commit to your single best answer in the required\nform. An empty, hedged, or explanatory answer is scored as wrong.\n",
54
+ "output_dir": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/derived/orena_segmentqa_fixed80_420x224_absmeta_v1/clean",
55
+ "resume": true,
56
+ "workers": 8,
57
+ "limit": null,
58
+ "seed": 42
59
+ }
pipelines/Tr006_V2/expected_outputs/v1/validation_report.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "pass",
3
+ "expected_frame_count": 80,
4
+ "expected_width": 420,
5
+ "expected_height": 224,
6
+ "segments": 18478,
7
+ "success": 18478,
8
+ "missing": 0,
9
+ "failures": 0,
10
+ "checked_at_utc": "2026-08-26T23:09:17.066162+00:00"
11
+ }
pipelines/Tr006_V2/expected_outputs/v2/provenance.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "fixed_frames_blueclean_v2",
3
+ "created_at_utc": "2026-08-27T03:55:02.119997+00:00",
4
+ "command": "scripts/preprocess_segmentqa_blueclean_v2.py --data-root /cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge --output-dir /cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/derived/orena_segmentqa_fixed80_420x224_absmeta_v2/clean --splits train test --frames 80 --width 420 --height 224 --workers 8 --min-train-valid-frames 16 --resume",
5
+ "data_root": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge",
6
+ "v1_dir": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/derived/orena_segmentqa_fixed80_420x224_absmeta_v1/clean",
7
+ "blue_intervals": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/heico-focus-vqa/blue_screen_temporal_annotations/data/intervals.csv",
8
+ "output_dir": "/cpfs01/projects-SSD/cfff-906dc71fafda_SSD/gbw_21307130160/dataset/challenge/derived/orena_segmentqa_fixed80_420x224_absmeta_v2/clean",
9
+ "splits": [
10
+ "train",
11
+ "test"
12
+ ],
13
+ "frame_count": 80,
14
+ "resize": "proportional letterbox 420x224",
15
+ "encoding": {
16
+ "container": "mp4",
17
+ "codec": "H.264/libx264",
18
+ "audio": "none"
19
+ },
20
+ "sampling_rule": "80 uniform bin centers over the non-blue complement inside the original [start,end)",
21
+ "min_train_valid_frames": 16,
22
+ "unaffected_copy": "cp --reflink=auto; byte-identical verification; no hardlinks",
23
+ "test_answers_used_for_decisions": false,
24
+ "resume": true,
25
+ "workers": 8,
26
+ "limit": null
27
+ }
pipelines/Tr006_V2/expected_outputs/v2/validation_report.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "pass",
3
+ "expected_frame_count": 80,
4
+ "expected_width": 420,
5
+ "expected_height": 224,
6
+ "segments_total": 18478,
7
+ "affected_temporal_overlap": 1215,
8
+ "unaffected": 17263,
9
+ "included_segments": 18478,
10
+ "excluded_train_segments": 0,
11
+ "excluded_train_questions": 0,
12
+ "train_questions": 13746,
13
+ "test_questions": 6254,
14
+ "test_unique_segments": 5661,
15
+ "actions": {
16
+ "copied": 17263,
17
+ "resampled_excluding_blue": 1214,
18
+ "retained_test_insufficient_valid": 1
19
+ },
20
+ "missing": 0,
21
+ "failures": 0,
22
+ "full_dataset_contract": true,
23
+ "checked_at_utc": "2026-08-27T03:55:02.119997+00:00"
24
+ }
pipelines/Tr006_V2/manifest/pipeline_manifest.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bundle_id": "Tr006_V2_checkpoint-6013",
3
+ "track": "segment",
4
+ "project_commit": "b66edaeba3c484c431d785f95a7fc81cffa530c0",
5
+ "code_state": "dirty; exact files included in this bundle are authoritative",
6
+ "base_model": "Qwen3.5-9B snapshots/master; not included",
7
+ "adapter_path": "weights/Tr006_V2/checkpoint-6013",
8
+ "raw_data": {
9
+ "included": false,
10
+ "required_layout": "README.md#原始数据目录契约",
11
+ "parquet_sha256": {
12
+ "heico-focus-vqa/data/segment/train.parquet": "ee6b1385a708ebe284cca5368e918bc62d72fb3bad2d9fc342d7a4fe4562d4fe",
13
+ "heico-focus-vqa/data/segment/test.parquet": "da7891b52269d27b89e21237efdaea7f51b3ee3ad0f82573eebdf6fbbf6985e9",
14
+ "lapchole-focus-vqa/data/segment/train.parquet": "f2198a516e9bb051a9071c2a80c1389fc9f283d81357d4e52f4d280f18f6eb70",
15
+ "lapchole-focus-vqa/data/segment/test.parquet": "f4522f845ceb6a3847f45d90f0bba84aa7df98abc4dd5e4cfd4fb914ceccac95"
16
+ }
17
+ },
18
+ "blue_intervals": {
19
+ "count": 323,
20
+ "sha256": "d523a529226f9bb8ec06147874a6321e1f5c201700d55a09fd8baecdb1240f03"
21
+ },
22
+ "v1_expected": {
23
+ "segments": 18478,
24
+ "frames": 80,
25
+ "width": 420,
26
+ "height": 224,
27
+ "train_jsonl_sha256": "a84cb85db66665a7b37d7827104ae053c1ef710a748b56d5d90198a71078f7c2",
28
+ "segment_manifest_sha256": "1a61c9e60647af24f7019e9fa0209aed10d63e2a0bf9d7ccb5cccd75eb3d06a6"
29
+ },
30
+ "v2_expected": {
31
+ "segments": 18478,
32
+ "affected_temporal_overlap": 1215,
33
+ "train_questions": 13746,
34
+ "test_questions": 6254,
35
+ "frames": 80,
36
+ "width": 420,
37
+ "height": 224,
38
+ "metadata_backend": "precomputed_absolute_pts",
39
+ "train_jsonl_sha256": "197800db3323b311a8ffb2c6459919d3b4db3ca1487b2f8790c7c5eacf16a4e2",
40
+ "segment_manifest_sha256": "6098c0abb1843be6ac24498914fb9c43c19d8bc4c2d034fbffb0d66456ad6414"
41
+ },
42
+ "training": {
43
+ "transformers": "5.15.0",
44
+ "ms_swift": "4.5.0.dev0",
45
+ "epochs": 10,
46
+ "selected_checkpoint_step": 6013,
47
+ "selected_checkpoint_epoch": 7.0,
48
+ "frames": 80,
49
+ "resolution": "420x224 letterbox",
50
+ "precision": "bfloat16",
51
+ "learning_rate": "1e-4",
52
+ "lora_rank": 64,
53
+ "lora_alpha": 128
54
+ },
55
+ "submission_image_snapshot": {
56
+ "image_id": "sha256:3ea66ddf64a40c9d2389f7c78326ab3728d54d6bb287b0cfa27cdee391827c5f",
57
+ "runtime_backend": "transformers Qwen3_5ForConditionalGeneration",
58
+ "actual_gpu_interface_smoke_before_submission": false
59
+ }
60
+ }
pipelines/Tr006_V2/manifest/pipeline_requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Raw blue-screen detection
2
+ opencv-python==4.11.0.86
3
+ numpy==2.4.6
4
+
5
+ # Segment fixed80 V1/V2 generation
6
+ av==18.1.0
7
+ pandas==2.2.3
8
+ Pillow==11.3.0
9
+ pyarrow
10
+
11
+ # Training/merge versions recorded for Tr006
12
+ ms-swift==4.5.0.dev0
13
+ transformers==5.15.0
14
+ peft==0.19.1
15
+ safetensors==0.8.0
pipelines/Tr006_V2/project/data_preprocessing/README.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 蓝屏时序预处理脚本说明
2
+
3
+ 本目录用于检测胃肠镜视频中的冻结蓝屏区段、细化时间边界,并将结果整理为可发布的数据集。所有检测脚本均以原始视频为只读输入,不会裁剪、覆盖或移动源视频。
4
+
5
+ ## 推荐流程
6
+
7
+ ```text
8
+ 视频目录
9
+ -> process_blue_screen_dataset.py
10
+ -> results/{intervals.csv,videos.jsonl,per_video/,evidence/,summary.json}
11
+ -> 结果验收及 validation.json
12
+ -> prepare_blue_screen_hf_release.py
13
+ -> 可上传的 HF Dataset 目录
14
+ ```
15
+
16
+ 正式批量处理优先使用 `process_blue_screen_dataset.py`。`blue_screen_detector.py`、`fast_blue_screen_detector.py` 和 `refine_blue_intervals.py` 主要用于方法验证、单视频诊断和边界复核,不应替代正式批处理入口。
17
+
18
+ ## 脚本清单
19
+
20
+ ### `process_blue_screen_dataset.py`
21
+
22
+ 正式的全数据集粗到细检测入口。
23
+
24
+ - 输入:包含 `.avi`、`.mp4`、`.mov` 或 `.mkv` 视频的目录。
25
+ - 粗检:顺序解码视频,每秒抽取一次圆形内镜 ROI,对 ROI 像素计算哈希和蓝色比例,定位连续静止蓝屏候选区段。
26
+ - 精修:只对候选区段及其前后缓冲区逐帧扫描,以原生 FPS 确定准确的开始帧和结束帧。
27
+ - 判定:ROI 解码帧哈希完全相同,同时蓝色比例达到阈值并满足最短持续时间。
28
+ - 支持 `--resume`,可复用已经完成的逐视频 JSON 结果。
29
+ - 输出:
30
+ - `intervals.csv`:每个蓝屏区段一行,是主要时序标注。
31
+ - `videos.jsonl`:每个视频一行,包含视频信息和全部区段。
32
+ - `per_video/<video_id>.json`:逐视频检查点和详细结果。
33
+ - `evidence/<video_id>/`:每个区段的前一帧、起始帧、结束帧和后一帧证据图。
34
+ - `summary.json`:全数据集汇总统计。
35
+
36
+ 正式 HeiCo 运行使用的主要参数为:1 秒粗采样、最短静止 6 秒、蓝色比例阈值 0.45、候选区段前后各扩展 5 秒、粗检尺寸 96、精修尺寸 160。
37
+
38
+ ### `fast_blue_screen_detector.py`
39
+
40
+ 单视频快速哈希检测工具,用于验证“蓝屏期间抽样帧是否完全相同”的假设。
41
+
42
+ - 顺序解码视频,但默认只每秒生成一个 ROI 哈希和蓝色比例。
43
+ - 将连续相同哈希且持续时间足够长的样本合并为粗粒度区段。
44
+ - 可通过重复传入 `--reference START END` 与用户提供的参考区段计算近似秒级 precision/recall。
45
+ - 输出单个 JSON;时间边界精度由 `--sample-seconds` 决定,不能代替逐帧精修结果。
46
+
47
+ ### `refine_blue_intervals.py`
48
+
49
+ 针对已知粗区段的单视频逐帧边界精修工具。
50
+
51
+ - 通过一个或多个 `--interval START END` 接收粗区段。
52
+ - 在粗区段前后增加 `--pad` 秒扫描范围。
53
+ - 逐帧计算圆形 ROI 蓝色比例,并通过连续确认帧抑制孤立噪声。
54
+ - 输出精修后的帧号、秒级时间、持续时长和平均蓝色比例。
55
+ - 适合人工复核或早期实验;正式批处理采用 `process_blue_screen_dataset.py` 内置的“完全重复帧哈希 + 蓝色比例”精修实现。
56
+
57
+ ### `blue_screen_detector.py`
58
+
59
+ 早期单视频候选检测工具,用于验证基于颜色和纹理的蓝屏判定方法。
60
+
61
+ - 自动估计圆形内镜 ROI,忽略外围黑色区域。
62
+ - 稀疏抽帧后计算蓝色比例、拉普拉斯纹理强度、平均色相和有效像素比例。
63
+ - 将高蓝色比例、低纹理且持续时间足够长的样本合并为候选区段。
64
+ - 输出详细 JSON 和区段 CSV。
65
+ - 该方法没有完全相同帧哈希约束,结果属于候选范围,不作为当前正式蓝屏时序标注。
66
+
67
+ ### `prepare_blue_screen_hf_release.py`
68
+
69
+ 将已经验收通过的运行结果整理为可移植的 Hugging Face Dataset 发布目录。
70
+
71
+ - 要求源运行包含 `run.json`、`results/intervals.csv`、`videos.jsonl`、`summary.json`、`validation.json` 和 `evidence/`。
72
+ - 只有 `validation.json` 状态为 `PASS` 时才允许打包。
73
+ - 删除结果中的服务器绝对视频路径,改用 `video_id` 和 `video_filename`。
74
+ - 复制边界证据图并生成便携的 provenance、summary、validation 和 release manifest。
75
+ - 对视频数、区段数和证据图数量进行一致性核验。
76
+ - 为避免误覆盖,目标输出目录已存在时会直接停止。
77
+
78
+ ## 时间区间语义
79
+
80
+ 蓝屏区段统一使用半开区间 `[start_seconds, end_seconds)`:开始时间包含在区段内,结束时间指向蓝屏结束后的第一个时间位置。逐帧结果同时提供 `start_frame` 和 `end_frame`,其中帧号边界为首尾均包含。
81
+
82
+ ## 依赖与运行环境
83
+
84
+ - Python 3
85
+ - OpenCV (`cv2`)
86
+ - NumPy
87
+
88
+ FVQA 项目中的正式运行应使用 `fvqa-harness.yaml` 指定的 Conda 环境。蓝屏检测只使用 CPU,不需要占用 GPU。
89
+
90
+ ## 当前正式结果
91
+
92
+ HeiCo 30 个视频的已验收结果位于 HF Dataset 仓库 `Gbone3176/orena-seg` 的 `blue_screen_temporal_annotations/` 子目录:共 323 个蓝屏区段和 1,291 张边界证据图。该结果通过了文件覆盖、逐帧解码、区��边界和汇总一致性检查;这里的 `PASS` 表示产物一致性验收通过,不代表基于独立人工金标准计算的检测准确率。
pipelines/Tr006_V2/project/data_preprocessing/process_blue_screen_dataset.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Coarse-to-fine frozen blue-screen detection for endoscopy videos.
3
+
4
+ Stage 1 decodes each video sequentially and hashes one circular-ROI frame per
5
+ second. Stage 2 re-reads only candidate windows and locates exact repeated-frame
6
+ runs at native FPS. Inputs are read-only; all results go below --output-dir.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import csv
12
+ import hashlib
13
+ import json
14
+ import socket
15
+ import time
16
+ from datetime import datetime, timezone
17
+ from pathlib import Path
18
+
19
+ import cv2
20
+ import numpy as np
21
+
22
+ VIDEO_SUFFIXES = {".avi", ".mp4", ".mov", ".mkv"}
23
+
24
+
25
+ def utc_now() -> str:
26
+ return datetime.now(timezone.utc).astimezone().isoformat()
27
+
28
+
29
+ def hhmmss(seconds: float) -> str:
30
+ ms = int(round(seconds * 1000))
31
+ hours, rem = divmod(ms, 3_600_000)
32
+ minutes, rem = divmod(rem, 60_000)
33
+ secs, millis = divmod(rem, 1000)
34
+ return f"{hours:02d}:{minutes:02d}:{secs:02d}.{millis:03d}"
35
+
36
+
37
+ def roi_mask(height: int, width: int) -> np.ndarray:
38
+ yy, xx = np.ogrid[:height, :width]
39
+ return (((xx - width / 2) / (width * 0.47)) ** 2 +
40
+ ((yy - height / 2) / (height * 0.47)) ** 2 <= 1)
41
+
42
+
43
+ def frame_features(frame: np.ndarray, mask: np.ndarray, size: int) -> tuple[str, float]:
44
+ small = cv2.resize(frame, (size, size), interpolation=cv2.INTER_AREA)
45
+ small_mask = cv2.resize(mask.astype(np.uint8), (size, size),
46
+ interpolation=cv2.INTER_NEAREST).astype(bool)
47
+ pixels = small[small_mask]
48
+ digest = hashlib.md5(pixels.tobytes()).hexdigest()
49
+ hsv = cv2.cvtColor(small, cv2.COLOR_BGR2HSV)
50
+ h, s, v = hsv[..., 0], hsv[..., 1], hsv[..., 2]
51
+ valid = small_mask & (v > 18)
52
+ if not valid.any():
53
+ return digest, 0.0
54
+ blue = valid & (h >= 88) & (h <= 140) & (s >= 55) & (v >= 35)
55
+ return digest, float(blue.sum() / valid.sum())
56
+
57
+
58
+ def coarse_scan(video: Path, sample_seconds: float, min_static_seconds: float,
59
+ blue_threshold: float, size: int) -> tuple[dict, list[dict]]:
60
+ cap = cv2.VideoCapture(str(video))
61
+ if not cap.isOpened():
62
+ raise RuntimeError("cannot open video")
63
+ fps = float(cap.get(cv2.CAP_PROP_FPS) or 0)
64
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
65
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 0)
66
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 0)
67
+ if fps <= 0 or frame_count <= 0 or width <= 0 or height <= 0:
68
+ cap.release()
69
+ raise RuntimeError(f"invalid metadata fps={fps} frames={frame_count} size={width}x{height}")
70
+ mask = roi_mask(height, width)
71
+ step = max(1, int(round(fps * sample_seconds)))
72
+ samples: list[dict] = []
73
+ frame_idx = 0
74
+ next_sample = 0
75
+ while True:
76
+ ok, frame = cap.read()
77
+ if not ok:
78
+ break
79
+ if frame_idx >= next_sample:
80
+ digest, ratio = frame_features(frame, mask, size)
81
+ samples.append({"frame": frame_idx, "time": frame_idx / fps,
82
+ "hash": digest, "blue_ratio": ratio})
83
+ next_sample += step
84
+ frame_idx += 1
85
+ cap.release()
86
+ if frame_idx < frame_count * 0.99:
87
+ raise RuntimeError(f"early decode stop decoded={frame_idx} metadata_frames={frame_count}")
88
+
89
+ min_samples = max(2, int(np.ceil(min_static_seconds / sample_seconds)))
90
+ candidates: list[dict] = []
91
+ run_start = 0
92
+ for i in range(1, len(samples) + 1):
93
+ if i == len(samples) or samples[i]["hash"] != samples[run_start]["hash"]:
94
+ chunk = samples[run_start:i]
95
+ mean_blue = float(np.mean([x["blue_ratio"] for x in chunk]))
96
+ if len(chunk) >= min_samples and mean_blue >= blue_threshold:
97
+ candidates.append({
98
+ "start_seconds": chunk[0]["time"],
99
+ "end_seconds": chunk[-1]["time"] + sample_seconds,
100
+ "sample_count": len(chunk),
101
+ "mean_blue_ratio": mean_blue,
102
+ "hash": chunk[0]["hash"],
103
+ })
104
+ run_start = i
105
+ metadata = {"fps": fps, "frame_count": frame_count, "width": width,
106
+ "height": height, "duration_seconds": frame_count / fps,
107
+ "decoded_frames": frame_idx, "coarse_samples": len(samples)}
108
+ return metadata, candidates
109
+
110
+
111
+ def refine_candidate(video: Path, metadata: dict, candidate: dict, pad_seconds: float,
112
+ min_static_seconds: float, blue_threshold: float,
113
+ size: int) -> list[dict]:
114
+ fps = metadata["fps"]
115
+ first = max(0, int(np.floor((candidate["start_seconds"] - pad_seconds) * fps)))
116
+ last = min(metadata["frame_count"] - 1,
117
+ int(np.ceil((candidate["end_seconds"] + pad_seconds) * fps)))
118
+ cap = cv2.VideoCapture(str(video))
119
+ cap.set(cv2.CAP_PROP_POS_FRAMES, first)
120
+ mask = roi_mask(metadata["height"], metadata["width"])
121
+ samples: list[dict] = []
122
+ frame_idx = first
123
+ while frame_idx <= last:
124
+ ok, frame = cap.read()
125
+ if not ok:
126
+ break
127
+ digest, ratio = frame_features(frame, mask, size)
128
+ samples.append({"frame": frame_idx, "hash": digest, "blue_ratio": ratio})
129
+ frame_idx += 1
130
+ cap.release()
131
+
132
+ min_frames = max(2, int(np.ceil(min_static_seconds * fps)))
133
+ runs: list[dict] = []
134
+ run_start = 0
135
+ for i in range(1, len(samples) + 1):
136
+ if i == len(samples) or samples[i]["hash"] != samples[run_start]["hash"]:
137
+ chunk = samples[run_start:i]
138
+ mean_blue = float(np.mean([x["blue_ratio"] for x in chunk]))
139
+ if len(chunk) >= min_frames and mean_blue >= blue_threshold:
140
+ start_frame = chunk[0]["frame"]
141
+ end_frame = chunk[-1]["frame"]
142
+ runs.append({
143
+ "start_frame": start_frame,
144
+ "end_frame": end_frame,
145
+ "start_seconds": start_frame / fps,
146
+ "end_seconds": (end_frame + 1) / fps,
147
+ "duration_seconds": len(chunk) / fps,
148
+ "start_time": hhmmss(start_frame / fps),
149
+ "end_time": hhmmss((end_frame + 1) / fps),
150
+ "frame_count": len(chunk),
151
+ "mean_blue_ratio": mean_blue,
152
+ "exact_repeated_hash": chunk[0]["hash"],
153
+ "confidence": "high",
154
+ })
155
+ run_start = i
156
+ return runs
157
+
158
+
159
+ def merge_duplicate_runs(runs: list[dict]) -> list[dict]:
160
+ by_key: dict[tuple[int, int], dict] = {}
161
+ for run in runs:
162
+ by_key[(run["start_frame"], run["end_frame"])] = run
163
+ return sorted(by_key.values(), key=lambda x: x["start_frame"])
164
+
165
+
166
+ def save_evidence(video: Path, intervals: list[dict], output_dir: Path, fps: float) -> None:
167
+ if not intervals:
168
+ return
169
+ output_dir.mkdir(parents=True, exist_ok=True)
170
+ cap = cv2.VideoCapture(str(video))
171
+ for index, interval in enumerate(intervals, 1):
172
+ points = {
173
+ "before": max(0, interval["start_frame"] - 1),
174
+ "start": interval["start_frame"],
175
+ "end": interval["end_frame"],
176
+ "after": interval["end_frame"] + 1,
177
+ }
178
+ evidence = {}
179
+ for label, frame_idx in points.items():
180
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
181
+ ok, frame = cap.read()
182
+ if ok:
183
+ name = f"interval_{index:03d}_{label}_f{frame_idx}.jpg"
184
+ cv2.imwrite(str(output_dir / name), frame)
185
+ evidence[label] = str((output_dir / name).name)
186
+ interval["evidence_frames"] = evidence
187
+ cap.release()
188
+
189
+
190
+ def write_aggregate(output_dir: Path, records: list[dict], started_at: str,
191
+ elapsed: float, input_dir: Path, config: dict) -> None:
192
+ intervals = []
193
+ for record in records:
194
+ for idx, interval in enumerate(record.get("intervals", []), 1):
195
+ intervals.append({"video_id": record["video_id"],
196
+ "video_path": record["video_path"],
197
+ "interval_index": idx, **interval})
198
+ fields = ["video_id", "video_path", "interval_index", "start_frame", "end_frame",
199
+ "start_seconds", "end_seconds", "duration_seconds", "start_time", "end_time",
200
+ "frame_count", "mean_blue_ratio", "confidence", "exact_repeated_hash"]
201
+ with (output_dir / "intervals.csv").open("w", newline="", encoding="utf-8") as handle:
202
+ writer = csv.DictWriter(handle, fieldnames=fields, extrasaction="ignore")
203
+ writer.writeheader(); writer.writerows(intervals)
204
+ with (output_dir / "videos.jsonl").open("w", encoding="utf-8") as handle:
205
+ for record in records:
206
+ handle.write(json.dumps(record, ensure_ascii=False) + "\n")
207
+ summary = {
208
+ "status": "completed" if all(r["status"] == "completed" for r in records) else "failed",
209
+ "started_at": started_at, "finished_at": utc_now(), "elapsed_seconds": elapsed,
210
+ "host": socket.gethostname(), "input_dir": str(input_dir), "config": config,
211
+ "video_count": len(records),
212
+ "completed_video_count": sum(r["status"] == "completed" for r in records),
213
+ "failed_video_count": sum(r["status"] == "failed" for r in records),
214
+ "videos_with_blue_screen": sum(bool(r.get("intervals")) for r in records),
215
+ "interval_count": len(intervals),
216
+ "total_video_seconds": sum(r.get("duration_seconds", 0) for r in records),
217
+ "total_blue_screen_seconds": sum(x["duration_seconds"] for x in intervals),
218
+ }
219
+ (output_dir / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
220
+
221
+
222
+ def main() -> None:
223
+ parser = argparse.ArgumentParser()
224
+ parser.add_argument("--input-dir", type=Path, required=True)
225
+ parser.add_argument("--output-dir", type=Path, required=True)
226
+ parser.add_argument("--sample-seconds", type=float, default=1.0)
227
+ parser.add_argument("--min-static-seconds", type=float, default=6.0)
228
+ parser.add_argument("--blue-ratio", type=float, default=0.45)
229
+ parser.add_argument("--pad-seconds", type=float, default=5.0)
230
+ parser.add_argument("--coarse-size", type=int, default=96)
231
+ parser.add_argument("--fine-size", type=int, default=160)
232
+ parser.add_argument("--resume", action="store_true")
233
+ args = parser.parse_args()
234
+ videos = sorted(p for p in args.input_dir.rglob("*") if p.suffix.lower() in VIDEO_SUFFIXES)
235
+ if not videos:
236
+ raise SystemExit("no videos found")
237
+ args.output_dir.mkdir(parents=True, exist_ok=True)
238
+ per_video_dir = args.output_dir / "per_video"
239
+ evidence_root = args.output_dir / "evidence"
240
+ per_video_dir.mkdir(exist_ok=True)
241
+ started_at = utc_now(); t0 = time.perf_counter()
242
+ config = {"sample_seconds": args.sample_seconds,
243
+ "min_static_seconds": args.min_static_seconds,
244
+ "blue_ratio": args.blue_ratio, "pad_seconds": args.pad_seconds,
245
+ "coarse_size": args.coarse_size, "fine_size": args.fine_size,
246
+ "definition": "exact repeated decoded ROI frames with blue ratio above threshold"}
247
+ records = []
248
+ for number, video in enumerate(videos, 1):
249
+ video_id = video.stem.split(" ", 1)[0]
250
+ result_path = per_video_dir / f"{video_id}.json"
251
+ if args.resume and result_path.exists():
252
+ records.append(json.loads(result_path.read_text(encoding="utf-8")))
253
+ print(f"[{number}/{len(videos)}] resume {video.name}", flush=True)
254
+ continue
255
+ print(f"[{number}/{len(videos)}] start {video.name} {utc_now()}", flush=True)
256
+ item_t0 = time.perf_counter()
257
+ record = {"video_id": video_id, "video_path": str(video), "status": "failed",
258
+ "started_at": utc_now(), "intervals": []}
259
+ try:
260
+ metadata, candidates = coarse_scan(video, args.sample_seconds,
261
+ args.min_static_seconds,
262
+ args.blue_ratio, args.coarse_size)
263
+ fine_runs = []
264
+ for candidate in candidates:
265
+ fine_runs.extend(refine_candidate(video, metadata, candidate,
266
+ args.pad_seconds,
267
+ args.min_static_seconds,
268
+ args.blue_ratio, args.fine_size))
269
+ fine_runs = merge_duplicate_runs(fine_runs)
270
+ save_evidence(video, fine_runs, evidence_root / video_id, metadata["fps"])
271
+ blue_seconds = sum(x["duration_seconds"] for x in fine_runs)
272
+ record.update(metadata)
273
+ record.update({"status": "completed", "coarse_candidate_count": len(candidates),
274
+ "interval_count": len(fine_runs), "blue_screen": bool(fine_runs),
275
+ "blue_screen_seconds": blue_seconds,
276
+ "blue_screen_ratio": blue_seconds / metadata["duration_seconds"],
277
+ "intervals": fine_runs})
278
+ except Exception as exc:
279
+ record["error"] = f"{type(exc).__name__}: {exc}"
280
+ record["finished_at"] = utc_now()
281
+ record["elapsed_seconds"] = time.perf_counter() - item_t0
282
+ result_path.write_text(json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")
283
+ records.append(record)
284
+ write_aggregate(args.output_dir, records, started_at,
285
+ time.perf_counter() - t0, args.input_dir, config)
286
+ print(f"[{number}/{len(videos)}] {record['status']} intervals={len(record['intervals'])} elapsed={record['elapsed_seconds']:.1f}s", flush=True)
287
+ write_aggregate(args.output_dir, records, started_at,
288
+ time.perf_counter() - t0, args.input_dir, config)
289
+ if any(r["status"] != "completed" for r in records):
290
+ raise SystemExit(2)
291
+
292
+
293
+ if __name__ == "__main__":
294
+ main()
pipelines/Tr006_V2/project/ms-swift/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
pipelines/Tr006_V2/project/ms-swift/MANIFEST.in ADDED
@@ -0,0 +1 @@
 
 
1
+ recursive-include requirements *.txt
pipelines/Tr006_V2/project/ms-swift/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ -r requirements/framework.txt
pipelines/Tr006_V2/project/ms-swift/requirements/framework.txt ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate
2
+ addict
3
+ aiohttp
4
+ attrdict
5
+ binpacking
6
+ charset_normalizer
7
+ cpm_kernels
8
+ dacite
9
+ datasets>=3.0,<4.8.5
10
+ einops
11
+ fastapi
12
+ gradio>=3.40.0,<6.0
13
+ importlib_metadata
14
+ json_repair
15
+ matplotlib
16
+ modelscope>=1.23
17
+ nltk
18
+ numpy
19
+ openai
20
+ oss2
21
+ pandas
22
+ peft>=0.11,<0.20
23
+ pillow
24
+ PyYAML>=5.4
25
+ requests
26
+ rouge
27
+ safetensors
28
+ scipy
29
+ sentencepiece
30
+ simplejson>=3.3.0
31
+ sortedcontainers>=1.5.9
32
+ tensorboard
33
+ tiktoken
34
+ tqdm
35
+ transformers>=4.33,<5.17.0
36
+ transformers_stream_generator
37
+ trl>=0.15,<1.0
38
+ uvicorn
39
+ zstandard
pipelines/Tr006_V2/project/ms-swift/requirements/swanlab.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ swanlab
pipelines/Tr006_V2/project/ms-swift/setup.cfg ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [isort]
2
+ line_length = 120
3
+ multi_line_output = 0
4
+ known_standard_library = setuptools
5
+ known_first_party = swift
6
+ known_third_party = json,yaml
7
+ no_lines_before = STDLIB,LOCALFOLDER
8
+ default_section = THIRDPARTY
9
+
10
+ [yapf]
11
+ BASED_ON_STYLE = pep8
12
+ COLUMN_LIMIT = 120
13
+ BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF = true
14
+ SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN = true
15
+ SPLIT_BEFORE_ARITHMETIC_OPERATOR = true
16
+
17
+ [codespell]
18
+ skip = *.ipynb
19
+ quiet-level = 3
20
+ ignore-words-list = patten,nd,ty,mot,hist,formating,winn,gool,datas,wan,confids
21
+
22
+ [flake8]
23
+ max-line-length = 120
24
+ select = B,C,E,F,P,T4,W,B9
25
+ ignore = F401,F403,F405,F821,W503,E251,W504,E126
26
+ exclude = docs/src,*.pyi,.git,peft.py
27
+
28
+ [darglint]
29
+ ignore=DAR101
30
+
31
+ [easy_install]
32
+ index-url=https://pypi.tuna.tsinghua.edu.cn/simple
pipelines/Tr006_V2/project/ms-swift/setup.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ # !/usr/bin/env python
3
+ from setuptools import find_packages, setup
4
+
5
+ import os
6
+ import re
7
+ from typing import List
8
+
9
+
10
+ def readme():
11
+ with open('README.md', encoding='utf-8') as f:
12
+ content = f.read()
13
+ return content
14
+
15
+
16
+ version_file = 'swift/version.py'
17
+
18
+
19
+ def get_version():
20
+ with open(version_file, 'r', encoding='utf-8') as f:
21
+ return re.search(r'^__version__\s*=\s*["\'](.+?)["\']', f.read(), re.M).group(1)
22
+
23
+
24
+ def parse_requirements(fname='requirements.txt', with_version=True):
25
+ """
26
+ Parse the package dependencies listed in a requirements file but strips
27
+ specific versioning information.
28
+
29
+ Args:
30
+ fname (str): path to requirements file
31
+ with_version (bool, default=False): if True include version specs
32
+
33
+ Returns:
34
+ List[str]: list of requirements items
35
+
36
+ CommandLine:
37
+ python -c "import setup; print(setup.parse_requirements())"
38
+ """
39
+ import re
40
+ import sys
41
+ from os.path import exists
42
+ require_fpath = fname
43
+
44
+ def parse_line(line):
45
+ """
46
+ Parse information from a line in a requirements text file
47
+ """
48
+ if line.startswith('-r '):
49
+ # Allow specifying requirements in other files
50
+ target = line.split(' ')[1]
51
+ relative_base = os.path.dirname(fname)
52
+ absolute_target = os.path.join(relative_base, target)
53
+ for info in parse_require_file(absolute_target):
54
+ yield info
55
+ else:
56
+ info = {'line': line}
57
+ if line.startswith('-e '):
58
+ info['package'] = line.split('#egg=')[1]
59
+ else:
60
+ # Remove versioning from the package
61
+ pat = '(' + '|'.join(['>=', '==', '>']) + ')'
62
+ parts = re.split(pat, line, maxsplit=1)
63
+ parts = [p.strip() for p in parts]
64
+
65
+ info['package'] = parts[0]
66
+ if len(parts) > 1:
67
+ op, rest = parts[1:]
68
+ if ';' in rest:
69
+ # Handle platform specific dependencies
70
+ # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
71
+ version, platform_deps = map(str.strip, rest.split(';'))
72
+ info['platform_deps'] = platform_deps
73
+ else:
74
+ version = rest # NOQA
75
+ info['version'] = (op, version)
76
+ yield info
77
+
78
+ def parse_require_file(fpath):
79
+ with open(fpath, 'r', encoding='utf-8') as f:
80
+ for line in f.readlines():
81
+ line = line.strip()
82
+ if line.startswith('http'):
83
+ print('skip http requirements %s' % line)
84
+ continue
85
+ if line and not line.startswith('#') and not line.startswith('--'):
86
+ for info in parse_line(line):
87
+ yield info
88
+ elif line and line.startswith('--find-links'):
89
+ eles = line.split()
90
+ for e in eles:
91
+ e = e.strip()
92
+ if 'http' in e:
93
+ info = dict(dependency_links=e)
94
+ yield info
95
+
96
+ def gen_packages_items():
97
+ items = []
98
+ deps_link = []
99
+ if exists(require_fpath):
100
+ for info in parse_require_file(require_fpath):
101
+ if 'dependency_links' not in info:
102
+ parts = [info['package']]
103
+ if with_version and 'version' in info:
104
+ parts.extend(info['version'])
105
+ if not sys.version.startswith('3.4'):
106
+ # apparently package_deps are broken in 3.4
107
+ platform_deps = info.get('platform_deps')
108
+ if platform_deps is not None:
109
+ parts.append(';' + platform_deps)
110
+ item = ''.join(parts)
111
+ items.append(item)
112
+ else:
113
+ deps_link.append(info['dependency_links'])
114
+ return items, deps_link
115
+
116
+ return gen_packages_items()
117
+
118
+
119
+ if __name__ == '__main__':
120
+ install_requires, deps_link = parse_requirements('requirements.txt')
121
+ extra_requires = {}
122
+ all_requires = []
123
+ extra_requires['megatron'], _ = parse_requirements('requirements/megatron.txt')
124
+ extra_requires['eval'], _ = parse_requirements('requirements/eval.txt')
125
+ extra_requires['swanlab'], _ = parse_requirements('requirements/swanlab.txt')
126
+ extra_requires['ray'], _ = parse_requirements('requirements/ray.txt')
127
+ all_requires.extend(install_requires)
128
+ all_requires.extend(extra_requires['eval'])
129
+ all_requires.extend(extra_requires['swanlab'])
130
+ all_requires.extend(extra_requires['ray'])
131
+ extra_requires['all'] = all_requires
132
+
133
+ setup(
134
+ name='ms_swift',
135
+ version=get_version(),
136
+ description='Swift: Scalable lightWeight Infrastructure for Fine-Tuning',
137
+ long_description=readme(),
138
+ long_description_content_type='text/markdown',
139
+ author='DAMO ModelScope teams',
140
+ author_email='contact@modelscope.cn',
141
+ keywords=['transformers', 'LLM', 'lora', 'megatron', 'grpo', 'sft'],
142
+ url='https://github.com/modelscope/ms-swift',
143
+ packages=find_packages(exclude=('tests', 'tests.*')),
144
+ include_package_data=True,
145
+ package_data={'': ['utils/*', 'dataset/data/*.*', 'config/*.json', 'loss_scale/config/*.json']},
146
+ python_requires='>=3.8.0',
147
+ classifiers=[
148
+ 'Development Status :: 4 - Beta',
149
+ 'License :: OSI Approved :: Apache Software License',
150
+ 'Operating System :: OS Independent',
151
+ 'Programming Language :: Python :: 3',
152
+ 'Programming Language :: Python :: 3.8',
153
+ 'Programming Language :: Python :: 3.9',
154
+ 'Programming Language :: Python :: 3.10',
155
+ 'Programming Language :: Python :: 3.11',
156
+ 'Programming Language :: Python :: 3.12',
157
+ ],
158
+ license='Apache License 2.0',
159
+ tests_require=parse_requirements('requirements/tests.txt'),
160
+ install_requires=install_requires,
161
+ extras_require=extra_requires,
162
+ entry_points={
163
+ 'console_scripts': ['swift=swift.cli.main:cli_main', 'megatron=swift.cli._megatron.main:cli_main']
164
+ },
165
+ dependency_links=deps_link,
166
+ zip_safe=False)
pipelines/Tr006_V2/project/ms-swift/swift/__init__.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ from typing import TYPE_CHECKING
3
+
4
+ from .utils.import_utils import _LazyModule
5
+
6
+ if TYPE_CHECKING:
7
+ from .agent_template import BaseAgentTemplate, agent_template_map
8
+ from .arguments import (AppArguments, BaseArguments, DeployArguments, EvalArguments, ExportArguments,
9
+ InferArguments, PretrainArguments, RLHFArguments, RolloutArguments, SamplingArguments,
10
+ SftArguments)
11
+ from .callbacks import TrainerCallback, callbacks_map
12
+ from .dataset import EncodePreprocessor, load_dataset
13
+ from .infer_engine import (AdapterRequest, GRPOVllmEngine, InferClient, InferEngine, InferRequest, LmdeployEngine,
14
+ RequestConfig, SglangEngine, TransformersEngine, VllmEngine)
15
+ from .loss import BaseLoss, loss_map
16
+ from .loss_scale import ALL_BASE_STRATEGY, ConfigLossScale, LossScale, get_loss_scale, loss_scale_map
17
+ from .metrics import InferStats, MeanMetric, eval_metrics_map
18
+ from .model import get_model_processor, get_processor
19
+ from .optimizers import OptimizerCallback, optimizers_map
20
+ from .pipelines import (app_main, deploy_main, eval_main, export_main, infer_main, merge_lora, pretrain_main,
21
+ rlhf_main, rollout_main, run_deploy, sampling_main, sft_main)
22
+ from .template import get_template
23
+ from .trainers import Seq2SeqTrainer, Seq2SeqTrainingArguments, Trainer, TrainingArguments
24
+ from .tuner_plugin import PeftTuner, Tuner, tuners_map
25
+ from .tuners import Swift
26
+ from .utils import get_logger, safe_snapshot_download
27
+ from .version import __release_datetime__, __version__
28
+ else:
29
+ _import_structure = {
30
+ 'version': ['__release_datetime__', '__version__'],
31
+ 'tuners': ['Swift'],
32
+ 'tuner_plugin': ['Tuner', 'PeftTuner', 'tuners_map'],
33
+ 'infer_engine': [
34
+ 'TransformersEngine', 'VllmEngine', 'SglangEngine', 'LmdeployEngine', 'InferRequest', 'RequestConfig',
35
+ 'AdapterRequest', 'InferEngine', 'InferClient', 'GRPOVllmEngine'
36
+ ],
37
+ 'trainers': ['TrainingArguments', 'Seq2SeqTrainingArguments', 'Trainer', 'Seq2SeqTrainer'],
38
+ 'arguments': [
39
+ 'PretrainArguments', 'SftArguments', 'RLHFArguments', 'ExportArguments', 'InferArguments', 'AppArguments',
40
+ 'EvalArguments', 'SamplingArguments', 'RolloutArguments', 'DeployArguments', 'BaseArguments'
41
+ ],
42
+ 'pipelines': [
43
+ 'sft_main', 'pretrain_main', 'infer_main', 'rlhf_main', 'export_main', 'app_main', 'eval_main',
44
+ 'sampling_main', 'rollout_main', 'deploy_main', 'merge_lora', 'run_deploy'
45
+ ],
46
+ 'model': ['get_model_processor', 'get_processor'],
47
+ 'template': ['get_template'],
48
+ 'dataset': ['load_dataset', 'EncodePreprocessor'],
49
+ 'utils': ['get_logger', 'safe_snapshot_download'],
50
+ 'agent_template': ['agent_template_map', 'BaseAgentTemplate'],
51
+ 'loss': ['loss_map', 'BaseLoss'],
52
+ 'metrics': ['eval_metrics_map', 'InferStats', 'MeanMetric'],
53
+ 'optimizers': ['optimizers_map', 'OptimizerCallback'],
54
+ 'callbacks': ['callbacks_map', 'TrainerCallback'],
55
+ 'loss_scale': ['loss_scale_map', 'LossScale', 'get_loss_scale', 'ALL_BASE_STRATEGY', 'ConfigLossScale'],
56
+ }
57
+
58
+ import sys
59
+
60
+ sys.modules[__name__] = _LazyModule(
61
+ __name__,
62
+ globals()['__file__'],
63
+ _import_structure,
64
+ module_spec=__spec__,
65
+ extra_objects={},
66
+ )
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ from .base import BaseAgentTemplate
3
+ from .mapping import agent_template_map
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/base.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ """
3
+ Agent template module for handling tool calling and function execution.
4
+
5
+ This module provides base classes and utilities for creating agent templates
6
+ that support tool calling in conversational AI systems. It includes support
7
+ for various agent formats like ReAct, function calling, and parallel execution.
8
+ """
9
+ import ast
10
+ import json
11
+ from abc import ABC, abstractmethod
12
+ from dataclasses import asdict, dataclass
13
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Union
14
+
15
+ from swift.infer_engine import Function
16
+ from swift.template import Prompt, split_str_parts_by
17
+
18
+
19
+ @dataclass
20
+ class AgentKeyword:
21
+ action: str = 'Action:'
22
+ action_input: str = 'Action Input:'
23
+ observation: str = 'Observation:'
24
+
25
+
26
+ @dataclass
27
+ class ToolDesc:
28
+ name_for_model: str
29
+ name_for_human: str
30
+ description_for_model: str
31
+ parameters: str
32
+ args_format: str
33
+
34
+
35
+ class ReactCompatMixin:
36
+ """
37
+ Mixin class providing ReAct-style agent compatibility.
38
+
39
+ This mixin handles parsing and formatting of tool calls in the ReAct format,
40
+ where actions and inputs are marked with specific keywords in the text.
41
+ """
42
+ keyword = AgentKeyword()
43
+
44
+ @staticmethod
45
+ def _split_action_action_input(response: str, keyword: AgentKeyword) -> List[Function]:
46
+ agent_parts = split_str_parts_by(response, list(asdict(keyword).values()))
47
+ functions = []
48
+ action_content = None
49
+
50
+ for part in agent_parts:
51
+ key, content = part['key'].lower(), part['content']
52
+ if action_content is None and key == keyword.action.lower():
53
+ action_content = content
54
+ elif action_content is not None and key == keyword.action_input.lower():
55
+ functions.append(Function(name=action_content, arguments=content))
56
+ action_content = None
57
+
58
+ return functions
59
+
60
+ def get_toolcall(self, response: str) -> List[Function]:
61
+ """
62
+ Extract tool calls from an agent response.
63
+
64
+ Args:
65
+ response: The agent's response text.
66
+
67
+ Returns:
68
+ List of Function objects representing tool calls.
69
+ """
70
+ functions = self._split_action_action_input(response, self.keyword)
71
+ if len(functions) == 0 and self.keyword != ReactCompatMixin.keyword:
72
+ # compat react
73
+ functions = self._split_action_action_input(response, ReactCompatMixin.keyword)
74
+ return functions
75
+
76
+ def _format_tool_responses(
77
+ self,
78
+ assistant_content: str,
79
+ tool_messages,
80
+ ) -> Tuple[str, 'Prompt']:
81
+ """
82
+ Format tool execution results into the conversation.
83
+
84
+ Args:
85
+ assistant_content: The assistant's message containing tool calls.
86
+ tool_messages: List of tool execution result messages.
87
+
88
+ Returns:
89
+ Tuple of (formatted assistant content, formatted tool responses).
90
+ """
91
+ assert len(tool_messages) > 0
92
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
93
+ if with_action:
94
+ if not assistant_content.endswith(self.keyword.observation):
95
+ if not assistant_content.endswith('\n'):
96
+ assistant_content += '\n'
97
+ assistant_content += self.keyword.observation
98
+ res = []
99
+ for i, tool_message in enumerate(tool_messages):
100
+ if i > 0:
101
+ res.append(self.keyword.observation)
102
+ tool_content = tool_message['content']
103
+ res.append(tool_content)
104
+ if not tool_content.endswith('\n'):
105
+ res.append('\n')
106
+ else:
107
+ res = []
108
+ for tool_message in tool_messages:
109
+ res.append(tool_message['content'])
110
+ return assistant_content, res
111
+
112
+ def _format_react_standalone_tool_responses(self, tool_messages) -> 'Prompt':
113
+ assert len(tool_messages) > 0
114
+ res = []
115
+ for tool_message in tool_messages:
116
+ tool_content = tool_message['content']
117
+ res.extend(['\n', self.keyword.observation, '\n', tool_content])
118
+ return res
119
+
120
+ def _format_standalone_tool_responses(self, tool_messages) -> 'Prompt':
121
+ """Format observations that do not follow an assistant tool call."""
122
+ raise NotImplementedError(
123
+ f'Agent template {self.__class__.__name__} does not support standalone tool responses.')
124
+
125
+ @staticmethod
126
+ def _parse_tool_call(content) -> Dict[str, Any]:
127
+ obj = BaseAgentTemplate._parse_json(content)
128
+ name = obj['name']
129
+ arguments = obj.get('arguments')
130
+ if arguments is None:
131
+ arguments = obj.get('parameters')
132
+ arguments = BaseAgentTemplate._parse_json(arguments)
133
+ assert arguments is not None, f'content: {content}'
134
+ return {'name': name, 'arguments': arguments}
135
+
136
+ def _format_tool_calls(self, tool_call_messages) -> str:
137
+ """
138
+ Format tool call messages into ReAct format.
139
+
140
+ Args:
141
+ tool_call_messages: List of messages containing tool call information.
142
+
143
+ Returns:
144
+ Formatted string with Action, Action Input, and Observation markers.
145
+ """
146
+ # -> assistant_content
147
+ tool_calls = []
148
+ for message in tool_call_messages:
149
+ tool_call = self._parse_tool_call(message['content'])
150
+ tool_calls.append(f'{self.keyword.action} {tool_call["name"]}\n'
151
+ f'{self.keyword.action_input} {tool_call["arguments"]}\n')
152
+ tool_calls.append(self.keyword.observation)
153
+ return ''.join(tool_calls)
154
+
155
+
156
+ class BaseAgentTemplate(ReactCompatMixin, ABC):
157
+ """
158
+ Abstract base class for agent templates.
159
+
160
+ This class provides common functionality for parsing and formatting tools,
161
+ as well as handling tool calls in different formats. Subclasses must
162
+ implement the following methods to define their specific behavior:
163
+ - `_format_tools`: Format tool definitions for the prompt
164
+ - `_format_tool_calls`: Format tool call messages
165
+ - `_format_tool_responses`: Format tool execution results
166
+ - `get_toolcall`: Extract tool calls from agent responses
167
+ """
168
+
169
+ def _add_tool_call_prefix(self, tool_content: str, pre_message=None) -> str:
170
+ """Hook to prepend a separator before tool_call content based on the preceding message.
171
+
172
+ Subclasses can override this to match their jinja template's separator logic
173
+ (e.g., Qwen3.5/3.6 inserts '\n\n' when assistant has effective content before tool_calls).
174
+
175
+ Args:
176
+ tool_content: The formatted tool_call string from _format_tool_calls.
177
+ pre_message: The message immediately before the tool_call block, or None.
178
+
179
+ Returns:
180
+ tool_content with any necessary prefix prepended.
181
+ """
182
+ return tool_content
183
+
184
+ @staticmethod
185
+ def _get_tool_name(tool):
186
+ return tool.get('name_for_model') or tool.get('name')
187
+
188
+ @staticmethod
189
+ def unwrap_tool(tool):
190
+ assert isinstance(tool, dict), f'tool: {tool}'
191
+ if 'type' in tool and 'function' in tool:
192
+ tool = tool['function']
193
+ return tool
194
+
195
+ @staticmethod
196
+ def wrap_tool(tool):
197
+ assert isinstance(tool, dict), f'tool: {tool}'
198
+ if 'type' not in tool and 'function' not in tool:
199
+ tool = {'type': 'function', 'function': tool}
200
+ return tool
201
+
202
+ @staticmethod
203
+ def _parse_tool(tool, lang: Literal['zh', 'en']) -> ToolDesc:
204
+ tool = BaseAgentTemplate.unwrap_tool(tool)
205
+ name_for_model = BaseAgentTemplate._get_tool_name(tool)
206
+ name_for_human = tool.get('name_for_human') or name_for_model
207
+
208
+ description = tool.get('description')
209
+ if description is None:
210
+ description = tool.get('description_for_model')
211
+ parameters = tool.get('parameters') or {}
212
+ parameters = parameters if isinstance(parameters, str) else json.dumps(parameters, ensure_ascii=False)
213
+ args_format = '此工具的输入应为JSON对象。' if lang == 'zh' else 'Format the arguments as a JSON object.'
214
+ tool_desc = ToolDesc(
215
+ name_for_model=name_for_model,
216
+ name_for_human=name_for_human,
217
+ description_for_model=description,
218
+ parameters=parameters,
219
+ args_format=args_format)
220
+ assert name_for_model is not None and description is not None, f'tool_desc: {tool_desc}'
221
+ return tool_desc
222
+
223
+ @staticmethod
224
+ def _parse_json(json_str: str) -> Optional[Any]:
225
+ """
226
+ Parse a JSON string with fallback to ast.literal_eval.
227
+
228
+ Args:
229
+ json_str: String to parse, or already parsed object.
230
+
231
+ Returns:
232
+ Parsed object, or None if parsing fails.
233
+ """
234
+ if not isinstance(json_str, str):
235
+ return json_str
236
+ try:
237
+ res = json.loads(json_str)
238
+ except json.JSONDecodeError:
239
+ try:
240
+ res = ast.literal_eval(json_str)
241
+ except Exception:
242
+ return
243
+ return res
244
+
245
+ @abstractmethod
246
+ def _format_tools(self,
247
+ tools: List[Union[str, dict]],
248
+ system: Optional[str] = None,
249
+ user_message: Optional[dict] = None) -> str:
250
+ """
251
+ Format tools for inclusion in the agent prompt.
252
+
253
+ Args:
254
+ tools: List of tool definitions (strings or dictionaries).
255
+ system: System prompt text.
256
+ user_message: Optional user message to incorporate.
257
+
258
+ Returns:
259
+ Formatted string to include in the prompt.
260
+ """
261
+ pass
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/deepseek_v3_1.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class DeepSeekV31AgentTemplate(BaseAgentTemplate):
12
+
13
+ def get_toolcall(self, response: str) -> List[Function]:
14
+ # Parse tool calls using the DSV3.1 format:
15
+ # <|tool▁calls▁begin|><|tool▁call▁begin|>name<|tool▁sep|>args<|tool▁call▁end|>
16
+ pattern = r'<|tool▁call▁begin|>(.*?)<|tool▁sep|>(.*?)<|tool▁call▁end|>'
17
+ res_list = re.findall(pattern, response, re.DOTALL)
18
+ functions = []
19
+ for name, arguments in res_list:
20
+ name = name.strip()
21
+ arguments = self._parse_json(arguments.strip())
22
+ if arguments is not None:
23
+ functions.append(Function(name=name, arguments=arguments))
24
+
25
+ if len(functions) == 0:
26
+ # compat react_en
27
+ return super().get_toolcall(response)
28
+ return functions
29
+
30
+ def _get_tool_responses(self, tool_messages):
31
+ return ''.join(f'<|tool▁output▁begin|>{tool_message["content"]}<|tool▁output▁end|>'
32
+ for tool_message in tool_messages)
33
+
34
+ def _get_tool_calls(self, tool_calls: List[str]):
35
+ return f'<|tool▁calls▁begin|>{"".join(tool_calls)}<|tool▁calls▁end|>'
36
+
37
+ def _format_tool_responses(
38
+ self,
39
+ assistant_content: str,
40
+ tool_messages,
41
+ ) -> Tuple[str, 'Prompt']:
42
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
43
+ if with_action:
44
+ return super()._format_tool_responses(assistant_content, tool_messages)
45
+ res = ['<|end▁of▁sentence|>', self._get_tool_responses(tool_messages)]
46
+ return assistant_content, res
47
+
48
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
49
+ tool_descs = []
50
+ system = system or ''
51
+ for tool in tools:
52
+ tool = self.unwrap_tool(tool)
53
+ tool_name = self._get_tool_name(tool)
54
+ description = tool.get('description', '')
55
+ parameters = tool.get('parameters', {})
56
+
57
+ tool_desc = f"""### {tool_name}
58
+ Description: {description}
59
+
60
+ Parameters: {json.dumps(parameters, ensure_ascii=False)}"""
61
+ tool_descs.append(tool_desc)
62
+
63
+ tools_section = '\n\n'.join(tool_descs)
64
+
65
+ return f"""{system}
66
+
67
+ ## Tools
68
+ You have access to the following tools:
69
+
70
+ {tools_section}
71
+
72
+ IMPORTANT: ALWAYS adhere to this exact format for tool use:
73
+ <|tool▁calls▁begin|><|tool▁call▁begin|>tool_call_name<|tool▁sep|>tool_call_arguments<|tool▁call▁end|>{{additional_tool_calls}}<|tool▁calls▁end|>
74
+
75
+ Where:
76
+ - `tool_call_name` must be an exact match to one of the available tools
77
+ - `tool_call_arguments` must be valid JSON that strictly follows the tool's Parameters Schema
78
+ - For multiple tool calls, chain them directly without separators or spaces"""
79
+
80
+ def _format_tool_calls(self, tool_call_messages):
81
+ tool_calls = []
82
+ for message in tool_call_messages:
83
+ tool_call = self._parse_tool_call(message['content'])
84
+ name = tool_call['name']
85
+ arguments = json.dumps(tool_call['arguments'], ensure_ascii=False)
86
+ tool_calls.append(f'<|tool▁call▁begin|>{name}<|tool▁sep|>{arguments}<|tool▁call▁end|>')
87
+ return self._get_tool_calls(tool_calls)
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/deepseek_v4.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import Any, Dict, List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+ DSML_TOKEN = '|DSML|'
11
+
12
+ TOOLS_TEMPLATE = """## Tools
13
+
14
+ You have access to a set of tools to help answer the user's question. \
15
+ You can invoke tools by writing a "<{dsml_token}tool_calls>" block like the following:
16
+
17
+ <{dsml_token}tool_calls>
18
+ <{dsml_token}invoke name="$TOOL_NAME">
19
+ <{dsml_token}parameter name="$PARAMETER_NAME" string="true|false">$PARAMETER_VALUE</{dsml_token}parameter>
20
+ ...
21
+ </{dsml_token}invoke>
22
+ <{dsml_token}invoke name="$TOOL_NAME2">
23
+ ...
24
+ </{dsml_token}invoke>
25
+ </{dsml_token}tool_calls>
26
+
27
+ String parameters should be specified as is and set `string="true"`. \
28
+ For all other types (numbers, booleans, arrays, objects), \
29
+ pass the value in JSON format and set `string="false"`.
30
+
31
+ If thinking_mode is enabled (triggered by <think>), \
32
+ you MUST output your complete reasoning inside <think>...</think> BEFORE any tool calls or final response.
33
+
34
+ Otherwise, output directly after </think> with tool calls or final response.
35
+
36
+ ### Available Tool Schemas
37
+
38
+ {tool_schemas}
39
+
40
+ You MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.
41
+ """
42
+
43
+
44
+ def _to_json(value: Any) -> str:
45
+ try:
46
+ return json.dumps(value, ensure_ascii=False)
47
+ except Exception:
48
+ return json.dumps(value, ensure_ascii=True)
49
+
50
+
51
+ def _encode_arguments_to_dsml(arguments: Dict[str, Any]) -> str:
52
+ """Encode tool call arguments dict into DSML parameter lines."""
53
+ lines = []
54
+ for k, v in arguments.items():
55
+ is_str = 'true' if isinstance(v, str) else 'false'
56
+ val = v if isinstance(v, str) else _to_json(v)
57
+ lines.append(f'<{DSML_TOKEN}parameter name="{k}" string="{is_str}">{val}</{DSML_TOKEN}parameter>')
58
+ return '\n'.join(lines)
59
+
60
+
61
+ class DeepSeekV4AgentTemplate(BaseAgentTemplate):
62
+
63
+ def get_toolcall(self, response: str) -> List[Function]:
64
+ # Parse DSML tool calls from model output
65
+ # Pattern: <|DSML|invoke name="tool_name">...params...</|DSML|invoke>
66
+ invoke_pattern = re.compile(
67
+ rf'<{re.escape(DSML_TOKEN)}invoke\s+name="([^"]+)">\s*(.*?)\s*</{re.escape(DSML_TOKEN)}invoke>', re.DOTALL)
68
+ param_pattern = re.compile(
69
+ rf'<{re.escape(DSML_TOKEN)}parameter\s+name="([^"]+)"\s+string="(true|false)">'
70
+ rf'(.*?)</{re.escape(DSML_TOKEN)}parameter>', re.DOTALL)
71
+
72
+ functions = []
73
+ for match in invoke_pattern.finditer(response):
74
+ tool_name = match.group(1)
75
+ params_block = match.group(2)
76
+ arguments = {}
77
+ for pm in param_pattern.finditer(params_block):
78
+ param_name = pm.group(1)
79
+ is_string = pm.group(2)
80
+ param_value = pm.group(3)
81
+ if is_string == 'false':
82
+ try:
83
+ param_value = json.loads(param_value)
84
+ except json.JSONDecodeError:
85
+ pass
86
+ arguments[param_name] = param_value
87
+ functions.append(Function(name=tool_name, arguments=json.dumps(arguments, ensure_ascii=False)))
88
+
89
+ if len(functions) == 0:
90
+ # Fallback to ReAct format
91
+ return super().get_toolcall(response)
92
+ return functions
93
+
94
+ def _get_tool_responses(self, tool_messages):
95
+ # The official encoding merges tool results into one user turn, joining the
96
+ # `<tool_result>` blocks with a blank line.
97
+ return '\n\n'.join(f'<tool_result>{tool_message["content"]}</tool_result>' for tool_message in tool_messages)
98
+
99
+ def _add_tool_call_prefix(self, tool_content: str, pre_message=None) -> str:
100
+ # The official encoding always renders the tool_calls block as `\n\n` + block,
101
+ # right after the assistant's (possibly empty) textual content.
102
+ return '\n\n' + tool_content
103
+
104
+ def _format_tool_responses(
105
+ self,
106
+ assistant_content: str,
107
+ tool_messages,
108
+ ) -> Tuple[str, 'Prompt']:
109
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
110
+ if with_action:
111
+ return super()._format_tool_responses(assistant_content, tool_messages)
112
+ res = [
113
+ '<|end▁of▁sentence|><|User|>',
114
+ self._get_tool_responses(tool_messages),
115
+ '<|Assistant|>',
116
+ ]
117
+ return assistant_content, res
118
+
119
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
120
+ tool_schemas = []
121
+ for tool in tools:
122
+ tool = self.unwrap_tool(tool)
123
+ tool_schemas.append(_to_json(tool))
124
+
125
+ tools_section = TOOLS_TEMPLATE.format(
126
+ tool_schemas='\n'.join(tool_schemas),
127
+ dsml_token=DSML_TOKEN,
128
+ )
129
+
130
+ system = system or ''
131
+ return f'{system}\n\n{tools_section}' if system else tools_section
132
+
133
+ def _format_tool_calls(self, tool_call_messages) -> str:
134
+ invocations = []
135
+ for message in tool_call_messages:
136
+ tool_call = self._parse_tool_call(message['content'])
137
+ name = tool_call['name']
138
+ arguments = tool_call['arguments']
139
+ if isinstance(arguments, str):
140
+ arguments = json.loads(arguments)
141
+ dsml_args = _encode_arguments_to_dsml(arguments)
142
+ invocations.append(f'<{DSML_TOKEN}invoke name="{name}">\n{dsml_args}\n</{DSML_TOKEN}invoke>')
143
+
144
+ tool_calls_str = '\n'.join(invocations)
145
+ return f'<{DSML_TOKEN}tool_calls>\n{tool_calls_str}\n</{DSML_TOKEN}tool_calls>'
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/extra.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ from typing import List, Optional, Union
3
+
4
+ from .base import BaseAgentTemplate
5
+
6
+
7
+ class ReactGRPOAgentTemplate(BaseAgentTemplate):
8
+
9
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
10
+ tool_names = []
11
+ tool_descs = []
12
+ for tool in tools:
13
+ tool_desc = self._parse_tool(tool, 'en')
14
+ tool_names.append(tool_desc.name_for_model)
15
+ tool_descs.append(
16
+ f'{tool_desc.name_for_model}: Call this tool to interact with the {tool_desc.name_for_human} API. '
17
+ f'What is the {tool_desc.name_for_human} API useful for? {tool_desc.description_for_model} '
18
+ f'Parameters: {tool_desc.parameters} {tool_desc.args_format}')
19
+
20
+ return """A conversation for tool calling between User and Assistant. The user asks a question which may be solved by calling tools, and the Assistant solves it. The assistant first thinks about the reasoning process in the mind and then provides the user with the answer. The reasoning process should be enclosed within <think> </think>tags and answer should follow the ReACT format(Action:xxx\nAction Input:xxx), i.e., <think> reasoning process here </think> Action: action here\nAction Input: parameters here
21
+
22
+ Answer the following questions as best as you can. You have access to the following tools:
23
+
24
+ """ + '\n\n'.join(tool_descs) + f"""
25
+
26
+ Use the following format:
27
+
28
+ <think>you should always think about what to do</think>
29
+ Action: the action to take, should be one of [{','.join(tool_names)}]
30
+ Action Input: the input to the action
31
+ Observation: the result of the action, given by the actual calling
32
+ ... (this Thought/Action/Action Input/Observation can be repeated zero or more times)
33
+ Final Answer: the final answer to the original input question
34
+
35
+ Begin!
36
+ """ # noqa
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/gemma4.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import Any, Dict, List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+ QUOTE = '<|"|>'
11
+ _STANDARD_KEYS = {'description', 'type', 'properties', 'required', 'nullable'}
12
+
13
+
14
+ class Gemma4AgentTemplate(BaseAgentTemplate):
15
+ """Agent template for Google Gemma-4 models.
16
+
17
+ Reference: chat_template.jinja shipped with google/gemma-4-12B-it.
18
+ Tool definitions are wrapped in `<|tool>...<tool|>` and rendered with the
19
+ custom DSL described by the official chat template.
20
+ Tool calls follow `<|tool_call>call:NAME{key:value,...}<tool_call|>` and
21
+ tool responses follow `<|tool_response>response:NAME{...}<tool_response|>`.
22
+ """
23
+
24
+ @classmethod
25
+ def _format_argument(cls, value: Any, escape_keys: bool = True) -> str:
26
+ if isinstance(value, bool):
27
+ return 'true' if value else 'false'
28
+ if isinstance(value, str):
29
+ return f'{QUOTE}{value}{QUOTE}'
30
+ if value is None:
31
+ return 'null'
32
+ if isinstance(value, dict):
33
+ items = []
34
+ for k in sorted(value.keys()):
35
+ v = value[k]
36
+ key_str = f'{QUOTE}{k}{QUOTE}' if escape_keys else str(k)
37
+ items.append(f'{key_str}:{cls._format_argument(v, escape_keys=escape_keys)}')
38
+ return '{' + ','.join(items) + '}'
39
+ if isinstance(value, (list, tuple)):
40
+ return '[' + ','.join(cls._format_argument(item, escape_keys=escape_keys) for item in value) + ']'
41
+ return str(value)
42
+
43
+ @classmethod
44
+ def _format_parameters(cls,
45
+ properties: Dict[str, Any],
46
+ required: Optional[List[str]] = None,
47
+ filter_keys: bool = False) -> str:
48
+ parts = []
49
+ for key in sorted(properties.keys()):
50
+ value = properties[key]
51
+ if filter_keys and key in _STANDARD_KEYS:
52
+ continue
53
+ if not isinstance(value, dict):
54
+ continue
55
+ inner: List[str] = []
56
+ type_upper = (value.get('type') or '').upper() if isinstance(value.get('type'), str) else ''
57
+ if value.get('description'):
58
+ inner.append(f'description:{QUOTE}{value["description"]}{QUOTE}')
59
+ if type_upper == 'STRING':
60
+ if value.get('enum'):
61
+ inner.append(f'enum:{cls._format_argument(value["enum"])}')
62
+ elif type_upper == 'ARRAY':
63
+ items_value = value.get('items')
64
+ if isinstance(items_value, dict) and items_value:
65
+ items_inner: List[str] = []
66
+ items_required = items_value.get('required', [])
67
+ for item_key in sorted(items_value.keys()):
68
+ item_value = items_value[item_key]
69
+ if item_value is None:
70
+ continue
71
+ if item_key == 'properties' and isinstance(item_value, dict):
72
+ items_inner.append(f'properties:{{{cls._format_parameters(item_value, items_required)}}}')
73
+ elif item_key == 'required':
74
+ req_str = ','.join(f'{QUOTE}{r}{QUOTE}' for r in item_value)
75
+ items_inner.append(f'required:[{req_str}]')
76
+ elif item_key == 'type':
77
+ if isinstance(item_value, str):
78
+ items_inner.append(f'type:{cls._format_argument(item_value.upper())}')
79
+ else:
80
+ items_inner.append(f'type:{cls._format_argument([str(t).upper() for t in item_value])}')
81
+ else:
82
+ items_inner.append(f'{item_key}:{cls._format_argument(item_value)}')
83
+ inner.append('items:{' + ','.join(items_inner) + '}')
84
+ if value.get('nullable'):
85
+ inner.append('nullable:true')
86
+ if type_upper == 'OBJECT':
87
+ inner_required = value.get('required', [])
88
+ if isinstance(value.get('properties'), dict):
89
+ inner.append(f'properties:{{{cls._format_parameters(value["properties"], inner_required)}}}')
90
+ else:
91
+ inner.append(f'properties:{{{cls._format_parameters(value, inner_required, filter_keys=True)}}}')
92
+ if value.get('required'):
93
+ req_str = ','.join(f'{QUOTE}{r}{QUOTE}' for r in value['required'])
94
+ inner.append(f'required:[{req_str}]')
95
+ inner.append(f'type:{QUOTE}{type_upper}{QUOTE}')
96
+ parts.append(f'{key}:{{{",".join(inner)}}}')
97
+ return ','.join(parts)
98
+
99
+ @classmethod
100
+ def _format_function_declaration(cls, tool: Dict[str, Any]) -> str:
101
+ function = tool['function']
102
+ name = function.get('name', '')
103
+ description = function.get('description', '') or ''
104
+ result = f'declaration:{name}{{description:{QUOTE}{description}{QUOTE}'
105
+ params = function.get('parameters')
106
+ if params:
107
+ param_parts: List[str] = []
108
+ properties = params.get('properties')
109
+ if properties:
110
+ param_parts.append(f'properties:{{{cls._format_parameters(properties, params.get("required", []))}}}')
111
+ if params.get('required'):
112
+ req_str = ','.join(f'{QUOTE}{r}{QUOTE}' for r in params['required'])
113
+ param_parts.append(f'required:[{req_str}]')
114
+ ptype = params.get('type')
115
+ if isinstance(ptype, str) and ptype:
116
+ param_parts.append(f'type:{QUOTE}{ptype.upper()}{QUOTE}')
117
+ if param_parts:
118
+ result += ',parameters:{' + ','.join(param_parts) + '}'
119
+ result += '}'
120
+ return result
121
+
122
+ def _format_tools(self,
123
+ tools: List[Union[str, dict]],
124
+ system: Optional[str] = None,
125
+ user_message: Optional[dict] = None) -> str:
126
+ tool_blocks: List[str] = []
127
+ for tool in tools:
128
+ tool = self.wrap_tool(tool)
129
+ tool_blocks.append(f'<|tool>{self._format_function_declaration(tool)}<tool|>')
130
+ system_text = (system or '').strip()
131
+ return system_text + ''.join(tool_blocks)
132
+
133
+ def _format_tool_calls(self, tool_call_messages) -> str:
134
+ invocations: List[str] = []
135
+ for message in tool_call_messages:
136
+ tool_call = self._parse_tool_call(message['content'])
137
+ name = tool_call['name']
138
+ arguments = tool_call['arguments']
139
+ if isinstance(arguments, str):
140
+ arguments = self._parse_json(arguments) or {}
141
+ if isinstance(arguments, dict):
142
+ args_str = ','.join(f'{k}:{self._format_argument(arguments[k], escape_keys=False)}'
143
+ for k in sorted(arguments.keys()))
144
+ else:
145
+ args_str = ''
146
+ invocations.append(f'<|tool_call>call:{name}{{{args_str}}}<tool_call|>')
147
+ return ''.join(invocations)
148
+
149
+ def _get_tool_responses(self, tool_messages) -> str:
150
+ parts: List[str] = []
151
+ for tool_message in tool_messages:
152
+ tool_name = tool_message.get('name') or 'unknown'
153
+ tool_content = tool_message.get('content')
154
+ if isinstance(tool_content, dict):
155
+ inner = ','.join(f'{k}:{self._format_argument(tool_content[k], escape_keys=False)}'
156
+ for k in sorted(tool_content.keys()))
157
+ parts.append(f'<|tool_response>response:{tool_name}{{{inner}}}<tool_response|>')
158
+ else:
159
+ # Match jinja: treat string/other content as a single `value:` field.
160
+ value = '' if tool_content is None else tool_content
161
+ parts.append(f'<|tool_response>response:{tool_name}'
162
+ f'{{value:{self._format_argument(value, escape_keys=False)}}}<tool_response|>')
163
+ return ''.join(parts)
164
+
165
+ def _format_tool_responses(
166
+ self,
167
+ assistant_content: str,
168
+ tool_messages,
169
+ ) -> Tuple[str, 'Prompt']:
170
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
171
+ if with_action:
172
+ return super()._format_tool_responses(assistant_content, tool_messages)
173
+ # If the model hallucinated a trailing `<|tool_response>` opener (e.g. when stop
174
+ # tokens were not configured), strip it so the rendered turn does not contain
175
+ # `<|tool_response><|tool_response>response:...`.
176
+ if assistant_content.endswith('<|tool_response>'):
177
+ assistant_content = assistant_content[:-len('<|tool_response>')]
178
+ # In gemma4, tool_call/tool_response/follow-up assistant text all live in the
179
+ # same `<|turn>model ... <turn|>` block, so we do not open a new model turn here.
180
+ res: 'Prompt' = [self._get_tool_responses(tool_messages)]
181
+ return assistant_content, res
182
+
183
+ @classmethod
184
+ def _gemma_to_json(cls, s: str) -> str:
185
+ # `<|"|>` -> `"`; bare keys preceded by `{` or `,` get JSON-quoted.
186
+ s = s.replace(QUOTE, '"')
187
+ s = re.sub(r'(?<=[\{,])([A-Za-z_][\w\-]*)(?=:)', r'"\1"', s)
188
+ return s
189
+
190
+ @classmethod
191
+ def _parse_arguments(cls, args_body: str) -> Dict[str, Any]:
192
+ json_str = cls._gemma_to_json('{' + args_body + '}')
193
+ try:
194
+ parsed = json.loads(json_str)
195
+ if isinstance(parsed, dict):
196
+ return parsed
197
+ except json.JSONDecodeError:
198
+ pass
199
+ return {}
200
+
201
+ def get_toolcall(self, response: str) -> List[Function]:
202
+ pattern = re.compile(r'<\|tool_call>call:([^\{]+)\{(.*?)\}<tool_call\|>', re.DOTALL)
203
+ functions: List[Function] = []
204
+ for match in pattern.finditer(response):
205
+ name = match.group(1).strip()
206
+ arguments = self._parse_arguments(match.group(2))
207
+ functions.append(Function(name=name, arguments=json.dumps(arguments, ensure_ascii=False)))
208
+ if not functions:
209
+ return super().get_toolcall(response)
210
+ return functions
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/glm4.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class ChatGLM4AgentTemplate(BaseAgentTemplate):
12
+ is_glm4_0414 = False
13
+
14
+ @staticmethod
15
+ def _find_function_call(single_content: str) -> Optional[Function]:
16
+ single_content = single_content.replace('<|observation|>', '')
17
+ pattern = re.compile(r'([^\n`]*?)\n({.*?})(?=\w*\n|$)', re.DOTALL)
18
+ matches = pattern.findall(single_content)
19
+ if not matches:
20
+ return
21
+ name, arguments = matches[0]
22
+ return Function(name=name, arguments=arguments)
23
+
24
+ def get_toolcall(self, response: str) -> List[Function]:
25
+ toolcall_list = response.split('<|assistant|>')
26
+ functions = []
27
+ for toolcall in toolcall_list:
28
+ function = self._find_function_call(toolcall)
29
+ if function:
30
+ functions.append(function)
31
+ if len(functions) == 0:
32
+ # compat react_en
33
+ return super().get_toolcall(response)
34
+ return functions
35
+
36
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
37
+ tool_descs = []
38
+ for tool in tools:
39
+ tool = self.unwrap_tool(tool)
40
+ name = self._get_tool_name(tool)
41
+ tool_descs.append(f'## {name}\n\n{json.dumps(tool, ensure_ascii=False, indent=4)}\n'
42
+ '在调用上述函数时,请使用 Json 格式表示调用的参数。')
43
+ glm4_system = '你是一个名为 GLM-4 的人工智能助手。你是基于智谱AI训练的语言模型 GLM-4 模型开发的,你的任务是针对用户的问题和要求提供适当的答复和支持。\n\n' # noqa
44
+ return ('' if self.is_glm4_0414 else glm4_system) + """# 可用工具
45
+
46
+ """ + '\n'.join(tool_descs)
47
+
48
+ def _format_tool_responses(
49
+ self,
50
+ assistant_content: str,
51
+ tool_messages,
52
+ ) -> Tuple[str, 'Prompt']:
53
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
54
+ if with_action:
55
+ return super()._format_tool_responses(assistant_content, tool_messages)
56
+ res = ['\n']
57
+ for i, tool_message in enumerate(tool_messages):
58
+ tool_content = tool_message['content']
59
+ if i > 0:
60
+ res.append('<|observation|>\n')
61
+ res.append(tool_content)
62
+ res.append('<|assistant|>\n')
63
+ return assistant_content, res
64
+
65
+ def _format_standalone_tool_responses(self, tool_messages) -> 'Prompt':
66
+ res = []
67
+ for tool_message in tool_messages:
68
+ res.extend(['<|observation|>\n', tool_message['content']])
69
+ return res
70
+
71
+ def _format_tool_calls(self, tool_call_messages) -> str:
72
+ tool_calls = []
73
+ for message in tool_call_messages:
74
+ tool_call = self._parse_tool_call(message['content'])
75
+ tool_calls.append(f'{tool_call["name"]}\n{tool_call["arguments"]}')
76
+ return '<|assistant|>'.join(tool_calls) + '<|observation|>'
77
+
78
+
79
+ class GLM4AgentTemplate(ChatGLM4AgentTemplate):
80
+ is_glm4_0414 = True
81
+
82
+
83
+ class GLM4_5AgentTemplate(BaseAgentTemplate):
84
+ model_type = 'glm4_5'
85
+
86
+ @staticmethod
87
+ def _find_function_call(single_content: str) -> Optional[Function]:
88
+ single_content = single_content.strip()
89
+ func_name_match = re.match(r'^([^\n<]+)', single_content)
90
+ if not func_name_match:
91
+ return None
92
+ func_name = func_name_match.group(1).strip()
93
+ keys = re.findall(r'<arg_key>(.*?)</arg_key>', single_content, re.DOTALL)
94
+ values = re.findall(r'<arg_value>(.*?)</arg_value>', single_content, re.DOTALL)
95
+ if len(keys) != len(values):
96
+ return None
97
+ args = {k.strip(): v.strip() for k, v in zip(keys, values)}
98
+ return Function(name=func_name, arguments=json.dumps(args, ensure_ascii=False))
99
+
100
+ def get_toolcall(self, response: str) -> List[Function]:
101
+ toolcall_list = re.findall(r'<tool_call>(.*?)</tool_call>', response, re.DOTALL)
102
+ functions = []
103
+ for toolcall in toolcall_list:
104
+ function = self._find_function_call(toolcall)
105
+ if function:
106
+ functions.append(function)
107
+ if len(functions) == 0:
108
+ # compat react_en
109
+ return super().get_toolcall(response)
110
+ return functions
111
+
112
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
113
+ tool_descs = [
114
+ '# Tools\n\nYou may call one or more functions to assist with the user query.\n\n'
115
+ 'You are provided with function signatures within <tools></tools> XML tags:\n<tools>'
116
+ ]
117
+ for tool in tools:
118
+ if self.model_type == 'glm5_1':
119
+ tool = self.unwrap_tool(tool)
120
+ tool_descs.append(f'{json.dumps(tool, ensure_ascii=False)}')
121
+ if self.model_type == 'glm4_5':
122
+ tool_desc = ('</tools>\n\nFor each function call, output the function name and arguments within '
123
+ 'the following XML format:\n<tool_call>{function-name}\n<arg_key>{arg-key-1}</arg_key>\n'
124
+ '<arg_value>{arg-value-1}</arg_value>\n<arg_key>{arg-key-2}</arg_key>\n'
125
+ '<arg_value>{arg-value-2}</arg_value>\n...\n</tool_call>')
126
+ elif self.model_type in {'glm4_7', 'glm5_1'}:
127
+ tool_desc = ('</tools>\n\nFor each function call, output the function name and arguments within '
128
+ 'the following XML format:\n<tool_call>{function-name}<arg_key>{arg-key-1}</arg_key>'
129
+ '<arg_value>{arg-value-1}</arg_value><arg_key>{arg-key-2}</arg_key><arg_value>'
130
+ '{arg-value-2}</arg_value>...</tool_call>')
131
+ else:
132
+ raise ValueError("model_type must be one of 'glm4_5', 'glm4_7', or 'glm5_1'.")
133
+ tool_descs.append(tool_desc)
134
+ tool_descs = '\n'.join(tool_descs)
135
+ if system is not None and system.strip():
136
+ tool_descs += '<|system|>\n' + system.strip()
137
+ elif self.model_type in {'glm4_7', 'glm5_1'} and not tool_descs.startswith('\n'):
138
+ tool_descs = '\n' + tool_descs
139
+ return tool_descs
140
+
141
+ def _format_tool_responses(
142
+ self,
143
+ assistant_content: str,
144
+ tool_messages,
145
+ ) -> Tuple[str, 'Prompt']:
146
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
147
+ if with_action:
148
+ return super()._format_tool_responses(assistant_content, tool_messages)
149
+ if self.model_type == 'glm4_5':
150
+ res = []
151
+ for tool_message in tool_messages:
152
+ tool_content = tool_message['content']
153
+ res.append(f'\n<tool_response>\n{tool_content}\n</tool_response>')
154
+ res.append('<|assistant|>\n')
155
+ elif self.model_type in {'glm4_7', 'glm5_1'}:
156
+ res = []
157
+ for tool_message in tool_messages:
158
+ tool_content = tool_message['content']
159
+ res.append(f'<tool_response>{tool_content}</tool_response>')
160
+ res.append('<|assistant|>')
161
+ return assistant_content, res
162
+
163
+ def _format_standalone_tool_responses(self, tool_messages) -> 'Prompt':
164
+ res = ['<|observation|>']
165
+ for tool_message in tool_messages:
166
+ tool_content = tool_message['content']
167
+ if self.model_type == 'glm4_5':
168
+ res.append(f'\n<tool_response>\n{tool_content}\n</tool_response>')
169
+ elif self.model_type in {'glm4_7', 'glm5_1'}:
170
+ res.append(f'<tool_response>{tool_content}</tool_response>')
171
+ return res
172
+
173
+ def _format_tool_calls(self, tool_call_messages) -> str:
174
+ tool_calls = []
175
+ for message in tool_call_messages:
176
+ tool_call = self._parse_tool_call(message['content'])
177
+ tool_calls.append(f"<tool_call>{tool_call['name']}")
178
+ for arg_key, arg_value in tool_call['arguments'].items():
179
+ tool_calls.append(f'<arg_key>{arg_key}</arg_key>')
180
+ tool_calls.append(f'<arg_value>{arg_value}</arg_value>')
181
+ tool_calls.append('</tool_call>')
182
+ if self.model_type == 'glm4_5':
183
+ sep = '\n'
184
+ elif self.model_type in {'glm4_7', 'glm5_1'}:
185
+ sep = ''
186
+ return sep.join(tool_calls) + '<|observation|>'
187
+
188
+
189
+ class GLM4_7AgentTemplate(GLM4_5AgentTemplate):
190
+ model_type = 'glm4_7'
191
+
192
+
193
+ class GLM5_1AgentTemplate(GLM4_5AgentTemplate):
194
+ model_type = 'glm5_1'
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/hermes.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class HermesAgentTemplate(BaseAgentTemplate):
12
+
13
+ def get_toolcall(self, response: str) -> List[Function]:
14
+ res_list = re.findall(r'<tool_call>(.+?)</tool_call>', response, re.DOTALL)
15
+ functions = []
16
+ for res in res_list:
17
+ res = self._parse_json(res)
18
+ if isinstance(res, dict) and 'name' in res and 'arguments' in res:
19
+ functions.append(Function(name=res['name'], arguments=res['arguments']))
20
+ if len(functions) == 0:
21
+ # compat react_en
22
+ return super().get_toolcall(response)
23
+ return functions
24
+
25
+ def _get_tool_responses(self, tool_messages):
26
+ res_tool = []
27
+ for tool_message in tool_messages:
28
+ tool_content = tool_message['content']
29
+ res_tool.append(f'<tool_response>\n{tool_content}\n</tool_response>')
30
+ return '\n'.join(res_tool)
31
+
32
+ def _get_tool_calls(self, tool_calls: List[str]):
33
+ return '\n'.join(tool_calls)
34
+
35
+ def _format_tool_responses(
36
+ self,
37
+ assistant_content: str,
38
+ tool_messages,
39
+ ) -> Tuple[str, 'Prompt']:
40
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
41
+ if with_action:
42
+ return super()._format_tool_responses(assistant_content, tool_messages)
43
+ if hasattr(self, 'template_meta'):
44
+ prompt = self.template_meta.prompt
45
+ chat_sep = self.template_meta.chat_sep
46
+ else:
47
+ prompt = ['<|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n']
48
+ chat_sep = ['<|im_end|>\n']
49
+ res = chat_sep.copy()
50
+ total_tool = self._get_tool_responses(tool_messages)
51
+ for context in prompt:
52
+ if isinstance(context, str):
53
+ context = context.replace('{{QUERY}}', total_tool)
54
+ res.append(context)
55
+ return assistant_content, res
56
+
57
+ def _format_standalone_tool_responses(self, tool_messages) -> 'Prompt':
58
+ """Render standalone results as a native tool-response user turn."""
59
+ if not hasattr(self, 'template_meta'):
60
+ return super()._format_standalone_tool_responses(tool_messages)
61
+
62
+ res = (self.template_meta.chat_sep or []).copy()
63
+ total_tool = self._get_tool_responses(tool_messages)
64
+ for context in self.template_meta.prompt:
65
+ if isinstance(context, str) and '{{QUERY}}' in context:
66
+ query_prefix = context.split('{{QUERY}}', maxsplit=1)[0]
67
+ if query_prefix:
68
+ res.append(query_prefix)
69
+ res.append(total_tool)
70
+ return res
71
+ res.append(context)
72
+ raise ValueError(f'Template prompt does not contain {{{{QUERY}}}}: {self.template_meta.prompt}')
73
+
74
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
75
+ tool_descs = [json.dumps(self.wrap_tool(tool), ensure_ascii=False) for tool in tools]
76
+ system = system or ''
77
+ return f"""{system}
78
+
79
+ # Tools
80
+
81
+ You may call one or more functions to assist with the user query.
82
+
83
+ You are provided with function signatures within <tools></tools> XML tags:
84
+ <tools>
85
+ """ + '\n'.join(tool_descs) + """
86
+ </tools>
87
+
88
+ For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
89
+ <tool_call>
90
+ {"name": <function-name>, "arguments": <args-json-object>}
91
+ </tool_call>"""
92
+
93
+ def _format_tool_calls(self, tool_call_messages):
94
+ tool_calls = []
95
+ for message in tool_call_messages:
96
+ tool_call = self._parse_tool_call(message['content'])
97
+ tool_calls.append(f'<tool_call>\n{json.dumps(tool_call, ensure_ascii=False)}\n</tool_call>')
98
+ return self._get_tool_calls(tool_calls)
99
+
100
+
101
+ class HunyuanHermesAgentTemplate(HermesAgentTemplate):
102
+
103
+ def get_toolcall(self, response: str) -> List[Function]:
104
+ res_list = re.findall(r'<tool_call>(.+?)\n```json(.+?)```</tool_call>', response, re.DOTALL)
105
+ functions = []
106
+ for name, arguments in res_list:
107
+ arguments = self._parse_json(arguments)
108
+ functions.append(Function(name=name, arguments=arguments))
109
+ if len(functions) == 0:
110
+ # compat react_en
111
+ return super().get_toolcall(response)
112
+ return functions
113
+
114
+ def _get_tool_responses(self, tool_messages):
115
+ res_tool = []
116
+ for tool_message in tool_messages:
117
+ tool_content = tool_message['content']
118
+ res_tool.append(f'<tool_response>{tool_content}</tool_response>')
119
+ tool_responses = '\n'.join(res_tool)
120
+ return f'<tool_responses>{tool_responses}</tool_responses>'
121
+
122
+ def _get_tool_calls(self, tool_calls: List[str]):
123
+ tool_calls = '\n'.join(tool_calls)
124
+ return f'<tool_calls>\n{tool_calls}\n</tool_calls>'
125
+
126
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
127
+ tool_descs = [json.dumps(self.wrap_tool(tool), ensure_ascii=False) for tool in tools]
128
+ system = system or ''
129
+ if system:
130
+ system = f'{system}\n\n'
131
+ return f"""{system}# Tools
132
+
133
+ You may call one or more functions to assist with the user query.
134
+
135
+ You are provided with function signatures within <tools></tools> XML tags:
136
+ <tools>
137
+ """ + '\n'.join(tool_descs) + """
138
+ </tools>
139
+
140
+ For function call returns, you should first print <tool_calls>For each function call, you should return object like:
141
+ <tool_call>function_name
142
+ ```json
143
+ function_arguments_in_json_format
144
+ ```</tool_call>At the end of function call returns, you should print </tool_calls>"""
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/hy_v3.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class HyV3PreviewAgentTemplate(BaseAgentTemplate):
12
+ HYTK = ''
13
+
14
+ def get_toolcall(self, response: str) -> List[Function]:
15
+ # Parse tool calls from <tool_calls>...<tool_call>name<tool_sep>...<arg_key>...<arg_value>...</tool_call>...
16
+ tool_call_blocks = re.findall(rf'<tool_call{self.HYTK}>(.*?)</tool_call{self.HYTK}>', response, re.DOTALL)
17
+ functions = []
18
+ for block in tool_call_blocks:
19
+ # Extract function name: text before <tool_sep>
20
+ name_match = re.match(rf'(.*?)<tool_sep{self.HYTK}>', block, re.DOTALL)
21
+ if not name_match:
22
+ continue
23
+ name = name_match.group(1).strip()
24
+ # Extract arg_key/arg_value pairs together to avoid misalignment
25
+ pairs = re.findall(
26
+ rf'<arg_key{self.HYTK}>(.*?)</arg_key{self.HYTK}>\s*<arg_value{self.HYTK}>(.*?)</arg_value{self.HYTK}>',
27
+ block, re.DOTALL)
28
+ arguments = {}
29
+ for k, v in pairs:
30
+ k = k.strip()
31
+ v = v.strip()
32
+ parsed = self._parse_json(v)
33
+ arguments[k] = parsed if parsed is not None else v
34
+ functions.append(Function(name=name, arguments=arguments))
35
+ if len(functions) == 0:
36
+ # compat react_en
37
+ return super().get_toolcall(response)
38
+ return functions
39
+
40
+ def _get_tool_responses(self, tool_messages):
41
+ res_tool = []
42
+ for tool_message in tool_messages:
43
+ tool_content = tool_message['content']
44
+ res_tool.append(f'<tool_response{self.HYTK}>\n{tool_content}\n</tool_response{self.HYTK}>')
45
+ tool_responses = '\n'.join(res_tool)
46
+ return f'<tool_responses{self.HYTK}>\n{tool_responses}\n</tool_responses{self.HYTK}>'
47
+
48
+ def _get_tool_calls(self, tool_calls: List[str]):
49
+ tool_calls_str = '\n'.join(tool_calls)
50
+ return f'<tool_calls{self.HYTK}>\n{tool_calls_str}\n</tool_calls{self.HYTK}>'
51
+
52
+ def _format_tool_responses(
53
+ self,
54
+ assistant_content: str,
55
+ tool_messages,
56
+ ) -> Tuple[str, 'Prompt']:
57
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
58
+ if with_action:
59
+ return super()._format_tool_responses(assistant_content, tool_messages)
60
+ res = [f'<|hy_eos{self.HYTK}|>', self._get_tool_responses(tool_messages), f'<|hy_Assistant{self.HYTK}|>']
61
+ return assistant_content, res
62
+
63
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
64
+ tool_descs = [json.dumps(self.wrap_tool(tool), ensure_ascii=False) for tool in tools]
65
+ system = system or ''
66
+ if system:
67
+ system = f'{system}\n\n'
68
+ return f"""{system}# Tools
69
+
70
+ You may call one or more functions to assist with the user query.
71
+
72
+ You are provided with function signatures within <tools></tools> XML tags:
73
+ <tools>
74
+ """ + '\n'.join(tool_descs) + f"""
75
+ </tools>
76
+
77
+ For function call returns, you should first print <tool_calls{self.HYTK}>
78
+ For each function call, you should return object like:
79
+ <tool_call{self.HYTK}>{{function-name}}<tool_sep{self.HYTK}>
80
+ <arg_key{self.HYTK}>{{arg-key-1}}</arg_key{self.HYTK}>
81
+ <arg_value{self.HYTK}>{{arg-value-1}}</arg_value{self.HYTK}>
82
+ <arg_key{self.HYTK}>{{arg-key-2}}</arg_key{self.HYTK}>
83
+ <arg_value{self.HYTK}>{{arg-value-2}}</arg_value{self.HYTK}>
84
+ ...
85
+ </tool_call{self.HYTK}>
86
+ At the end of function call returns, you should print </tool_calls{self.HYTK}>"""
87
+
88
+ def _format_tool_calls(self, tool_call_messages):
89
+ tool_calls = []
90
+ for message in tool_call_messages:
91
+ tool_call = self._parse_tool_call(message['content'])
92
+ name = tool_call['name']
93
+ arguments = tool_call['arguments']
94
+ arg_lines = []
95
+ if isinstance(arguments, dict):
96
+ for k, v in arguments.items():
97
+ if not isinstance(v, str):
98
+ v = json.dumps(v, ensure_ascii=False)
99
+ arg_lines.append(f'<arg_key{self.HYTK}>{k}</arg_key{self.HYTK}>\n'
100
+ f'<arg_value{self.HYTK}>{v}</arg_value{self.HYTK}>')
101
+ arg_str = '\n'.join(arg_lines)
102
+ tool_calls.append(f'<tool_call{self.HYTK}>{name}<tool_sep{self.HYTK}>\n{arg_str}\n</tool_call{self.HYTK}>')
103
+ return self._get_tool_calls(tool_calls)
104
+
105
+
106
+ class HyV3AgentTemplate(HyV3PreviewAgentTemplate):
107
+ HYTK = ':opensource'
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/kimi_k25.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class KimiK25AgentTemplate(BaseAgentTemplate):
12
+ """Agent template for Kimi K2.5/K2.6 models.
13
+
14
+ Tool calling format:
15
+ - Tools are declared in a separate system message with role 'tool_declare'
16
+ using TypeScript namespace format.
17
+ - Tool calls:
18
+ <|tool_calls_section_begin|>
19
+ <|tool_call_begin|>{function_name}<|tool_call_argument_begin|>{args_json}<|tool_call_end|>
20
+ <|tool_calls_section_end|>
21
+ - Tool response:
22
+ <|im_system|>tool<|im_middle|>## Return of {tool_call_id}
23
+ {content}<|im_end|>
24
+ """
25
+
26
+ @staticmethod
27
+ def _json_type_to_ts(json_type):
28
+ type_map = {
29
+ 'string': 'string',
30
+ 'number': 'number',
31
+ 'integer': 'number',
32
+ 'boolean': 'boolean',
33
+ 'array': 'any[]',
34
+ 'object': 'object',
35
+ 'null': 'null',
36
+ }
37
+ return type_map.get(json_type, 'any')
38
+
39
+ def _tools_to_typescript(self, tools):
40
+ parts = []
41
+ for tool in tools:
42
+ tool = self.unwrap_tool(tool)
43
+ name = self._get_tool_name(tool)
44
+ description = tool.get('description', '')
45
+ parameters = tool.get('parameters', {})
46
+ properties = parameters.get('properties', {})
47
+
48
+ lines = []
49
+ if description:
50
+ lines.append(f'// {description}')
51
+
52
+ if not properties:
53
+ lines.append(f'type {name} = (_: {{}}) => any;')
54
+ else:
55
+ lines.append(f'type {name} = (_: {{')
56
+ props = list(properties.items())
57
+ for i, (pname, pschema) in enumerate(props):
58
+ pdesc = pschema.get('description', '')
59
+ ptype = self._json_type_to_ts(pschema.get('type', ''))
60
+ if pdesc:
61
+ lines.append(f' // {pdesc}')
62
+ if i < len(props) - 1:
63
+ lines.append(f' {pname}: {ptype},')
64
+ else:
65
+ lines.append(f' {pname}: {ptype}')
66
+ lines.append('}) => any;')
67
+ parts.append('\n'.join(lines))
68
+ return '\n'.join(parts)
69
+
70
+ def get_toolcall(self, response: str) -> List[Function]:
71
+ pattern = r'<\|tool_call_begin\|>(.*?)<\|tool_call_argument_begin\|>(.*?)<\|tool_call_end\|>'
72
+ res_list = re.findall(pattern, response, re.DOTALL)
73
+ functions = []
74
+ for name, arguments in res_list:
75
+ name = name.strip()
76
+ arguments = arguments.strip()
77
+ parsed_args = self._parse_json(arguments)
78
+ if parsed_args is not None:
79
+ functions.append(Function(name=name, arguments=parsed_args))
80
+ else:
81
+ functions.append(Function(name=name, arguments=arguments))
82
+ if len(functions) == 0:
83
+ # compat react_en
84
+ return super().get_toolcall(response)
85
+ return functions
86
+
87
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
88
+ ts_tools = self._tools_to_typescript(tools)
89
+ tool_content = f'# Tools\n\n## functions\nnamespace functions {{\n{ts_tools}\n}}\n'
90
+ system = system or ''
91
+ res = f'tool_declare<|im_middle|>{tool_content}'
92
+ if system:
93
+ res += f'<|im_end|><|im_system|>system<|im_middle|>{system}'
94
+ return res
95
+
96
+ def _format_tool_calls(self, tool_call_messages) -> str:
97
+ tool_calls = []
98
+ for message in tool_call_messages:
99
+ tool_call = self._parse_tool_call(message['content'])
100
+ name = tool_call['name']
101
+ arguments = json.dumps(tool_call['arguments'], ensure_ascii=False)
102
+ tool_calls.append(f'<|tool_call_begin|>{name}<|tool_call_argument_begin|>{arguments}<|tool_call_end|>')
103
+ return f'<|tool_calls_section_begin|>{"".join(tool_calls)}<|tool_calls_section_end|>'
104
+
105
+ def _format_tool_responses(
106
+ self,
107
+ assistant_content: str,
108
+ tool_messages,
109
+ ) -> Tuple[str, 'Prompt']:
110
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
111
+ if with_action:
112
+ return super()._format_tool_responses(assistant_content, tool_messages)
113
+ res = ['<|im_end|>']
114
+ for tool_message in tool_messages:
115
+ tool_call_id = tool_message.get('tool_call_id', '')
116
+ tool_content = tool_message['content']
117
+ res.append(f'<|im_system|>tool<|im_middle|>## Return of {tool_call_id}\n{tool_content}<|im_end|>')
118
+ res.append('<|im_assistant|>assistant<|im_middle|>')
119
+ return assistant_content, res
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/kimi_k3.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class KimiK3AgentTemplate(BaseAgentTemplate):
12
+ """Agent template for Kimi K3 models (XTML format).
13
+
14
+ Aligned with `encoding_k3.py` in the model repo:
15
+ - Tools are declared in a separate system message with `type="tool-declare"`,
16
+ carrying the deep-sorted JSONSchema in a compact ```json block.
17
+ - Tool calls follow the closed response channel:
18
+ <|close|>response<|sep|><|open|>tools<|sep|>
19
+ <|open|>call tool="{name}" index="{i}"<|sep|>
20
+ <|open|>argument key="{key}" type="{type}"<|sep|>{value}<|close|>argument<|sep|>
21
+ <|close|>call<|sep|><|close|>tools<|sep|>
22
+ - Tool response:
23
+ <|open|>message role="tool" tool="{name}" index="{i}"<|sep|>{content}<|close|>message<|sep|><|end_of_msg|>
24
+ """
25
+
26
+ @staticmethod
27
+ def _deep_sort(obj):
28
+ if isinstance(obj, dict):
29
+ return {k: KimiK3AgentTemplate._deep_sort(v) for k, v in sorted(obj.items())}
30
+ if isinstance(obj, list):
31
+ return [KimiK3AgentTemplate._deep_sort(item) for item in obj]
32
+ return obj
33
+
34
+ @staticmethod
35
+ def _escape_attr(value) -> str:
36
+ return str(value).replace('&', '&amp;').replace('"', '&quot;')
37
+
38
+ @staticmethod
39
+ def _unescape_attr(value: str) -> str:
40
+ return value.replace('&quot;', '"').replace('&amp;', '&')
41
+
42
+ @staticmethod
43
+ def _xtml_type(value) -> str:
44
+ if isinstance(value, bool):
45
+ return 'boolean'
46
+ if value is None:
47
+ return 'null'
48
+ if isinstance(value, (int, float)):
49
+ return 'number'
50
+ if isinstance(value, str):
51
+ return 'string'
52
+ if isinstance(value, dict):
53
+ return 'object'
54
+ return 'array'
55
+
56
+ @staticmethod
57
+ def _xtml_value(value) -> str:
58
+ if isinstance(value, str):
59
+ return value
60
+ return json.dumps(value, ensure_ascii=False)
61
+
62
+ def get_toolcall(self, response: str) -> List[Function]:
63
+ call_pattern = r'<\|open\|>call tool="(.*?)"(?: index="\d+")?<\|sep\|>(.*?)<\|close\|>call<\|sep\|>'
64
+ arg_pattern = r'<\|open\|>argument key="(.*?)" type="(.*?)"<\|sep\|>(.*?)<\|close\|>argument<\|sep\|>'
65
+ json_pattern = r'<\|open\|>json type="object"<\|sep\|>(.*?)<\|close\|>json<\|sep\|>'
66
+ functions = []
67
+ for name, body in re.findall(call_pattern, response, re.DOTALL):
68
+ name = self._unescape_attr(name)
69
+ json_match = re.search(json_pattern, body, re.DOTALL)
70
+ if json_match:
71
+ arguments = self._parse_json(json_match.group(1))
72
+ if arguments is None:
73
+ arguments = json_match.group(1)
74
+ else:
75
+ arguments = {}
76
+ for key, arg_type, value in re.findall(arg_pattern, body, re.DOTALL):
77
+ key = self._unescape_attr(key)
78
+ if arg_type == 'string':
79
+ arguments[key] = value
80
+ elif arg_type == 'null':
81
+ arguments[key] = None
82
+ else:
83
+ parsed = self._parse_json(value)
84
+ arguments[key] = value if parsed is None else parsed
85
+ functions.append(Function(name=name, arguments=arguments))
86
+ if len(functions) == 0:
87
+ # compat react_en
88
+ return super().get_toolcall(response)
89
+ return functions
90
+
91
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
92
+ tools = self._deep_sort([self.wrap_tool(tool) for tool in tools])
93
+ tools_json = json.dumps(tools, ensure_ascii=False, separators=(',', ':'))
94
+ tool_content = ('# Tools\nHere are the available tools, described in JSONSchema.\n\n'
95
+ f'```json\n{tools_json}\n```')
96
+ # Hijack the `<|open|>message {{SYSTEM}}<|close|>message<|sep|><|end_of_msg|>` system
97
+ # prefix so the tool-declare message is rendered before the plain system message.
98
+ res = f'role="system" type="tool-declare"<|sep|>{tool_content}'
99
+ if system:
100
+ res += f'<|close|>message<|sep|><|end_of_msg|><|open|>message role="system"<|sep|>{system}'
101
+ return res
102
+
103
+ def _format_tool_calls(self, tool_call_messages) -> str:
104
+ calls = []
105
+ for index, message in enumerate(tool_call_messages, start=1):
106
+ tool_call = self._parse_tool_call(message['content'])
107
+ name, arguments = tool_call['name'], tool_call['arguments']
108
+ call = [f'<|open|>call tool="{self._escape_attr(name)}" index="{index}"<|sep|>']
109
+ if isinstance(arguments, dict):
110
+ for key, value in arguments.items():
111
+ call.append(f'<|open|>argument key="{self._escape_attr(key)}" '
112
+ f'type="{self._xtml_type(value)}"<|sep|>'
113
+ f'{self._xtml_value(value)}<|close|>argument<|sep|>')
114
+ else:
115
+ # non-object arguments fall back to a raw json block
116
+ json_block = arguments if isinstance(arguments, str) else json.dumps(arguments, ensure_ascii=False)
117
+ call.append(f'<|open|>json type="object"<|sep|>{json_block}<|close|>json<|sep|>')
118
+ call.append('<|close|>call<|sep|>')
119
+ calls.append(''.join(call))
120
+ return f'<|open|>tools<|sep|>{"".join(calls)}<|close|>tools<|sep|>'
121
+
122
+ def _add_tool_call_prefix(self, tool_content: str, pre_message=None) -> str:
123
+ # Without a preceding assistant text message (whose converted content already ends
124
+ # with `<|close|>response<|sep|>`), the empty response channel must be closed here.
125
+ if pre_message is None or pre_message['role'] != 'assistant':
126
+ tool_content = '<|close|>response<|sep|>' + tool_content
127
+ return tool_content
128
+
129
+ def _format_tool_responses(
130
+ self,
131
+ assistant_content,
132
+ tool_messages,
133
+ ) -> Tuple[str, 'Prompt']:
134
+ contents = assistant_content if isinstance(assistant_content, list) else [assistant_content]
135
+ joined = ''.join(c for c in contents if isinstance(c, str))
136
+ with_action = self.keyword.action in joined and self.keyword.action_input in joined
137
+ if with_action:
138
+ return super()._format_tool_responses(assistant_content, tool_messages)
139
+ # `tool`/`index` attributes are resolved from the preceding assistant tool calls,
140
+ # in call order (aligned with the `tool_index` enumeration in encoding_k3.py).
141
+ names = re.findall(r'<\|open\|>call tool="(.*?)" index="\d+"<\|sep\|>', joined)
142
+ res = ['<|close|>message<|sep|><|end_of_msg|>']
143
+ for index, tool_message in enumerate(tool_messages, start=1):
144
+ if index <= len(names):
145
+ name = self._unescape_attr(names[index - 1])
146
+ else:
147
+ name = tool_message.get('tool') or tool_message.get('name') or ''
148
+ res.append(f'<|open|>message role="tool" tool="{self._escape_attr(name)}" index="{index}"<|sep|>'
149
+ f'{tool_message["content"]}<|close|>message<|sep|><|end_of_msg|>')
150
+ res.append('<|open|>message role="assistant"<|sep|>')
151
+ return assistant_content, res
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/llama.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class Llama3AgentTemplate(BaseAgentTemplate):
12
+ eom_token = '<|eom_id|>'
13
+ start_token = '<|start_header_id|>'
14
+ end_token = '<|end_header_id|>'
15
+ eot_token = '<|eot_id|>'
16
+
17
+ def get_toolcall(self, response: str) -> List[Function]:
18
+ if response.endswith(self.eom_token):
19
+ response = response[:-len(self.eom_token)]
20
+ functions = []
21
+ res_list = re.findall(r'{[^{]*?"name":.*?"parameters":\s*?{.*?}\s*?}', response, re.DOTALL)
22
+ for res in res_list:
23
+ res = self._parse_json(res)
24
+ if isinstance(res, dict) and 'name' in res and 'parameters' in res:
25
+ functions.append(Function(name=res['name'], arguments=res['parameters']))
26
+ if len(functions) == 0:
27
+ # compat react_en
28
+ return super().get_toolcall(response)
29
+ return functions
30
+
31
+ def _format_tool_responses(
32
+ self,
33
+ assistant_content: str,
34
+ tool_messages,
35
+ ) -> Tuple[str, 'Prompt']:
36
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
37
+ if with_action:
38
+ return super()._format_tool_responses(assistant_content, tool_messages)
39
+ res = [self.eot_token]
40
+ for tool_message in tool_messages:
41
+ tool_content = tool_message['content']
42
+ res.append(f'{self.start_token}tool{self.end_token}\n\n{tool_content}{self.eot_token}')
43
+ res.append(f'{self.start_token}assistant{self.end_token}\n\n')
44
+ return assistant_content, res
45
+
46
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
47
+ assert user_message is not None
48
+ user_content = user_message['content']
49
+ tool_descs = [json.dumps(tool, ensure_ascii=False, indent=4) for tool in tools]
50
+ new_user_content = """Given the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.
51
+
52
+ Respond in the format {"name": function name, "parameters": dictionary of argument name and its value}. Do not use variables.
53
+
54
+ """ + '\n\n'.join(tool_descs) + f"""
55
+
56
+ {user_content}""" # noqa
57
+ user_message['content'] = new_user_content
58
+ return system or ''
59
+
60
+ def _format_tool_calls(self, tool_call_messages) -> str:
61
+ tool_calls = []
62
+ for message in tool_call_messages:
63
+ tool_call = self._parse_tool_call(message['content'])
64
+ tool_call['parameters'] = tool_call.pop('arguments')
65
+ tool_calls.append(json.dumps(tool_call, ensure_ascii=False))
66
+ return '\n'.join(tool_calls)
67
+
68
+
69
+ class Llama4AgentTemplate(Llama3AgentTemplate):
70
+ eom_token = '<|eom|>'
71
+ start_token = '<|header_start|>'
72
+ end_token = '<|header_end|>'
73
+ eot_token = '<|eot|>'
74
+ toolcall_pattern = r'(.+?)<\|eom\|>'
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/mapping.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ from .deepseek_v3_1 import DeepSeekV31AgentTemplate
3
+ from .deepseek_v4 import DeepSeekV4AgentTemplate
4
+ from .extra import ReactGRPOAgentTemplate
5
+ from .gemma4 import Gemma4AgentTemplate
6
+ from .glm4 import (ChatGLM4AgentTemplate, GLM4_5AgentTemplate, GLM4_7AgentTemplate, GLM4AgentTemplate,
7
+ GLM5_1AgentTemplate)
8
+ from .hermes import HermesAgentTemplate, HunyuanHermesAgentTemplate
9
+ from .hy_v3 import HyV3AgentTemplate, HyV3PreviewAgentTemplate
10
+ from .kimi_k3 import KimiK3AgentTemplate
11
+ from .kimi_k25 import KimiK25AgentTemplate
12
+ from .llama import Llama3AgentTemplate, Llama4AgentTemplate
13
+ from .minicpm5 import MiniCPM5AgentTemplate
14
+ from .minimax_m2 import MinimaxM2AgentTemplate
15
+ from .minimax_m3 import MinimaxM3AgentTemplate
16
+ from .mistral import MistralAgentTemplate
17
+ from .qwen import QwenEnAgentTemplate, QwenEnParallelAgentTemplate, QwenZhAgentTemplate, QwenZhParallelAgentTemplate
18
+ from .qwen3_coder import Qwen3_5AgentTemplate, Qwen3CoderAgentTemplate
19
+ from .react import ReactEnAgentTemplate, ReactZnAgentTemplate
20
+ from .seed_oss import SeedAgentTemplate
21
+ from .toolbench import ToolBenchAgentTemplate
22
+ from .youtu import YoutuAgentTemplate
23
+
24
+ agent_template_map = {
25
+ # ref: https://qwen.readthedocs.io/zh-cn/latest/framework/function_call.html#function-calling-templates
26
+ 'react_en': ReactEnAgentTemplate,
27
+ 'react_zh': ReactZnAgentTemplate,
28
+ # ref: https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/llm/fncall_prompts/qwen_fncall_prompt.py
29
+ 'qwen_en': QwenEnAgentTemplate,
30
+ 'qwen_zh': QwenZhAgentTemplate,
31
+ 'qwen_en_parallel': QwenEnParallelAgentTemplate,
32
+ 'qwen_zh_parallel': QwenZhParallelAgentTemplate,
33
+ 'qwen3_coder': Qwen3CoderAgentTemplate,
34
+ 'qwen3_5': Qwen3_5AgentTemplate,
35
+ 'hermes': HermesAgentTemplate,
36
+ 'hunyuan_hermes': HunyuanHermesAgentTemplate,
37
+ 'hy_v3_preview': HyV3PreviewAgentTemplate,
38
+ 'hy_v3': HyV3AgentTemplate,
39
+ 'toolbench': ToolBenchAgentTemplate, # ref: https://modelscope.cn/datasets/swift/ToolBench
40
+ 'chatglm4': ChatGLM4AgentTemplate,
41
+ 'glm4': GLM4AgentTemplate, # ref: https://modelscope.cn/models/ZhipuAI/GLM-4-9B-0414
42
+ 'glm4_5': GLM4_5AgentTemplate,
43
+ 'glm4_7': GLM4_7AgentTemplate,
44
+ 'glm5_1': GLM5_1AgentTemplate,
45
+ 'llama3': Llama3AgentTemplate,
46
+ 'llama4': Llama4AgentTemplate,
47
+ # ref: https://huggingface.co/deepseek-ai/DeepSeek-V3.1
48
+ 'deepseek_v3_1': DeepSeekV31AgentTemplate,
49
+ # ref: https://modelscope.cn/models/deepseek-ai/DeepSeek-V4-Flash
50
+ 'deepseek_v4': DeepSeekV4AgentTemplate,
51
+ 'minimax_m2': MinimaxM2AgentTemplate,
52
+ 'minimax_m3': MinimaxM3AgentTemplate,
53
+ 'seed_oss': SeedAgentTemplate,
54
+ # ref: https://modelscope.cn/models/google/gemma-4-12B-it
55
+ 'gemma4': Gemma4AgentTemplate,
56
+ # extra
57
+ 'react_grpo': ReactGRPOAgentTemplate,
58
+ 'mistral': MistralAgentTemplate,
59
+ 'youtu': YoutuAgentTemplate,
60
+ 'kimi_k25': KimiK25AgentTemplate,
61
+ 'kimi_k3': KimiK3AgentTemplate,
62
+ 'minicpm5': MiniCPM5AgentTemplate,
63
+ }
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/minicpm5.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class MiniCPM5AgentTemplate(BaseAgentTemplate):
12
+ """Agent template for MiniCPM5 models using XML-based function calling format.
13
+
14
+ Tool call format:
15
+ <function name="function-name"><param name="param-name">param-value</param></function>
16
+
17
+ Tool response format:
18
+ <tool_response>
19
+ response_content
20
+ </tool_response>
21
+ """
22
+
23
+ def get_toolcall(self, response: str) -> List[Function]:
24
+ # Match <function name="...">...</function> blocks
25
+ func_pattern = re.compile(r'<function\s+name="([^"]+)">(.*?)</function>', re.DOTALL)
26
+ param_pattern = re.compile(r'<param\s+name="([^"]+)">'
27
+ r'(?:<!\[CDATA\[(.*?)\]\]>|([^<]*))'
28
+ r'</param>', re.DOTALL)
29
+
30
+ functions = []
31
+ for func_match in func_pattern.finditer(response):
32
+ func_name = func_match.group(1)
33
+ func_body = func_match.group(2)
34
+ arguments = {}
35
+ for param_match in param_pattern.finditer(func_body):
36
+ param_name = param_match.group(1)
37
+ # CDATA value or plain value
38
+ param_value = param_match.group(2) if param_match.group(2) is not None else param_match.group(3)
39
+ # Try to parse as JSON value (number, bool, etc.)
40
+ try:
41
+ param_value = json.loads(param_value)
42
+ except (json.JSONDecodeError, ValueError):
43
+ pass
44
+ arguments[param_name] = param_value
45
+ functions.append(Function(name=func_name, arguments=arguments))
46
+
47
+ if len(functions) == 0:
48
+ # Fallback to ReAct-style parsing
49
+ return super().get_toolcall(response)
50
+ return functions
51
+
52
+ def _get_tool_responses(self, tool_messages):
53
+ res_tool = []
54
+ for tool_message in tool_messages:
55
+ tool_content = tool_message['content']
56
+ res_tool.append(f'<tool_response>\n{tool_content}\n</tool_response>')
57
+ return '\n'.join(res_tool)
58
+
59
+ def _format_tool_responses(
60
+ self,
61
+ assistant_content: str,
62
+ tool_messages,
63
+ ) -> Tuple[str, 'Prompt']:
64
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
65
+ if with_action:
66
+ return super()._format_tool_responses(assistant_content, tool_messages)
67
+ if hasattr(self, 'template_meta'):
68
+ prompt = self.template_meta.prompt
69
+ chat_sep = self.template_meta.chat_sep
70
+ else:
71
+ prompt = ['<|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n']
72
+ chat_sep = ['<|im_end|>\n']
73
+ res = chat_sep.copy()
74
+ total_tool = self._get_tool_responses(tool_messages)
75
+ for context in prompt:
76
+ if isinstance(context, str):
77
+ context = context.replace('{{QUERY}}', total_tool)
78
+ res.append(context)
79
+ return assistant_content, res
80
+
81
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
82
+ tool_descs = [json.dumps(self.wrap_tool(tool), ensure_ascii=False) for tool in tools]
83
+ system = system or ''
84
+ if system:
85
+ system = f'{system}\n\n'
86
+ return (f'{system}# Tools\n\n'
87
+ 'You are provided with function signatures within <tools></tools> XML tags:\n'
88
+ '<tools>\n' + '\n'.join(tool_descs) + '\n</tools>\n\n'
89
+ 'Tool usage guidelines:\n'
90
+ '- You may call zero or more functions. If no function calls are needed, '
91
+ 'just answer normally and do not include any <function ... </function>.\n'
92
+ '- When calling a function, return an XML object within <function ... </function> using:\n'
93
+ '<function name="function-name"><param name="param-name">param-value</param></function>\n'
94
+ '- param-value may be multi-line. If it contains <, & or newline characters, '
95
+ 'wrap it in a CDATA block: <param name="param-name"><![CDATA[...multi-line value...]]></param>')
96
+
97
+ def _format_tool_calls(self, tool_call_messages) -> str:
98
+ tool_calls = []
99
+ for message in tool_call_messages:
100
+ tool_call = self._parse_tool_call(message['content'])
101
+ name = tool_call['name']
102
+ arguments = tool_call['arguments']
103
+ params_xml = ''
104
+ if isinstance(arguments, dict):
105
+ for param_name, param_value in arguments.items():
106
+ value_str = param_value if isinstance(param_value, str) else json.dumps(
107
+ param_value, ensure_ascii=False)
108
+ if isinstance(param_value, str) and ('<' in param_value or '&' in param_value
109
+ or '\n' in param_value):
110
+ params_xml += f'<param name="{param_name}"><![CDATA[{value_str}]]></param>'
111
+ else:
112
+ params_xml += f'<param name="{param_name}">{value_str}</param>'
113
+ tool_calls.append(f'<function name="{name}">{params_xml}</function>')
114
+ return '\n'.join(tool_calls)
pipelines/Tr006_V2/project/ms-swift/swift/agent_template/minimax_m2.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) ModelScope Contributors. All rights reserved.
2
+ import json
3
+ import re
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ from swift.infer_engine import Function
7
+ from swift.template import Prompt
8
+ from .base import BaseAgentTemplate
9
+
10
+
11
+ class MinimaxM2AgentTemplate(BaseAgentTemplate):
12
+ """
13
+ Agent template for MiniMax-M2 series models.
14
+
15
+ This template handles tool calling in MiniMax's XML-based format:
16
+ <minimax:tool_call>
17
+ <invoke name="tool-name">
18
+ <parameter name="param-key">param-value</parameter>
19
+ </invoke>
20
+ </minimax:tool_call>
21
+ """
22
+
23
+ def get_toolcall(self, response: str) -> List[Function]:
24
+ """
25
+ Extract tool calls from MiniMax response format.
26
+
27
+ Format:
28
+ <minimax:tool_call>
29
+ <invoke name="tool-name">
30
+ <parameter name="param-key">param-value</parameter>
31
+ </invoke>
32
+ </minimax:tool_call>
33
+ """
34
+ functions = []
35
+
36
+ # Find all tool_call blocks
37
+ tool_call_blocks = re.findall(r'<minimax:tool_call>(.*?)</minimax:tool_call>', response, re.DOTALL)
38
+
39
+ for block in tool_call_blocks:
40
+ # Find all invoke blocks within the tool_call
41
+ invoke_blocks = re.findall(r'<invoke name="([^"]+)">(.*?)</invoke>', block, re.DOTALL)
42
+
43
+ for tool_name, params_block in invoke_blocks:
44
+ # Extract parameters
45
+ params = {}
46
+ param_matches = re.findall(r'<parameter name="([^"]+)">(.*?)</parameter>', params_block, re.DOTALL)
47
+
48
+ for param_name, param_value in param_matches:
49
+ param_value = param_value.strip()
50
+ # Try to parse as JSON if it looks like a JSON structure
51
+ parsed_value = self._parse_json(param_value)
52
+ params[param_name] = parsed_value if parsed_value is not None else param_value
53
+
54
+ functions.append(Function(name=tool_name, arguments=params))
55
+
56
+ # Fallback to react format if no functions found
57
+ if len(functions) == 0:
58
+ return super().get_toolcall(response)
59
+
60
+ return functions
61
+
62
+ def _format_tool_responses(
63
+ self,
64
+ assistant_content: str,
65
+ tool_messages,
66
+ ) -> Tuple[str, 'Prompt']:
67
+ """
68
+ Format tool execution results in MiniMax format.
69
+
70
+ Tool responses are wrapped in <response></response> tags.
71
+ """
72
+ # Check if using react format
73
+ with_action = self.keyword.action in assistant_content and self.keyword.action_input in assistant_content
74
+ if with_action:
75
+ return super()._format_tool_responses(assistant_content, tool_messages)
76
+
77
+ # Use template meta if available
78
+ if hasattr(self, 'template_meta'):
79
+ prompt = self.template_meta.prompt.copy()
80
+ chat_sep = self.template_meta.chat_sep
81
+ for i in range(len(prompt)):
82
+ if isinstance(prompt[i], str):
83
+ prompt[i] = prompt[i].replace('user', 'tool')
84
+ else:
85
+ # Default format based on the Jinja2 template
86
+ prompt = [']~b]tool\n{{QUERY}}[e~[\n']
87
+ chat_sep = ['[e~[\n']
88
+
89
+ res = chat_sep.copy()
90
+
91
+ # Format tool responses
92
+ tool_responses = []
93
+ for tool_message in tool_messages:
94
+ tool_content = tool_message['content']
95
+ tool_responses.append(f'<response>{tool_content}</response>')
96
+
97
+ total_tool = '\n'.join(tool_responses)
98
+
99
+ for context in prompt:
100
+ if isinstance(context, str):
101
+ context = context.replace('{{QUERY}}', total_tool)
102
+ res.append(context)
103
+
104
+ return assistant_content, res
105
+
106
+ def _format_tools(self, tools: List[Union[str, dict]], system: Optional[str] = None, user_message=None) -> str:
107
+ """
108
+ Format tools in MiniMax format with JSONSchema and XML invocation examples.
109
+ """
110
+ # Parse tools to JSONSchema format
111
+ tool_schemas = []
112
+ for tool in tools:
113
+ tool = self.unwrap_tool(tool)
114
+ tool_schemas.append(json.dumps(tool, ensure_ascii=False))
115
+
116
+ system = system or ''
117
+
118
+ return f"""{system}
119
+
120
+ # Tools
121
+ You may call one or more tools to assist with the user query.
122
+ Here are the tools available in JSONSchema format:
123
+
124
+ <tools>
125
+ """ + '\n'.join(f'<tool>{schema}</tool>' for schema in tool_schemas) + """
126
+ </tools>
127
+
128
+ When making tool calls, use XML format to invoke tools and pass parameters:
129
+
130
+ <minimax:tool_call>
131
+ <invoke name="tool-name-1">
132
+ <parameter name="param-key-1">param-value-1</parameter>
133
+ <parameter name="param-key-2">param-value-2</parameter>
134
+ ...
135
+ </invoke>
136
+ </minimax:tool_call>"""
137
+
138
+ def _format_tool_calls(self, tool_call_messages):
139
+ """
140
+ Format tool call messages into MiniMax XML format.
141
+
142
+ Args:
143
+ tool_call_messages: List of messages containing tool call information.
144
+
145
+ Returns:
146
+ Formatted string with tool calls in MiniMax XML format.
147
+ """
148
+ tool_calls = []
149
+
150
+ for message in tool_call_messages:
151
+ tool_call = self._parse_tool_call(message['content'])
152
+ name = tool_call['name']
153
+ arguments = tool_call['arguments']
154
+
155
+ # Build parameter list
156
+ params = []
157
+ for key, value in arguments.items():
158
+ # Convert value to JSON string if it's not a string
159
+ if not isinstance(value, str):
160
+ value = json.dumps(value, ensure_ascii=False)
161
+ params.append(f'<parameter name="{key}">{value}</parameter>')
162
+
163
+ # Build invoke block
164
+ invoke_block = f'<invoke name="{name}">\n' + '\n'.join(params) + '\n</invoke>'
165
+ tool_calls.append(invoke_block)
166
+
167
+ # Wrap all invocations in tool_call tags
168
+ if tool_calls:
169
+ return '<minimax:tool_call>\n' + '\n'.join(tool_calls) + '\n</minimax:tool_call>'
170
+
171
+ return ''