@@ -97,6 +97,17 @@ private async Task<AiConfig> ResolveConfigAsync(long? providerId)
9797
9898 /// <summary>调用大模型对话(OpenAI 兼容接口)。providerId 指定供应商,0=第一个启用供应商;modelOverride 可在供应商可用模型内切换。</summary>
9999 public async Task < string > ChatAsync ( string systemPrompt , string userPrompt , long providerId = 0 , AiConfig ? config = null , string ? modelOverride = null )
100+ {
101+ var messages = new List < ( string Role , string Content ) >
102+ {
103+ ( "system" , systemPrompt ) ,
104+ ( "user" , userPrompt )
105+ } ;
106+ return await ChatMessagesAsync ( messages , providerId , config , modelOverride ) ;
107+ }
108+
109+ /// <summary>多轮对话:完整消息列表(system + 历史消息)一次性发送。</summary>
110+ public async Task < string > ChatMessagesAsync ( List < ( string Role , string Content ) > messages , long providerId = 0 , AiConfig ? config = null , string ? modelOverride = null )
100111 {
101112 var cfg = config ?? await ResolveConfigAsync ( providerId > 0 ? providerId : null ) ;
102113 if ( ! string . IsNullOrEmpty ( modelOverride ) ) cfg . Model = modelOverride ;
@@ -114,11 +125,7 @@ public async Task<string> ChatAsync(string systemPrompt, string userPrompt, long
114125 reqMsg . Content = new StringContent ( JsonSerializer . Serialize ( new
115126 {
116127 model = cfg . Model ,
117- messages = new object [ ]
118- {
119- new { role = "system" , content = systemPrompt } ,
120- new { role = "user" , content = userPrompt }
121- } ,
128+ messages = messages . Select ( m => new { role = m . Role , content = m . Content } ) . ToArray ( ) ,
122129 temperature = 0.2
123130 } ) , Encoding . UTF8 , "application/json" ) ;
124131
@@ -404,6 +411,114 @@ public async Task<object> RunAssistantAsync(AiAssistantEntity assistant, Diction
404411 catch { return new List < Dictionary < string , object ? > > ( ) ; }
405412 }
406413
414+ // ==================== 多轮对话会话 ====================
415+
416+ /// <summary>开启新对话:快照助手配置,输入参数并入系统上下文。</summary>
417+ public async Task < AiConversationEntity > StartConversationAsync ( AiAssistantEntity assistant ,
418+ Dictionary < string , object ? > ? inputs , long providerId , string ? model )
419+ {
420+ var conv = new AiConversationEntity
421+ {
422+ AssistantId = assistant . Id ,
423+ AssistantName = assistant . AssistantName ,
424+ SystemPrompt = assistant . SystemPrompt ,
425+ InputParams = assistant . InputParams ,
426+ OutputParams = assistant . OutputParams ,
427+ ProviderId = providerId ,
428+ Model = model ,
429+ Messages = "[]" ,
430+ Status = 0 ,
431+ CreatedAt = DateTime . Now . ToString ( "o" )
432+ } ;
433+ // 输入参数并入系统上下文(每轮对话可见)
434+ var inputParams = ParseParamList ( assistant . InputParams ) ;
435+ if ( inputParams . Count > 0 )
436+ {
437+ var sb = new StringBuilder ( ) ;
438+ sb . AppendLine ( "## 输入参数" ) ;
439+ foreach ( var p in inputParams )
440+ {
441+ var name = p . GetValueOrDefault ( "name" ) ? . ToString ( ) ?? "" ;
442+ var label = p . GetValueOrDefault ( "label" ) ? . ToString ( ) ?? name ;
443+ sb . AppendLine ( $ "- { label } :{ inputs ? . GetValueOrDefault ( name ) ?? "" } ") ;
444+ }
445+ conv . SystemPrompt = ( conv . SystemPrompt ?? "" ) + "\n " + sb ;
446+ }
447+ _db . AiConversations . Add ( conv ) ;
448+ await _db . SaveChangesAsync ( ) ;
449+ return conv ;
450+ }
451+
452+ /// <summary>多轮对话:用户消息入历史,携带完整历史调用模型,回复回写历史。</summary>
453+ public async Task < string > ConversationChatAsync ( AiConversationEntity conv , string userContent , long providerId = 0 , string ? model = null )
454+ {
455+ var history = ParseMessages ( conv . Messages ) ;
456+ history . Add ( ( "user" , userContent ) ) ;
457+ var messages = new List < ( string , string ) > { ( "system" , conv . SystemPrompt ?? "" ) } ;
458+ messages . AddRange ( history ) ;
459+ var reply = await ChatMessagesAsync ( messages , providerId , null , model ) ;
460+ history . Add ( ( "assistant" , reply ) ) ;
461+ conv . Messages = JsonSerializer . Serialize ( history . Select ( m => new { role = m . Role , content = m . Content } ) ) ;
462+ if ( string . IsNullOrEmpty ( conv . Title ) )
463+ conv . Title = userContent . Length > 30 ? userContent [ ..30 ] : userContent ;
464+ conv . UpdatedAt = DateTime . Now . ToString ( "o" ) ;
465+ await _db . SaveChangesAsync ( ) ;
466+ return reply ;
467+ }
468+
469+ /// <summary>结束对话:有输出参数时要求模型基于对话生成最终 JSON 结果并解析,会话标记已结束。</summary>
470+ public async Task < object > EndConversationAsync ( AiConversationEntity conv , long providerId = 0 , string ? model = null )
471+ {
472+ var outputParams = ParseParamList ( conv . OutputParams ) ;
473+ var finalReply = "" ;
474+ var outputs = new Dictionary < string , object ? > ( ) ;
475+ if ( outputParams . Count > 0 )
476+ {
477+ var names = string . Join ( ", " , outputParams . Select ( p => $ "\" { p . GetValueOrDefault ( "name" ) } \" ") ) ;
478+ var descs = string . Join ( "、" , outputParams . Select ( p =>
479+ $ "\" { p . GetValueOrDefault ( "name" ) } \" 表示{ p . GetValueOrDefault ( "label" ) ?? p . GetValueOrDefault ( "name" ) } ") ) ;
480+ var messages = new List < ( string , string ) > { ( "system" , conv . SystemPrompt ?? "" ) } ;
481+ messages . AddRange ( ParseMessages ( conv . Messages ) ) ;
482+ messages . Add ( ( "user" , $ "请根据以上对话生成最终结果,只输出 JSON(不要解释、不要 markdown 围栏),字段:{ names } ,其中 { descs } 。") ) ;
483+ finalReply = await ChatMessagesAsync ( messages , providerId , null , model ) ;
484+ try
485+ {
486+ var json = ExtractJson ( finalReply ) ;
487+ using var doc = JsonDocument . Parse ( json ) ;
488+ foreach ( var p in outputParams )
489+ {
490+ var name = p . GetValueOrDefault ( "name" ) ? . ToString ( ) ?? "" ;
491+ if ( name . Length > 0 && doc . RootElement . TryGetProperty ( name , out var v ) )
492+ outputs [ name ] = CloneToObject ( v ) ;
493+ }
494+ }
495+ catch { /* 模型未按 JSON 输出时保留原文 */ }
496+ }
497+ conv . Status = 1 ;
498+ conv . Outputs = JsonSerializer . Serialize ( outputs ) ;
499+ conv . UpdatedAt = DateTime . Now . ToString ( "o" ) ;
500+ await _db . SaveChangesAsync ( ) ;
501+ return new { reply = finalReply , outputs } ;
502+ }
503+
504+ private static List < ( string Role , string Content ) > ParseMessages ( string ? json )
505+ {
506+ if ( string . IsNullOrWhiteSpace ( json ) ) return new List < ( string , string ) > ( ) ;
507+ try
508+ {
509+ using var doc = JsonDocument . Parse ( json ) ;
510+ var list = new List < ( string , string ) > ( ) ;
511+ foreach ( var m in doc . RootElement . EnumerateArray ( ) )
512+ {
513+ var role = m . TryGetProperty ( "role" , out var r ) ? r . GetString ( ) ?? "" : "" ;
514+ var content = m . TryGetProperty ( "content" , out var c ) ? c . GetString ( ) ?? "" : "" ;
515+ if ( role . Length > 0 ) list . Add ( ( role , content ) ) ;
516+ }
517+ return list ;
518+ }
519+ catch { return new List < ( string , string ) > ( ) ; }
520+ }
521+
407522 /// <summary>流程生成的系统提示词:描述与设计器一致的节点格式。</summary>
408523 private static string BuildSystemPrompt ( )
409524 {
0 commit comments