初创agent实习-dayily clip的设计
DailyClip:从多帧 VLM 结果到 Seedance 视频这份 README 按真实执行顺序讲解。每一节尽量贴出一个完整函数,然后解释:输入是什么;输出是什么;这一段在总流程中的位置;关键算法为什么这样设计。总流程:predictions.jsonl - 完整多帧组评分 - MMR Top-K - 本地拼图裁代表帧 - JPEG Base64 - 分镜 Prompt - Seedance 异步任务 - MP41. 完整的main():先建立全局视角defmain()-int:"""执行选择、构造请求,以及可选的远程提交与下载。"""args=parse_args()try:args.source=workspace_path(args.source)args.output=workspace_path(args.output)validate_args(args)# 一条输入记录始终作为一个完整多帧组评分,不拆成单帧。candidates,stats=load_candidates(args.source,args.frames_per_group)selected=select_top_k(candidates,args.top_k,args.diversity_weight)ifnotselected:raiseValueError(f"No eligible multi-frame groups found in{args.source}")# 排名用于审计;Prompt 可按原始索引恢复当天事件顺序。story_groups=(sorted(selected,key=lambdaitem:item.source_index)ifargs.story_order=="source"elseselected)references=prepare_references(story_groups,project_root=PROJECT_ROOT,source_file=args.source,max_images=args.max_reference_images,frame_grid=args.frame_grid,frame_number=args.representative_frame,)prompt=build_daily_clip_prompt(story_groups,references=references,duration=args.duration,ratio=args.ratio,style=args.style,generate_audio=args.generate_audio,)payload=build_generation_payload(prompt,model=args.model,image_urls=(reference.data_urlforreferenceinreferences),ratio=args.ratio,duration=args.duration,generate_audio=args.generate_audio,watermark=args.watermark,)selection_path=derived_path(args.output,"selection")request_path=derived_path(args.output,"request")task_path=derived_path(args.output,"task")write_json(selection_path,selection_document(args,candidates,selected,stats,references,prompt,),)write_json(request_path,payload)print_summary(selected,stats,references,request_path)# dry-run 完成本地全流程,但不会读取 key 或访问网络。ifargs.dry_run:print("Dry run complete. No API calls were made.")return0api_key=os.environ.get(args.api_key_env)ifnotapi_key:raiseRuntimeError(f"Set{args.api_key_env}before submitting")client=SeedanceClient(api_key=api_key,base_url=args.base_url)task_id=client.create_task(payload)# 先保存任务 ID;即使终端中断,也可以从 task.json 找回服务端任务。write_json(task_path,{"id":task_id,"status":"submitted"})print(f"Seedance task submitted:{task_id}")ifargs.submit_only:return0task=client.wait_for_task(task_id,poll_interval=args.poll_interval,timeout=args.timeout,on_status=lambdastatus:print(f"status={status}"),)write_json(task_path,task)client.download_video(client.video_url(task),args.output)print(f"Video saved:{args.output}")return0exceptExceptionasexc:print(f"Error:{exc}",file=sys.stderr)return1可以把它分成六个阶段:参数与路径 - 候选加载和 Top-K - 参考图与 Prompt - 保存本地审计文件 - 创建并轮询远程任务 - 下载视频main()自己不实现评分、图片裁剪或 HTTP 细节,只负责把各模块串起来。dry-run的位置为什么重要dry-run位于 request 文件写完之后、API key 读取之前。因此它会执行:读取数据、评分、MMR、裁图、Base64、Prompt、request、selection但不会:读取 key、访问网络、创建任务、生成视频这就是为什么 dry-run 很快,但仍然可以检查完整请求。2. 参数和路径关键参数:参数默认值含义--top-k5选择多少个完整多帧组--frames-per-group6每组必须有多少帧--diversity-weight0.5MMR 多样性权重--story-ordersource按时间线或 MMR 顺序编排--max-reference-images5参考图数量,最多 9--frame-grid3x2多帧拼图布局--representative-frame4每组使用哪一格--duration11视频时长,4~15 秒--generate-audio关闭是否生成音轨--dry-run关闭是否只运行本地流程路径函数:defworkspace_path(path:Path)-Path:"""相对路径统一从仓库根目录解释,避免 remote/ 下执行时报错。"""returnpathifpath.is_absolute()elsePROJECT_ROOT/path所以从remote/运行:python../cat_description/dailyclip.py\results/result15/multiframe_baseline_eval/predictions.jsonl输入仍会解析为:/home/alex/soulgard/soulgard-vl/results/...参数检查会在联网前拒绝:top_k 1 diversity_weight 不在 0~1 duration 不在 4~15 秒 参考图超过 9 张 代表帧超出拼图范围 轮询时间参数不是正数3. 完整的load_candidates():输入如何成为候选组defload_candidates(path:Path,frames:int=6,)-tuple[list[HighlightCandidate],dict[str,int]]:"""读取候选组,并统计无效、帧数不符和有效行。"""stats={"total":0,"invalid":0,"wrong_frames":0,"eligible":0,}candidates:list[HighlightCandidate]=[]forrow_number,rowinenumerate(_rows(path)):stats["total"]+=1prediction=_json_object(row.get("raw_prediction"))ifnotprediction:prediction=(row.get("prediction")ifisinstance(row.get("prediction"),dict)else{})ifnotprediction:stats["invalid"]+=1continuetry:frame_count=int(prediction.get("帧数"))except(TypeError,ValueError):frame_count=-1ifframe_count!=frames:stats["wrong_frames"]+=1continueoverview=str(prediction.get("概述内容")orprediction.get("概述")orprediction.get("summary")or"").strip()ifnotoverview:stats["invalid"]+=1continuetry:source_index=int(row.get("index",row_number))except(TypeError,ValueError):source_index=row_number candidate=HighlightCandidate(source_index=source_index,overview=overview,raw_prediction=prediction,image_path=(str(row.get(