ActionParser 数据预处理与中文训练样本起步

张开发
2026/4/18 12:08:18 15 分钟阅读

分享文章

ActionParser 数据预处理与中文训练样本起步
在完成 AutoDL 环境部署、项目目录整理、基座模型迁移以及原始数据集入库之后StoryVerse 项目的开发重点开始从“基础设施搭建”逐步转向“任务数据准备与训练链路打通”。如果说前一阶段解决的是“项目能不能在服务器上稳定运行”的问题那么这一阶段要解决的就是“现有数据能不能真正服务于我们的中文小说交互场景”这一更核心的问题。本阶段的工作主要围绕ActionParser模块展开。对于 StoryVerse 这样一个基于大语言模型的多智能体小说情节角色扮演平台来说ActionParser 并不是一个简单的文本分类器它承担的是将用户在小说场景中的自然语言输入转化为结构化动作表示的关键任务。它直接决定系统能否理解用户意图、识别交互对象、抽取关键动作信息并进一步驱动角色响应和剧情推进。因此在真正开始模型微调之前最关键的一步不是盲目训练而是先判断现有公开数据是否真的适合我们的目标任务。围绕这一目标我首先开始处理已经准备好的NousResearch/hermes-function-calling-v1数据集https://huggingface.co/datasets/NousResearch/hermes-function-calling-v111578条样本和Schema-Guided Dialogue数据集https://github.com/google-research-datasets/dstc8-schema-guided-dialogue超过20000条样本两套数据集。前者更偏向指令跟随与对话式任务表达后者则属于较为典型的任务型对话数据集具有明确的意图、槽位和结构化语义信息。从表面上看这两类数据都与“自然语言转结构化表示”存在一定关联因此可以作为 ActionParser 微调的起点资源。然而当真正进入数据预处理和样本检查阶段后我逐渐发现这些数据虽然有参考价值但并不能直接无缝适配 StoryVerse 所面向的中文小说场景。在具体实现上我首先定义了ActionParser schema配置文件定义解析动作类型集合、输出 JSON 格式说明、每类动作可选字段为后续预处理 Hermes 和 SGD 数据定义第一版标准配置文件代码如下{ schema_version: v1, task_name: ActionParser, description: Parse a natural language action description into a structured JSON action object for StoryVerse., output_format: { type: object, required: [ action_type, parameters ], properties: { action_type: { type: string, description: The normalized action category. }, parameters: { type: object, description: Key-value arguments extracted from the input text. } } }, allowed_action_types: [ speak, move, observe, use, take, give, ask, reply, wait, attack ], action_definitions: { speak: { description: Character speaks or expresses something., common_parameters: [ target, content, tone, emotion, location ] }, move: { description: Character moves from one place to another., common_parameters: [ source, destination, path, speed ] }, observe: { description: Character observes a person, object, scene, or event., common_parameters: [ target, location, focus, detail_level ] }, use: { description: Character uses an item, tool, or ability., common_parameters: [ item, target, method, purpose ] }, take: { description: Character takes or picks up an object., common_parameters: [ item, source, location ] }, give: { description: Character gives something to another character., common_parameters: [ item, target, quantity, location ] }, ask: { description: Character asks a question or requests information., common_parameters: [ target, question, topic, location ] }, reply: { description: Character replies or responds to someone., common_parameters: [ target, content, topic, tone ] }, wait: { description: Character waits, pauses, or does nothing temporarily., common_parameters: [ duration, location, reason ] }, attack: { description: Character attacks or attempts to harm a target., common_parameters: [ target, weapon, method, intensity, location ] } }, normalization_rules: { empty_parameters_allowed: true, unknown_action_policy: map_to_closest_allowed_action_if_possible, extra_parameters_policy: keep_if_semantically_valid, missing_required_fields_policy: allow_if_action_type_is_correct }, training_prompt_rules: { must_output_json_only: true, must_not_output_markdown: true, must_not_output_explanations: true, action_type_must_be_from_allowed_list: true } }接着我对 Hermes 和 SGD 的预处理脚本进行了设计和迭代重点是让原始样本能够更稳定地转换为适合后续训练的统一格式。这个过程并不是简单地“把数据读出来”那么直接而是涉及样本字段清洗、输入输出结构重组、目标格式对齐以及异常样本排查等一系列工作。#以preprocess_sgd.py为例 import argparse import json from pathlib import Path from typing import Any, Dict, List, Optional RAW_DIR Path(/root/storyverse/data/raw/sgd) DEFAULT_OUT_DIR Path(/root/storyverse/data/interim) ALLOWED_ACTION_TYPES {speak, move, observe, interact, use, wait} def load_json(path: Path) - Any: with path.open(r, encodingutf-8) as f: return json.load(f) def dump_jsonl(records: List[Dict[str, Any]], path: Path) - None: path.parent.mkdir(parentsTrue, exist_okTrue) with path.open(w, encodingutf-8) as f: for r in records: f.write(json.dumps(r, ensure_asciiFalse) \n) def simplify_service_name(service: Optional[str]) - str: Restaurants_1 - Restaurants Flights_3 - Flights if not service: return return service.split(_)[0] def pick_best_value(action: Dict[str, Any]) - Optional[Any]: Prefer canonical_values, then values. If single item list - scalar; else keep list. canonical action.get(canonical_values) values action.get(values) candidate canonical if canonical else values if not candidate: return None if isinstance(candidate, list): if len(candidate) 1: return candidate[0] return candidate return candidate def extract_current_turn_delta(frame: Dict[str, Any]) - Dict[str, Any]: 只抽取当前轮“新增”的动作信息不使用累计 state.slot_values。 返回 { intent: ..., inform_slots: {...}, requested_slots: [...], dialog_acts: [...] } result { intent: None, inform_slots: {}, requested_slots: [], dialog_acts: [] } actions frame.get(actions, []) for action in actions: act action.get(act) slot action.get(slot) value pick_best_value(action) if act: result[dialog_acts].append(act) # 当前轮 intent if act in {INFORM_INTENT, OFFER_INTENT, AFFIRM_INTENT, NEGATE_INTENT}: if value is not None: result[intent] value continue # 当前轮显式告知的槽位 if act in {INFORM, CONFIRM, OFFER, SELECT}: if slot and slot ! intent and value is not None: result[inform_slots][slot] value continue # 当前轮请求的槽位 if act REQUEST: if slot: result[requested_slots].append(slot) continue # 去重保持顺序 seen set() dedup_requested [] for s in result[requested_slots]: if s not in seen: dedup_requested.append(s) seen.add(s) result[requested_slots] dedup_requested return result def build_content_from_delta(utterance: str, delta: Dict[str, Any]) - str: 将当前轮增量信息压缩成一个短字符串贴合最终 content 字段。 不使用累计状态只反映当前轮行为。 parts [] if delta.get(intent): parts.append(fintent{delta[intent]}) inform_slots delta.get(inform_slots, {}) if inform_slots: parts.append(slots json.dumps(inform_slots, ensure_asciiFalse, sort_keysTrue)) requested_slots delta.get(requested_slots, []) if requested_slots: parts.append(request json.dumps(requested_slots, ensure_asciiFalse)) # 如果完全没有抽到结构化信息就退回原 utterance if not parts: return utterance.strip() return ; .join(parts) def map_to_final_action_type(turn: Dict[str, Any], delta: Dict[str, Any]) - str: 在你们当前正式 schema 下SGD 最稳妥的映射就是 interact。 因为 SGD 的用户行为本质上是任务交互、请求、确认、补充条件。 action_type interact if action_type not in ALLOWED_ACTION_TYPES: raise ValueError(fInvalid action_type: {action_type}) return action_type def build_record_from_user_turn( dialogue: Dict[str, Any], turn: Dict[str, Any], frame: Dict[str, Any], split: str, file_name: str ) - Optional[Dict[str, Any]]: utterance turn.get(utterance, ).strip() if not utterance: return None delta extract_current_turn_delta(frame) # 如果当前轮没有结构化信息也可保留为弱样本这里选择保留 action_type map_to_final_action_type(turn, delta) service frame.get(service, ) target simplify_service_name(service) content build_content_from_delta(utterance, delta) return { instruction: utterance, output: { action_type: action_type, target: target, content: content }, source: sgd, task_family: interaction_to_action_json, source_meta: { split: split, dataset_file: file_name, dialogue_id: dialogue.get(dialogue_id), service: service, raw_intent: delta.get(intent), raw_inform_slots: delta.get(inform_slots, {}), raw_requested_slots: delta.get(requested_slots, []), raw_dialog_acts: delta.get(dialog_acts, []) } } def process_dialogue_file(path: Path, split: str, max_records: Optional[int] None) - List[Dict[str, Any]]: data load_json(path) records: List[Dict[str, Any]] [] for dialogue in data: turns dialogue.get(turns, []) for turn in turns: if max_records is not None and len(records) max_records: return records if turn.get(speaker) ! USER: continue frames turn.get(frames, []) if not frames: continue # 第一版只取第一帧减少复杂度 frame frames[0] rec build_record_from_user_turn(dialogue, turn, frame, split, path.name) if rec is not None: records.append(rec) return records def process_split(split: str, max_files: Optional[int], max_records: Optional[int]) - List[Dict[str, Any]]: split_dir RAW_DIR / split files sorted(split_dir.glob(dialogues_*.json)) records: List[Dict[str, Any]] [] if max_files is not None: files files[:max_files] for fp in files: if max_records is not None and len(records) max_records: break remaining None if max_records is None else max_records - len(records) recs process_dialogue_file(fp, split, max_recordsremaining) print(f[INFO] {split}/{fp.name}: {len(recs)} records) records.extend(recs) return records def main(): parser argparse.ArgumentParser() parser.add_argument( --splits, nargs, default[train], choices[train, dev, test], ) parser.add_argument( --output, typestr, defaultstr(DEFAULT_OUT_DIR / sgd_normalized.jsonl), ) parser.add_argument( --max_files_per_split, typeint, defaultNone, helpOptional debug limit for number of dialogue files per split, ) parser.add_argument( --max_records_total, typeint, defaultNone, helpOptional debug limit for total output records, ) args parser.parse_args() all_records: List[Dict[str, Any]] [] for split in args.splits: remaining None if args.max_records_total is not None: remaining args.max_records_total - len(all_records) if remaining 0: break recs process_split(split, args.max_files_per_split, remaining) all_records.extend(recs) out_path Path(args.output) dump_jsonl(all_records, out_path) print(f[DONE] Wrote {len(all_records)} records to {out_path}) if __name__ __main__: main()经过预处理流程后我对输出结果进行了检查确认新的脚本已经基本实现了预期目的它能够更规范地把原始 Hermes 数据转成可进一步筛选、分析和利用的训练候选样本。这意味着项目已经不再停留在“有数据可用”的阶段而是开始进入“能否把数据处理成适合自己任务的形式”的阶段。{instruction: I am feeling hungry so I would like to find a place to eat., output: {action_type: interact, target: Restaurants, content: intentFindRestaurants}, source: sgd, task_family: interaction_to_action_json, source_meta: {split: train, dataset_file: dialogues_001.json, dialogue_id: 1_00000, service: Restaurants_1, raw_intent: FindRestaurants, raw_inform_slots: {}, raw_requested_slots: [], raw_dialog_acts: [INFORM_INTENT]}} {instruction: I would like for it to be in San Jose., output: {action_type: interact, target: Restaurants, content: slots{\city\: \San Jose\}}, source: sgd, task_family: interaction_to_action_json, source_meta: {split: train, dataset_file: dialogues_001.json, dialogue_id: 1_00000, service: Restaurants_1, raw_intent: null, raw_inform_slots: {city: San Jose}, raw_requested_slots: [], raw_dialog_acts: [INFORM]}} {instruction: I usually like eating the American type of food., output: {action_type: interact, target: Restaurants, content: slots{\cuisine\: \American\}}, source: sgd, task_family: interaction_to_action_json, source_meta: {split: train, dataset_file: dialogues_001.json, dialogue_id: 1_00000, service: Restaurants_1, raw_intent: null, raw_inform_slots: {cuisine: American}, raw_requested_slots: [], raw_dialog_acts: [INFORM]}} {instruction: Can you give me the address of this restaurant., output: {action_type: interact, target: Restaurants, content: request[\street_address\]}, source: sgd, task_family: interaction_to_action_json, source_meta: {split: train, dataset_file: dialogues_001.json, dialogue_id: 1_00000, service: Restaurants_1, raw_intent: null, raw_inform_slots: {}, raw_requested_slots: [street_address], raw_dialog_acts: [REQUEST]}} {instruction: Can you give me the phone number that I can contact them with?, output: {action_type: interact, target: Restaurants, content: request[\phone_number\]}, source: sgd, task_family: interaction_to_action_json, source_meta: {split: train, dataset_file: dialogues_001.json, dialogue_id: 1_00000, service: Restaurants_1, raw_intent: null, raw_inform_slots: {}, raw_requested_slots: [phone_number], raw_dialog_acts: [REQUEST]}}但与此同时随着对处理结果的分析加深我也更加明确了 Hermes 和 SGD 这类通用数据集的适配边界对于Hermes作为结构化输出预热数据用于训练模型学会固定 JSON 输出、训练模型学会把复杂输入压缩成动作化表达、训练模型学会稳定遵守 schema而SGD作为交互型行为解析辅助数据用于训练模型学会把请求、询问、补充条件这类输入压成统一动作 JSON重点帮助 interact 类行为的学习。它们的确为任务启动提供了基础资源但它们并不能完美覆盖我们未来想要实现的中文小说 ActionParser 能力。原因主要体现在几个方面。首先Hermes 和 SGD 的任务语境与我们的目标场景存在本质差异。SGD 的核心语料主要围绕订票、酒店、支付、导航等任务型交互强调的是现实世界中的功能性意图识别和槽位填充Hermes 虽然更加开放但它的指令表达多数仍然是通用助手场景下的问答、生成、执行命令等任务。相比之下StoryVerse 中的用户输入往往发生在小说世界内部带有强烈的叙事性、角色性和情境依赖性。例如同样一句话在现实任务型系统中可能对应明确意图但在小说语境中它既可能是动作也可能是试探、对话推进、情绪表达甚至是隐藏动机的铺垫。这种语义开放性和叙事情境依赖是通用公开数据集中很难充分体现的。其次现有公开数据大多以英文或偏英文任务体系为主和我们后续希望重点支持的中文交互表达之间存在明显差异。中文小说式输入在语言组织方式、动作表达粒度、礼貌和语气变化、指代省略以及语义隐含等方面都与英文任务型数据存在很大不同。也就是说即便在结构层面可以借鉴 Hermes 或 SGD 的样本组织方式它们也依然无法替代真正面向中文小说世界构建的高质量训练样本。再次从任务目标本身看我们未来的 ActionParser 不只是要判断“用户想做什么”还要尽可能从自然语言中提取出动作对象、目标角色、交互方式、语气色彩乃至潜在剧情影响。这类需求比传统任务型意图识别更复杂也比一般指令微调数据要求更高。因此公开数据集最多只能作为冷启动资源和格式参考真正决定模型上限的仍然是贴合 StoryVerse 场景的自建中文训练样本。基于上述认识这一阶段我没有把重点停留在“继续堆更多公开数据”上而是进一步推进了中文主训练样本的设计工作。为了让后续数据收集和人工标注有统一标准我先整理出了一套适用于中文 ActionParser 任务的标注模板。这个模板的核心作用是把原本较为模糊的“小说交互理解任务”转化为可执行的数据构造规范让每条样本都能形成相对统一的输入—输出对应关系。换句话说这一步是在给未来的训练数据建立“语法规则”和“标注标准”避免后续样本越积越多时出现格式不统一、动作定义混乱、字段含义不一致的问题。{ instruction: 我走到门边敲门说有人吗, output: { action_type: interact, target: 门, content: 敲门并询问有人吗 }, source: custom_zh_actionparser, task_family: zh_novel_action_parsing, source_meta: { book: 示例小说名, chapter: 第3章, character: 某角色 } }在此基础上我进一步整理并生成了一批高质量的中文种子样本初稿总计 30 条。这批样本并不是为了追求数量而是为了优先保证结构质量和场景代表性。它们更像是一批“样本原型”一方面可以用来验证当前定义的输出格式是否合理另一方面也可以作为后续扩写、仿写和批量生成训练数据的基础模板。更重要的是这批种子样本已经成功通过了解析这说明目前设计的格式在工程上是可执行的不只是停留在纸面方案阶段。对于一个正在从任务设计走向模型微调的项目来说这一步非常关键因为它意味着我们的 ActionParser 数据链路已经从“理论可行”迈入了“样本级可运行”的状态。{instruction:林冲压低声音对鲁智深说先别出声。,output:{action_type:speak,target:鲁智深,content:压低声音提醒先别出声},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:speak}} {instruction:她抬头朝屋里喊了一声有人吗。,output:{action_type:speak,target:屋里,content:朝屋里喊话询问有人吗},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:speak}} {instruction:我看着店小二问这里还有空房吗。,output:{action_type:speak,target:店小二,content:询问是否还有空房},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:speak}} {instruction:老人缓缓开口说天黑之前不要出城。,output:{action_type:speak,target:众人,content:提醒天黑前不要出城},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:speak}} {instruction:她轻声回答我刚才一直在这里等你。,output:{action_type:speak,target:对方,content:轻声说明一直在等待},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:speak}} {instruction:他快步走到窗前。,output:{action_type:move,target:窗前,content:快步移动到窗前},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:move}} {instruction:我沿着走廊慢慢退到门边。,output:{action_type:move,target:门边,content:沿走廊退到门边},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:move}} {instruction:她转身走向院子中央。,output:{action_type:move,target:院子中央,content:转身走向院子中央},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:move}} {instruction:少年悄悄靠近桌边。,output:{action_type:move,target:桌边,content:悄悄靠近桌边},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:move}} {instruction:他连忙往后退了两步。,output:{action_type:move,target:后方,content:向后退开两步},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:move}} {instruction:她侧耳听了听门外的动静。,output:{action_type:observe,target:门外,content:倾听门外动静},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:observe}} {instruction:我站在窗边往街上看了一眼。,output:{action_type:observe,target:街上,content:查看街上情况},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:observe}} {instruction:他低头仔细检查地上的脚印。,output:{action_type:observe,target:脚印,content:检查地上脚印},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:observe}} {instruction:她抬眼打量了一下对面的黑衣人。,output:{action_type:observe,target:黑衣人,content:打量对面的黑衣人},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:observe}} {instruction:我回头看了看身后的巷口。,output:{action_type:observe,target:巷口,content:回头查看巷口},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:observe}} {instruction:我走到门边敲门说有人吗。,output:{action_type:interact,target:门,content:敲门并询问有人吗},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:interact}} {instruction:她伸手拍了拍我的肩膀。,output:{action_type:interact,target:我,content:拍肩示意互动},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:interact}} {instruction:他抬手推了推半掩着的木门。,output:{action_type:interact,target:木门,content:推动半掩木门},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:interact}} {instruction:我朝掌柜招了招手示意他过来。,output:{action_type:interact,target:掌柜,content:招手示意掌柜过来},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:interact}} {instruction:她轻轻碰了碰桌上的茶杯。,output:{action_type:interact,target:茶杯,content:轻碰桌上茶杯},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:interact}} {instruction:他拿出钥匙打开了木门。,output:{action_type:use,target:钥匙,content:用钥匙打开木门},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:use}} {instruction:我举起油灯照向墙角。,output:{action_type:use,target:油灯,content:用油灯照向墙角},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:use}} {instruction:她拔出短刀割断了绳子。,output:{action_type:use,target:短刀,content:用短刀割断绳子},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:use}} {instruction:他端起茶碗喝了一口热茶。,output:{action_type:use,target:茶碗,content:端起茶碗喝茶},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:use}} {instruction:我按下墙上的机关按钮。,output:{action_type:use,target:机关按钮,content:按下墙上机关按钮},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:use}} {instruction:她站在原地沉默了一会儿。,output:{action_type:wait,target:原地,content:站在原地短暂等待},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:wait}} {instruction:我靠在墙边静静等他回来。,output:{action_type:wait,target:墙边,content:靠墙等待对方回来},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:wait}} {instruction:他抬手示意众人先不要动。,output:{action_type:wait,target:众人,content:示意众人暂时别动},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:wait}} {instruction:她坐在桌边安静地等着天亮。,output:{action_type:wait,target:桌边,content:坐在桌边等待天亮},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:wait}} {instruction:我屏住呼吸躲在门后没有出声。,output:{action_type:wait,target:门后,content:屏息躲在门后等待},source:custom_zh_actionparser,task_family:zh_novel_action_parsing,source_meta:{collector:manual,split:seed_v1,note:wait}}从项目推进角度来看这一阶段还有一个非常重要的收获我们对“为什么 Hermes 和 SGD 不能直接解决问题”这件事有了更清晰的认识。这个结论本身其实很有价值因为它帮助项目及时避免了一条高风险路线——如果过于依赖通用公开数据可能在前期看起来推进很快但到了真正面向中文小说交互落地时模型输出会出现大量语境错位、动作抽取不自然、角色交互不符合叙事逻辑的问题。现在通过预处理、检查和样本构建我们已经明确了公开数据的定位它们更适合作为早期结构参考、任务冷启动数据来源以及帮助我们理解“自然语言到结构化动作”这一问题的基础范式而不是最终训练体系的全部主体。因此本阶段的工作重点其实可以概括为三件事第一打通 Hermes 数据的预处理链路验证原始公开数据向训练样本格式迁移的可行性第二分析并明确 Hermes、SGD 与 StoryVerse 中文小说场景之间的适配差距第三开始建立真正服务于项目目标的中文 ActionParser 标注模板与种子样本体系。这三项工作共同推动项目从“资源准备阶段”进入“任务定制阶段”也让 ActionParser 模块第一次有了面向 StoryVerse 场景的专属数据基础。总体来看这一阶段虽然还没有正式进入大规模模型训练但完成的工作量并不小。因为相比于直接调用现成模型或仓促开始微调前期把数据问题想清楚、把格式打通、把样本体系立起来实际上更能决定后续模型效果和系统上限。到目前为止StoryVerse 项目的 ActionParser 部分已经完成了从公开数据调研到预处理适配、再到中文自建样本起步的关键过渡这也意味着项目开始真正摆脱“依赖通用资源”的状态逐步形成自己的任务定义和数据资产。接下来后续工作的重点将进一步转向中文主训练样本的扩充与迭代包括继续丰富样本类型、覆盖更多小说交互情境、优化动作表示格式并逐步为后续的微调实验和效果评估提供可持续的数据支持。可以说第二阶段的核心成果不是简单“又处理了多少数据”而是我们已经为 StoryVerse 的 ActionParser 奠定了面向中文小说场景的第一套可执行数据基础。

更多文章