@@ -57,6 +57,13 @@ def ask(
5757 files : Annotated [
5858 list [str ] | None , Option ("-f" , "--file" , help = "附加文件内容到 prompt(可多次指定)" )
5959 ] = None ,
60+ format : Annotated [
61+ str ,
62+ Option (
63+ "--format" ,
64+ help = "输出格式: text(默认) 或 json(结构化: {content, thinking, usage, model, elapsed_ms})" ,
65+ ),
66+ ] = "text" ,
6067 dry_run : Annotated [bool , Option ("--dry-run" , help = "预览操作内容,不实际执行" )] = False ,
6168 ):
6269 """LLM 快速问答(支持管道输入)
@@ -79,9 +86,21 @@ def ask(
7986 flexllm ask "用 Python 写个快排" -x
8087 flexllm ask "用 Python 写个快排" -x > sort.py
8188
89+ JSON 输出 (--format json): 给 agent/脚本解析
90+ flexllm ask "你好" --format json
91+ # {"content":"...","thinking":null,"usage":{...},"model":"...","elapsed_ms":123}
92+
8293 预览:
8394 flexllm ask "测试" --dry-run # 预览请求内容
8495 """
96+ if format not in ("text" , "json" ):
97+ cli_error (
98+ ErrorType .INVALID_ARGS ,
99+ "--format 参数值无效" ,
100+ context = {"arg" : "--format" , "received" : format , "expected" : ["text" , "json" ]},
101+ suggestion = "使用 --format text 或 --format json" ,
102+ doc = "flexllm ask --help" ,
103+ )
85104 stdin_content = None
86105 if not sys .stdin .isatty ():
87106 stdin_content = sys .stdin .read ().strip ()
@@ -147,8 +166,12 @@ async def _ask():
147166 async with LLMClient (model = model_id , base_url = base_url , api_key = api_key ) as client :
148167 return await client .chat_completions (messages , ** model_params )
149168
169+ import time
170+
150171 try :
172+ t0 = time .perf_counter ()
151173 result = asyncio .run (_ask ())
174+ elapsed_ms = int ((time .perf_counter () - t0 ) * 1000 )
152175 if result is None :
153176 cli_error (
154177 ErrorType .GENERAL ,
@@ -173,6 +196,23 @@ async def _ask():
173196 retryable = True ,
174197 )
175198 output = str (result ) if not isinstance (result , str ) else result
199+
200+ if format == "json" :
201+ thinking_text = None
202+ usage = None
203+ if hasattr (result , "data" ) and isinstance (result .data , dict ):
204+ thinking_text = result .data .get ("thinking" )
205+ usage = result .data .get ("usage" )
206+ payload = {
207+ "content" : output ,
208+ "thinking" : thinking_text ,
209+ "usage" : usage ,
210+ "model" : model_id ,
211+ "elapsed_ms" : elapsed_ms ,
212+ }
213+ print (json .dumps (payload , ensure_ascii = False ))
214+ return
215+
176216 if extract :
177217 code = extract_code_block (output )
178218 if code is not None :
@@ -228,6 +268,13 @@ def chat(
228268 files : Annotated [
229269 list [str ] | None , Option ("-f" , "--file" , help = "附加文件内容到 prompt(可多次指定)" )
230270 ] = None ,
271+ format : Annotated [
272+ str ,
273+ Option (
274+ "--format" ,
275+ help = "输出格式: text(默认) 或 json(仅单条模式, 多轮模式会报错)" ,
276+ ),
277+ ] = "text" ,
231278 dry_run : Annotated [bool , Option ("--dry-run" , help = "预览操作内容,不实际执行" )] = False ,
232279 ):
233280 """交互式对话
@@ -245,9 +292,28 @@ def chat(
245292 代码提取 (-x): 只输出回复中的代码块(仅单条模式)
246293 flexllm chat "写个 hello world" -x
247294
295+ JSON 输出 (--format json): 仅单条模式支持
296+ flexllm chat "你好" --format json
297+
248298 预览:
249299 flexllm chat "测试" --dry-run # 预览请求配置
250300 """
301+ if format not in ("text" , "json" ):
302+ cli_error (
303+ ErrorType .INVALID_ARGS ,
304+ "--format 参数值无效" ,
305+ context = {"arg" : "--format" , "received" : format , "expected" : ["text" , "json" ]},
306+ suggestion = "使用 --format text 或 --format json" ,
307+ doc = "flexllm chat --help" ,
308+ )
309+ if format == "json" and not message :
310+ cli_error (
311+ ErrorType .INVALID_ARGS ,
312+ "--format json 仅支持单条对话模式" ,
313+ context = {"mode" : "interactive" , "message" : None },
314+ suggestion = '提供 message 切到单条模式: flexllm chat "你好" --format json' ,
315+ doc = "flexllm chat --help" ,
316+ )
251317 model , base_url , api_key = resolve_model_config (model , base_url , api_key )
252318 config = get_config ()
253319
@@ -319,6 +385,7 @@ def chat(
319385 user_template ,
320386 thinking = resolved_thinking ,
321387 extract = extract ,
388+ output_format = format ,
322389 )
323390 elif not sys .stdin .isatty ():
324391 cli_error (
@@ -700,6 +767,13 @@ def batch(
700767 help = "结构化输出 (json=JSON模式, @file.json=从文件读取, 或 JSON Schema 字符串)" ,
701768 ),
702769 ] = None ,
770+ format : Annotated [
771+ str ,
772+ Option (
773+ "--format" ,
774+ help = "输出格式: text(默认) 或 json(stdout 输出聚合汇总 JSON)" ,
775+ ),
776+ ] = "text" ,
703777 dry_run : Annotated [bool , Option ("--dry-run" , help = "预览操作内容,不实际执行" )] = False ,
704778 ):
705779 """批量处理 JSONL 文件(支持断点续传)
@@ -732,9 +806,20 @@ def batch(
732806 flexllm batch data.jsonl -o out.jsonl -uf text -sf sys_prompt
733807 flexllm batch input.jsonl -n 5 # 只处理前5条(试跑)
734808
809+ JSON 输出 (--format json): stdout 输出聚合汇总,方便 agent/脚本解析
810+ flexllm batch input.jsonl -o out.jsonl --format json
811+
735812 预览:
736813 flexllm batch input.jsonl --dry-run # 预览处理计划
737814 """
815+ if format not in ("text" , "json" ):
816+ cli_error (
817+ ErrorType .INVALID_ARGS ,
818+ "--format 参数值无效" ,
819+ context = {"arg" : "--format" , "received" : format , "expected" : ["text" , "json" ]},
820+ suggestion = "使用 --format text 或 --format json" ,
821+ doc = "flexllm batch --help" ,
822+ )
738823 has_stdin = not sys .stdin .isatty ()
739824 if not input and not has_stdin :
740825 cli_error (
@@ -1048,9 +1133,33 @@ async def _run_batch():
10481133
10491134 results , summary = asyncio .run (_run_batch ())
10501135
1051- if summary :
1052- print (f"\n 完成: { summary } " , file = sys .stderr )
1053- print (f"输出文件: { output } " , file = sys .stderr )
1136+ if format == "json" :
1137+ if isinstance (summary , dict ):
1138+ summary_payload = summary
1139+ elif summary is None :
1140+ summary_payload = None
1141+ else :
1142+ summary_payload = {"raw" : str (summary )}
1143+ success_count = sum (1 for r in results if r is not None )
1144+ print (
1145+ json .dumps (
1146+ {
1147+ "input_file" : input ,
1148+ "output_file" : output ,
1149+ "record_count" : len (records ),
1150+ "success_count" : success_count ,
1151+ "failure_count" : len (records ) - success_count ,
1152+ "format_type" : format_type ,
1153+ "model" : model_id ,
1154+ "summary" : summary_payload ,
1155+ },
1156+ ensure_ascii = False ,
1157+ )
1158+ )
1159+ else :
1160+ if summary :
1161+ print (f"\n 完成: { summary } " , file = sys .stderr )
1162+ print (f"输出文件: { output } " , file = sys .stderr )
10541163
10551164 except json .JSONDecodeError as e :
10561165 cli_error (
0 commit comments