diff --git a/DifySharp.Demo.AspNet/Program.cs b/DifySharp.Demo.AspNet/Program.cs index 1f2eac8..e7c610d 100644 --- a/DifySharp.Demo.AspNet/Program.cs +++ b/DifySharp.Demo.AspNet/Program.cs @@ -1,12 +1,8 @@ -using System.Text.Encodings.Web; -using System.Text.Json; using DifySharp; -using DifySharp.Apis; using DifySharp.Chat.ChatMessages; using DifySharp.Extensions; using DifySharp.KnowledgeBase; using DifySharp.KnowledgeBase.Document; -using Microsoft.AspNetCore.Mvc; var builder = WebApplication.CreateBuilder(args); @@ -91,7 +87,6 @@ var response = await client.PostChatMessageBlocking(new ChatMessage.RequestBody { Query = "ping", - ResponseMode = ChatMessage.ResponseMode.Blocking, User = "test-user" }); diff --git a/DifySharp.Demo.Console/Program.cs b/DifySharp.Demo.Console/Program.cs index 210df8d..8db0740 100644 --- a/DifySharp.Demo.Console/Program.cs +++ b/DifySharp.Demo.Console/Program.cs @@ -1,12 +1,8 @@ // See https://aka.ms/new-console-template for more information -using System.Text.Encodings.Web; -using System.Text.Json; using DifySharp; using DifySharp.Chat.ChatMessages; using DifySharp.Extensions; -using DifySharp.KnowledgeBase.Dataset; -using Microsoft.Extensions.Logging; // knowledge base api @@ -28,7 +24,6 @@ var response = await chatClient.PostChatMessageBlocking(new ChatMessage.RequestBody { Query = "ping", - ResponseMode = ChatMessage.ResponseMode.Blocking, User = "test-user" }); @@ -38,13 +33,12 @@ var chunks = chatClient.PostChatMessageStreaming(new ChatMessage.RequestBody { Query = "What dose the fox say?", - ResponseMode = ChatMessage.ResponseMode.Streaming, User = "test-user" }); await foreach (var chunk in chunks) { - if (chunk is ChatMessage.MessageEvent messageEvent) + if (chunk is MessageEvent messageEvent) Console.Write(messageEvent.Answer); } diff --git a/DifySharp.Test/Apis/KnowledgeBaseApiTest/ChunkApiTest.cs b/DifySharp.Test/Apis/KnowledgeBaseApiTest/ChunkApiTest.cs index f1484d9..2483a30 100644 --- a/DifySharp.Test/Apis/KnowledgeBaseApiTest/ChunkApiTest.cs +++ b/DifySharp.Test/Apis/KnowledgeBaseApiTest/ChunkApiTest.cs @@ -19,7 +19,7 @@ public class ChunkApiTestFixture : KnowledgeBaseApiTestFixture public KnowledgeBaseClient Client { get; set; } - public ChunkApiTestFixture() : base() + public ChunkApiTestFixture() { Client = ServiceProvider.GetRequiredKeyedService("knowledge"); diff --git a/DifySharp.Test/Apis/KnowledgeBaseApiTest/DatasetApiTest.cs b/DifySharp.Test/Apis/KnowledgeBaseApiTest/DatasetApiTest.cs index e0e0291..6691b9a 100644 --- a/DifySharp.Test/Apis/KnowledgeBaseApiTest/DatasetApiTest.cs +++ b/DifySharp.Test/Apis/KnowledgeBaseApiTest/DatasetApiTest.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; - namespace DifySharp.Test.Apis.KnowledgeBaseApiTest; public class DatasetApiTestFixture : KnowledgeBaseApiTestFixture diff --git a/DifySharp.Test/Apis/KnowledgeBaseApiTest/DocumentApiTest.cs b/DifySharp.Test/Apis/KnowledgeBaseApiTest/DocumentApiTest.cs index 521f77e..bcd046f 100644 --- a/DifySharp.Test/Apis/KnowledgeBaseApiTest/DocumentApiTest.cs +++ b/DifySharp.Test/Apis/KnowledgeBaseApiTest/DocumentApiTest.cs @@ -1,4 +1,3 @@ -using DifySharp.Apis; using DifySharp.KnowledgeBase; using DifySharp.KnowledgeBase.Dataset; using DifySharp.KnowledgeBase.Document; @@ -13,7 +12,7 @@ public class DocumentApiTestFixture : KnowledgeBaseApiTestFixture public Dataset Dataset { get; private set; } public KnowledgeBaseClient Client { get; } - public DocumentApiTestFixture() : base() + public DocumentApiTestFixture() { Client = ServiceProvider.GetRequiredKeyedService("knowledge"); diff --git a/DifySharp.Test/Apis/KnowledgeBaseApiTest/KnowledgeBaseApiTestFixture.cs b/DifySharp.Test/Apis/KnowledgeBaseApiTest/KnowledgeBaseApiTestFixture.cs index c534bbd..50cc6e1 100644 --- a/DifySharp.Test/Apis/KnowledgeBaseApiTest/KnowledgeBaseApiTestFixture.cs +++ b/DifySharp.Test/Apis/KnowledgeBaseApiTest/KnowledgeBaseApiTestFixture.cs @@ -1,6 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Xunit.DependencyInjection.Logging; namespace DifySharp.Test.Apis.KnowledgeBaseApiTest; diff --git a/DifySharp.Test/DifyClientTest.cs b/DifySharp.Test/DifyClientTest.cs index 134e9d3..7f42e23 100644 --- a/DifySharp.Test/DifyClientTest.cs +++ b/DifySharp.Test/DifyClientTest.cs @@ -1,4 +1,3 @@ -using DifySharp; using JetBrains.Annotations; namespace DifySharp.Test; diff --git a/DifySharp.Test/DifySharp.Test.csproj b/DifySharp.Test/DifySharp.Test.csproj index 42f697f..d583f26 100644 --- a/DifySharp.Test/DifySharp.Test.csproj +++ b/DifySharp.Test/DifySharp.Test.csproj @@ -35,4 +35,8 @@ + + + + diff --git a/DifySharp.Test/Startup.cs b/DifySharp.Test/Startup.cs index 731a251..a27253c 100644 --- a/DifySharp.Test/Startup.cs +++ b/DifySharp.Test/Startup.cs @@ -34,7 +34,7 @@ private static void ReadEnvironmentVariables() if (!File.Exists(settingFilePath)) return; using var file = File.Open(settingFilePath, FileMode.Open); - var document = JsonDocument.Parse(file, new JsonDocumentOptions() + var document = JsonDocument.Parse(file, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip }); diff --git a/DifySharp/Apis/ApplicationApi.cs b/DifySharp/Apis/ApplicationApi.cs deleted file mode 100644 index 32201e6..0000000 --- a/DifySharp/Apis/ApplicationApi.cs +++ /dev/null @@ -1,33 +0,0 @@ -using WebApiClientCore.Attributes; -using WebApiClientCore.Parameters; - -namespace DifySharp.Apis; - -// public interface IApplicationApi -// { -// [HttpPost("files/upload")] -// public Task PostFilesUpload( -// [FormDataContent] string user, -// FileInfo file -// ); -// -// [HttpPost("/messages/{messageId}/feedbacks")] -// public Task PostMessagesFeedbacks( -// string messageId, -// [JsonContent] object requestBody -// ); -// -// [HttpPost("/text-to-audio")] -// public Task PostTextToAudio( -// [JsonContent] object requestBody -// ); -// -// [HttpGet("/info")] -// public Task GetInfo(); -// -// [HttpGet("/parameters")] -// public Task GetParameters(); -// -// [HttpGet("/meta")] -// public Task GetMeta(); -// } \ No newline at end of file diff --git a/DifySharp/Apis/ChatApi.cs b/DifySharp/Apis/ChatApi.cs index 6b32173..701e939 100644 --- a/DifySharp/Apis/ChatApi.cs +++ b/DifySharp/Apis/ChatApi.cs @@ -197,7 +197,7 @@ Delete.RequestBody requestBody /// /// [HttpPost("/v1/conversations/{conversationId}/name")] - public Task PostRenameConversation( + public Task PostRenameConversation( string conversationId, [JsonContent] Rename.RequestBody requestBody ); diff --git a/DifySharp/Apis/CompletionApi.cs b/DifySharp/Apis/CompletionApi.cs index 5c9c7bc..cbbcfd1 100644 --- a/DifySharp/Apis/CompletionApi.cs +++ b/DifySharp/Apis/CompletionApi.cs @@ -1,32 +1,106 @@ -using WebApiClientCore.Attributes; +using DifySharp.Completion.Application; +using DifySharp.Completion.CompletionMessages; +using DifySharp.Completion.Messages; +using WebApiClientCore.Attributes; -namespace DifySharp.Apis; - -/// -/// Completion App API -/// The text generation application offers non-session support and is ideal for translation, article writing, summarization AI, and more. -/// -public interface ICompletionApi +namespace DifySharp.Apis { /// - /// # Create Completion Message - /// Send a request to the text generation application. + /// Completion App API + /// The text generation application offers non-session support and is ideal for translation, article writing, summarization AI, and more. /// - /// - /// - [HttpPost("/completion-messages")] - public Task PostCompletionMessages( - [JsonContent] object requestBody - ); + public interface ICompletionApi : IApplicationApi, ICompletionMessagesApi, IMessagesApi + { + /// + /// Upload a file (currently only images are supported) for use when sending messages, + /// enabling multimodal understanding of images and text. Supports png, jpg, jpeg, webp, + /// gif formats. Uploaded files are for use by the current end-user only. + /// + /// + /// + /// + [HttpPost("files/upload")] + public Task PostFilesUpload( + [FormDataContent] string user, + FileInfo file + ); - /// - /// # Stop Generate - /// Only supported in streaming mode. - /// - /// - /// - [HttpPost("/completion-messages/taskId/stop")] - public Task PostCompletionsMessagesStop( - string taskId - ); + /// + /// Text to audio + /// + /// + /// + [HttpPost("/v1/text-to-audio")] + public Task PostTextToAudio( + [JsonContent] object requestBody + ); + } +} + +namespace DifySharp.Completion.CompletionMessages +{ + public interface ICompletionMessagesApi + { + /// + /// # Create Completion Message + /// Send a request to the text generation application. + /// + /// + /// + [HttpPost("/completion-messages")] + public Task PostCompletionMessages( + [JsonContent] CompletionMessages.RequestBody requestBody + ); + + /// + /// # Stop Generate + /// Only supported in streaming mode. + /// + /// + /// + [HttpPost("/completion-messages/taskId/stop")] + public Task PostCompletionsMessagesStop(string taskId); + } +} + +namespace DifySharp.Completion.Messages +{ + public interface IMessagesApi + { + /// + /// # Message Feedback + /// End-users can provide feedback messages, facilitating application developers to optimize expected outputs. + /// + /// + /// + /// + [HttpPost("/v1/messages/{messageId}/feedbacks")] + public Task PostMessagesFeedbacks( + string messageId, + [JsonContent] PostFeedback.RequestBody requestBody + ); + } +} + + +namespace DifySharp.Completion.Application +{ + public interface IApplicationApi + { + /// + /// # Get application basic information + /// Used to get basic information about this application + /// + /// + [HttpGet("/v1/info")] + public Task GetInfo(); + + /// + /// # Get Application Parameters Information + /// Used at the start of entering the page to obtain information such as features, input parameter names, types, and default values. + /// + /// + [HttpGet("/v1/parameters")] + public Task GetParameters(); + } } \ No newline at end of file diff --git a/DifySharp/Apis/KnowledgeBaseApi.cs b/DifySharp/Apis/KnowledgeBaseApi.cs index 36f3c3f..e3a14bf 100644 --- a/DifySharp/Apis/KnowledgeBaseApi.cs +++ b/DifySharp/Apis/KnowledgeBaseApi.cs @@ -3,7 +3,6 @@ using DifySharp.KnowledgeBase.Dataset; using DifySharp.KnowledgeBase.Document; using WebApiClientCore.Attributes; -using WebApiClientCore; namespace DifySharp.Apis { diff --git a/DifySharp/Apis/WorkflowApi.cs b/DifySharp/Apis/WorkflowApi.cs index 96f80a8..8556cd6 100644 --- a/DifySharp/Apis/WorkflowApi.cs +++ b/DifySharp/Apis/WorkflowApi.cs @@ -1,29 +1,74 @@ -using WebApiClientCore.Attributes; +using DifySharp.Workflow.Application; +using DifySharp.Workflow.Run; +using WebApiClientCore.Attributes; -namespace DifySharp.Apis; - -public interface IWorkflowApi +namespace DifySharp.Apis { - [HttpPost("/workflows/run")] - public Task PostWorkflowsRun( - [JsonContent] object requestBody - ); + public interface IWorkflowApi : IApplicationApi + { + /// + /// 执行 workflow,没有已发布的 workflow,不可执行。 + /// + /// + /// + [HttpPost("/workflows/run")] + public Task PostWorkflowsRun( + [JsonContent] Run.RequestBody requestBody + ); + + [HttpGet("/workflows/run/{workflowId}")] + public Task GetWorkflowsRun( + string workflowId + ); + + [HttpPost("/workflows/tasks/{taskId}/stop")] + public Task PostWorkflowsTasksStop( + string taskId + ); - [HttpGet("/workflows/run/{workflowId}")] - public Task GetWorkflowsRun( - string workflowId - ); + [HttpGet("/workflows/logs")] + public Task GetWorkflowsLogs( + [PathQuery] string keyword, + [PathQuery] string status, + [PathQuery] int page, + [PathQuery] int limit + ); + + /// + /// Upload a file (currently only images are supported) for use when sending messages, + /// enabling multimodal understanding of images and text. Supports png, jpg, jpeg, webp, + /// gif formats. Uploaded files are for use by the current end-user only. + /// + /// + /// + /// + [HttpPost("files/upload")] + public Task PostFilesUpload( + [FormDataContent] string user, + FileInfo file + ); + } +} - [HttpPost("/workflows/tasks/{taskId}/stop")] - public Task PostWorkflowsTasksStop( - string taskId - ); - [HttpGet("/workflows/logs")] - public Task GetWorkflowsLogs( - [PathQuery] string keyword, - [PathQuery] string status, - [PathQuery] int page, - [PathQuery] int limit - ); +namespace DifySharp.Workflow.Application +{ + public interface IApplicationApi + { + /// + /// # Get application basic information + /// Used to get basic information about this application + /// + /// + [HttpGet("/v1/info")] + public Task GetInfo(); + + /// + /// # Get Application Parameters Information + /// Used at the start of entering the page to obtain information such as features, input parameter names, types, and default values. + /// + /// + [HttpGet("/v1/parameters")] + public Task GetParameters(); + } } \ No newline at end of file diff --git a/DifySharp/Attributes/DifyAuthAttribute.cs b/DifySharp/Attributes/DifyAuthAttribute.cs index b56190e..6225ca7 100644 --- a/DifySharp/Attributes/DifyAuthAttribute.cs +++ b/DifySharp/Attributes/DifyAuthAttribute.cs @@ -1,7 +1,6 @@ using System.Net.Http.Headers; using DifySharp.ApiKey; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using WebApiClientCore; using WebApiClientCore.Attributes; diff --git a/DifySharp/DTOs/ApplicationResponseMode.cs b/DifySharp/DTOs/ApplicationResponseMode.cs new file mode 100644 index 0000000..e0c1f8f --- /dev/null +++ b/DifySharp/DTOs/ApplicationResponseMode.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace DifySharp; + +/// +/// represents the mode of chat api response +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ApplicationResponseMode +{ + /// + /// Streaming mode (recommended), implements a typewriter-like output through SSE (Server-Sent Events). + /// + Streaming, + + /// + /// Blocking mode, returns result after execution is complete. + /// (Requests may be interrupted if the process is long) + /// Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds. + /// Note: blocking mode is not supported in Agent Assistant mode + /// + Blocking +} diff --git a/DifySharp/DTOs/Chat/ChatMessages/ChatMessage.cs b/DifySharp/DTOs/Chat/ChatMessages/ChatMessage.cs index 07ecb64..39b8f6c 100644 --- a/DifySharp/DTOs/Chat/ChatMessages/ChatMessage.cs +++ b/DifySharp/DTOs/Chat/ChatMessages/ChatMessage.cs @@ -1,12 +1,9 @@ using System.Text.Json; -using System.Text.Json.Serialization; namespace DifySharp.Chat.ChatMessages; public static class ChatMessage { - #region Request - public record RequestBody { /// @@ -31,7 +28,8 @@ public record RequestBody /// 注:Agent模式下不允许blocking。 /// /// - public required ResponseMode ResponseMode { get; init; } + public ApplicationResponseMode ApplicationResponseMode { get; internal set; } = + ApplicationResponseMode.Blocking; /// /// 用户标识,用于定义终端用户的身份,方便检索、统计。 \ @@ -57,27 +55,7 @@ public record RequestBody public bool AutoGenerateName { get; init; } = true; } - /// - /// represents the mode of chat api response - /// - [JsonConverter(typeof(JsonStringEnumConverter))] - public enum ResponseMode - { - /// - /// Streaming mode (recommended), implements a typewriter-like output through SSE (Server-Sent Events). - /// - Streaming, - /// - /// Blocking mode, returns result after execution is complete. - /// (Requests may be interrupted if the process is long) - /// Due to Cloudflare restrictions, the request will be interrupted without a return after 100 seconds. - /// Note: blocking mode is not supported in Agent Assistant mode - /// - Blocking - } - - [Serializable] public record File { /// @@ -87,11 +65,14 @@ public record File /// /// 传递方式 + /// + /// Possible values: + /// + /// remote_url + /// local_file + /// + /// /// - /// <可能的值> - /// remote_url
- /// local_file - /// public string? TransferMethods { get; init; } /// @@ -111,10 +92,6 @@ public record File public string? UploadFileId { get; init; } } - #endregion - - #region Response - Blocking Mode - /// /// 返回完整的 App 结果, Content-Typeapplication/json。 /// @@ -122,7 +99,7 @@ public record File /// `response_mode` 为 `blocking` 时,返回 /// [Serializable] - public record ChatCompletionResponseBody + public record ResponseBody { /// /// 消息唯一 ID @@ -168,665 +145,4 @@ public record MetadataImpl public JsonElement RetrieverResources { get; init; } } } - - #endregion - - #region Response - Streaming Mode - - /// - /// 返回 App 输出的流式块, - /// Content-Type 为 text/event-stream。 - /// 每个流式块均为 data: 开头,块之间以 \n\n 即两个换行符分隔,如下所示: - /// - /// data: { - /// "event": "message", - /// "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", - /// "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", - /// "created_at": 1705398420 - /// }\n\n - /// - /// 除了event字段,其他字段由event字段内容决定。 - /// 见每一个继承的类。 - /// - [JsonPolymorphic(TypeDiscriminatorPropertyName = "event")] - [JsonDerivedType(typeof(MessageEvent), typeDiscriminator: "message")] - [JsonDerivedType(typeof(AgentMessageEvent), typeDiscriminator: "agent_message")] - [JsonDerivedType(typeof(AgentThoughtEvent), typeDiscriminator: "agent_thought")] - [JsonDerivedType(typeof(MessageFileEvent), typeDiscriminator: "message_file")] - [JsonDerivedType(typeof(MessageEndEvent), typeDiscriminator: "message_end")] - [JsonDerivedType(typeof(MessageReplaceEvent), typeDiscriminator: "message_replace")] - [JsonDerivedType(typeof(WorkflowStartedEvent), typeDiscriminator: "workflow_started")] - [JsonDerivedType(typeof(NodeStartedEvent), typeDiscriminator: "node_started")] - [JsonDerivedType(typeof(NodeFinishedEvent), typeDiscriminator: "node_finished")] - [JsonDerivedType(typeof(WorkflowFinishedEvent), typeDiscriminator: "workflow_finished")] - [JsonDerivedType(typeof(ErrorEvent), typeDiscriminator: "error")] - [JsonDerivedType(typeof(PingEvent), typeDiscriminator: "ping")] - [Serializable] - public record ChunkChatCompletionResponseBody - { - public const string CHUNK_PREFIX = "data: "; - public const string CHUNK_SUFFIX = "\n\n"; - - /// - /// 事件类型
- /// possible values: - /// - /// message - /// agent_message - /// agent_thought - /// message_file - /// message_end - /// message_replace - /// workflow_started - /// node_started - /// node_finished - /// workflow_finished - /// error - /// ping - /// - ///
- public string? Event { get; set; } - } - - /* - event: message LLM 返回文本块事件,即:完整的文本以分块的方式输出。 - task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - message_id (string) 消息唯一 ID - conversation_id (string) 会话 ID - answer (string) LLM 返回文本块内容 - created_at (int) 创建时间戳,如:1705395332 - */ - /// - /// LLM 返回文本块事件,即:完整的文本以分块的方式输出。when Event is message - /// - [Serializable] - public record MessageEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// 消息唯一 ID - /// - public required string MessageId { get; init; } - - /// - /// 会话 ID - /// - public required string ConversationId { get; init; } - - /// - /// LLM 返回文本块内容 - /// - public required string Answer { get; init; } - - /// - /// 创建时间戳 - /// - public int CreateAt { get; init; } - } - - /// - /// Agent模式下返回文本块事件,即:在Agent模式下,文章的文本以分块的方式输出(仅Agent模式下使用) - /// - [Serializable] - public record AgentMessageEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// 消息唯一 ID - /// - public required string MessageId { get; init; } - - /// - /// 会话 ID - /// - public required string ConversationId { get; init; } - - /// - /// LLM 返回文本块内容 - /// - public required string Answer { get; init; } - - /// - /// 创建时间戳 - /// - public int CreateAt { get; init; } - } - - /* - event: agent_thought Agent模式下有关Agent思考步骤的相关内容,涉及到工具调用(仅Agent模式下使用) - id (string) agent_thought ID,每一轮Agent迭代都会有一个唯一的id - task_id (string) 任务ID,用于请求跟踪下方的停止响应接口 - message_id (string) 消息唯一ID - position (int) agent_thought在消息中的位置,如第一轮迭代position为1 - thought (string) agent的思考内容 - observation (string) 工具调用的返回结果 - tool (string) 使用的工具列表,以 ; 分割多个工具 - tool_input (string) 工具的输入,JSON格式的字符串(object)。如:{"dalle3": {"prompt": "a cute cat"}} - created_at (int) 创建时间戳,如:1705395332 - message_files (array[string]) 当前 agent_thought 关联的文件ID - file_id (string) 文件ID - conversation_id (string) 会话ID - */ - - /// - /// Agent模式下有关Agent思考步骤的相关内容,涉及到工具调用(仅Agent模式下使用) - /// - public record AgentThoughtEvent : ChunkChatCompletionResponseBody - { - /// - /// agent_thought ID,每一轮Agent迭代都会有一个唯一的id - /// - public required string Id { get; init; } - - /// - /// 任务ID,用于请求跟踪下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// 消息唯一ID - /// - public required string MessageId { get; init; } - - /// - /// agent_thought在消息中的位置,如第一轮迭代position为1 - /// - public int Position { get; init; } - - /// - /// agent的思考内容 - /// - public required string Thought { get; init; } - - /// - /// 工具调用的返回结果 - /// - public required string Observation { get; init; } - - /// - /// 使用的工具列表,以 ; 分割多个工具 - /// - public required string Tool { get; init; } - - /// - /// 工具的输入,JSON格式的字符串(object)。如: - /// {"dalle3": {"prompt": "a cute cat"}} - /// - public required string ToolInput { get; init; } - - /// - /// 创建时间戳 - /// - public int CreatedAt { get; init; } - - /// - /// 当前 agent_thought 关联的文件ID - /// - public required string[] MessageFiles { get; init; } - - /// - /// 文件ID - /// - public required string FileId { get; init; } - - /// - /// 会话ID - /// - public required string ConversationId { get; init; } - } - - - /* - event: message_file 文件事件,表示有新文件需要展示 - id (string) 文件唯一ID - type (string) 文件类型,目前仅为image - belongs_to (string) 文件归属,user或assistant,该接口返回仅为 assistant - url (string) 文件访问地址 - conversation_id (string) 会话ID - */ - /// - /// 文件事件,表示有新文件需要展示 - /// - [Serializable] - public record MessageFileEvent : ChunkChatCompletionResponseBody - { - /// - /// 文件唯一ID - /// - public required string Id { get; init; } - - /// - /// 文件类型,目前仅为image - /// - public required string Type { get; init; } - - /// - /// 文件归属,user或assistant,该接口返回仅为 assistant - /// - public required string BelongsTo { get; init; } // user or assistant - - /// - /// 文件访问地址 - /// - public required string Url { get; init; } - - /// - /// 会话ID - /// - public required string ConversationId { get; init; } - } - - /* - event: message_end 消息结束事件,收到此事件则代表流式返回结束。 - task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - message_id (string) 消息唯一 ID - conversation_id (string) 会话 ID - metadata (object) 元数据 - usage (Usage) 模型用量信息 - retriever_resources (array[RetrieverResource]) 引用和归属分段列表 - */ - /// - /// 消息结束事件,收到此事件则代表流式返回结束。 - /// - [Serializable] - public record MessageEndEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// 消息唯一 ID - /// - public required string MessageId { get; init; } - - /// - /// 会话 ID - /// - public required string ConversationId { get; init; } - - /// - /// - /// - public MetadataImpl? Metadata { get; init; } - - - [Serializable] - public record MetadataImpl - { - /// - /// 模型用量信息 - /// - public JsonElement Usage { get; init; } - - /// - /// 引用和归属分段列表 - /// - /// retriever_resources - public JsonElement RetrieverResources { get; init; } - } - } - - /// - /// 消息内容替换事件。 开启内容审查和审查输出内容时,若命中了审查条件,则会通过此事件替换消息内容为预设回复。 - /// - [Serializable] - public record MessageReplaceEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// 消息唯一 ID - /// - public required string MessageId { get; init; } - - /// - /// 会话 ID - /// - public required string ConversationId { get; init; } - - /// - /// 替换内容(直接替换 LLM 所有回复文本) - /// - public required string Answer { get; init; } - - /// - /// 创建时间戳 - /// - public int CreateAt { get; init; } - } - - /// - /// workflow 开始执行 - /// - [Serializable] - public record WorkflowStartedEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// workflow 执行 ID - /// - public required string WorkflowRunId { get; init; } - - /// - /// 详细内容 - /// - public DataImpl? Data { get; init; } - - /// - /// - /// - [Serializable] - public record DataImpl - { - /// - /// workflow 执行 ID - /// - public required string Id { get; init; } - - /// - /// 关联 Workflow ID - /// - public required string WorkflowId { get; init; } - - /// - /// 自增序号,App 内自增,从 1 开始 - /// - public int SequenceNumber { get; init; } - - /// - /// 开始时间 - /// - public long CreatedAt { get; init; } - } - } - - /// - /// node_started node 开始执行 - /// - [Serializable] - public record NodeStartedEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// workflow 执行 ID - /// - public required string WorkflowRunId { get; init; } - - /// - /// 详细内容 - /// - public DataImpl? Data { get; init; } - - [Serializable] - public record DataImpl - { - /// - /// workflow 执行 ID - /// - public required string Id { get; init; } - - /// - /// 节点 ID - /// - public required string NodeId { get; init; } - - /// - /// 节点类型 - /// - public required string NodeType { get; init; } - - /// - /// 节点名称 - /// - public required string Title { get; init; } - - /// - /// 执行序号,用于展示 Tracing Node 顺序 - /// - public int Index { get; init; } - - /// - /// 前置节点 ID,用于画布展示执行路径 - /// - public required string PredecessorNodeId { get; init; } - - /// - /// 节点中所有使用到的前置节点变量内容 - /// - public JsonElement Inputs { get; init; } - - /// - /// 开始时间 - /// - public long CreatedAt { get; init; } - } - } - - /// - /// node_finished node 执行结束,成功失败同一事件中不同状态 - /// - [Serializable] - public record NodeFinishedEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// workflow 执行 ID - /// - public required string WorkflowRunId { get; init; } - - /// - /// 详细内容 - /// - public DataImpl? Data { get; init; } - - [Serializable] - public record DataImpl - { - /// - /// node 执行 ID - /// - public required string Id { get; init; } - - /// - /// 节点 ID - /// - public required string NodeId { get; init; } - - /// - /// 执行序号,用于展示 Tracing Node 顺序 - /// - public int Index { get; init; } - - /// - /// optional 前置节点 ID,用于画布展示执行路径 - /// - public required string PredecessorNodeId { get; init; } - - /// - /// 节点中所有使用到的前置节点变量内容 - /// - public JsonElement Inputs { get; init; } - - /// - /// Optional 节点过程数据 - /// - public JsonElement ProcessData { get; init; } - - /// - /// Optional 输出内容 - /// - public JsonElement Outputs { get; init; } - - /// - /// 执行状态 running / succeeded / failed / stopped - /// - public required string Status { get; init; } - - /// - /// Optional 错误原因 - /// - public required string Error { get; init; } - - /// - /// Optional 耗时(s) - /// - public float ElapsedTime { get; init; } - - /// - /// 元数据 - /// - public JsonElement ExecutionMetadata { get; init; } - - /// - /// 开始时间 - /// - public long CreatedAt { get; init; } - } - } - - /// - /// workflow_finished workflow 执行结束,成功失败同一事件中不同状态 - /// - [Serializable] - public record WorkflowFinishedEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public required string TaskId { get; init; } - - /// - /// workflow 执行 ID - /// - public required string WorkflowRunId { get; init; } - - public DataImpl? Data { get; init; } - - [Serializable] - public record DataImpl - { - /// - /// workflow 执行 ID - /// - public required string Id { get; init; } - - /// - /// 关联 Workflow ID - /// - public required string WorkflowId { get; init; } - - /// - /// 执行状态 running / succeeded / failed / stopped - /// - public required string Status { get; init; } - - /// - /// Optional 输出内容 - /// - public JsonElement? Outputs { get; init; } - - /// - /// Optional 错误原因 - /// - public required string Error { get; init; } - - /// - /// Optional 耗时(s) - /// - public float ElapsedTime { get; init; } - - /// - /// Optional 总使用 tokens - /// - public int TotalTokens { get; init; } - - /// - /// 总步数(冗余),默认 0 - /// - public int TotalSteps { get; init; } - - /// - /// 开始时间 - /// - public long CreatedAt { get; init; } - - /// - /// 结束时间 - /// - public long FinishedAt { get; init; } - } - } - - /* - event: error 流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。 - task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口 - message_id (string) 消息唯一 ID - status (int) HTTP 状态码 - code (string) 错误码 - message (string) 错误消息 - */ - /// - /// error 流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。 - /// - [Serializable] - public record ErrorEvent : ChunkChatCompletionResponseBody - { - /// - /// 任务 ID,用于请求跟踪和下方的停止响应接口 - /// - public string TaskId { get; init; } = ""; - - /// - /// 消息唯一 ID - /// - public required string MessageId { get; init; } - - /// - /// HTTP 状态码 - /// - public int Status { get; init; } - - /// - /// 错误码 - /// - public required string Code { get; init; } - - /// - /// 错误消息 - /// - public required string Message { get; init; } - } - - /// - /// 每 10s 一次的 ping 事件,保持连接存活。 - /// - [Serializable] - public record PingEvent : ChunkChatCompletionResponseBody; - - #endregion } \ No newline at end of file diff --git a/DifySharp/DTOs/Completion/Application/Basic.cs b/DifySharp/DTOs/Completion/Application/Basic.cs new file mode 100644 index 0000000..5b6c64d --- /dev/null +++ b/DifySharp/DTOs/Completion/Application/Basic.cs @@ -0,0 +1,12 @@ +namespace DifySharp.Completion.Application; + +public class Basic +{ + /// + /// + /// + /// application name + /// description + /// tags + public record ResponseBody(string Name, String Description, ICollection Tags); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Completion/Application/Parameters.cs b/DifySharp/DTOs/Completion/Application/Parameters.cs new file mode 100644 index 0000000..27d797d --- /dev/null +++ b/DifySharp/DTOs/Completion/Application/Parameters.cs @@ -0,0 +1,62 @@ +namespace DifySharp.Completion.Application; + +public record Parameters +{ + public record ResponseBody( + string OpeningStatement, + SuggestedQuestionsAfterAnswer SuggestedQuestionsAfterAnswer, + SpeechToText SpeechToText, + RetrieverResource RetrieverResource, + AnnotationReply AnnotationReply, + List UserInputForm, + FileUpload FileUpload, + SystemParameters SystemParameters + ); + + public record SuggestedQuestionsAfterAnswer(bool Enabled); + + public record SpeechToText(bool Enabled); + + public record RetrieverResource(bool Enabled); + + public record AnnotationReply(bool Enabled); + + public record UserInputForm(TextInput TextInput, Paragraph Paragraph, Select Select); + + public record TextInput( + string Label, + string Variable, + bool Required, + string Default + ); + + public record Paragraph( + string Label, + string Variable, + bool Required, + string Default + ); + + public record Select( + string Label, + string Variable, + bool Required, + List Options + ); + + public record FileUpload(Image Image); + + public record Image( + bool Enabled, + int NumberLimits, + string Detail, + List TransferMethods + ); + + public record SystemParameters( + int FileSizeLimit, + int ImageFileSizeLimit, + int AudioFileSizeLimit, + int VideoFileSizeLimit + ); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Completion/CompletionMessages/CompletionMessages.cs b/DifySharp/DTOs/Completion/CompletionMessages/CompletionMessages.cs new file mode 100644 index 0000000..20fdfe0 --- /dev/null +++ b/DifySharp/DTOs/Completion/CompletionMessages/CompletionMessages.cs @@ -0,0 +1,113 @@ +using System.Text.Json; + +namespace DifySharp.Completion.CompletionMessages; + +public class CompletionMessages +{ + public record RequestBody + { + /// + /// 用户输入/提问内容。 + /// + public required string Query { get; init; } + + /// + /// 允许传入 App 定义的各变量值。默认值为 {}。 + /// inputs 参数包含了多组键值对(Key/Value pairs), + /// 每组的键对应一个特定变量,每组的值则是该变量的具体值。 + /// + public object Inputs { get; init; } = new(); + + /// + /// + /// `streaming` 流式模式(推荐)。基于 SSE(Server-Sent Events)实现类似打字机输出方式的流式返回。 + /// + /// + /// `blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 + /// 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 + /// 注:Agent模式下不允许blocking。 + /// + /// + public ApplicationResponseMode ResponseMode { get; internal set; } = + ApplicationResponseMode.Blocking; + + /// + /// 用户标识,用于定义终端用户的身份,方便检索、统计。 \ + /// 由开发者定义规则,需保证用户标识在应用内唯一。 + /// + public required string User { get; init; } + + /// + /// (选填)会话 ID, + /// 若需要基于之前的聊天记录继续对话,则必须传之前消息的 conversation_id。 + /// + public string ConversationId { get; init; } = ""; + + /// + /// 上传的文件。 + /// + public ICollection? Files { get; init; } + } + + public record File + { + /// + /// 支持类型:图片 image(目前仅支持图片格式) + /// + public string? Type { get; init; } + + /// + /// 传递方式 + /// + /// Possible values: + /// + /// remote_url + /// local_file + /// + /// + /// + public string? TransferMethods { get; init; } + + /// + /// 图片地址。 + /// + /// <仅当> + /// TransferMethods 为 remote_url 时 + /// + public string? Url { get; init; } + + /// + /// 上传文件 ID。 + /// + /// <仅当> + /// TransferMethods 为 local_file 时 + /// + public string? UploadFileId { get; init; } + } + + /// + /// + /// + /// 消息唯一 ID + /// App 模式,固定为 chat + /// 完整回复内容 + /// 元数据 + /// 消息创建时间戳,如:1705395332 + public record ResponseBody( + string MessageId, + string Mode, + string Answer, + Metadata Metadata, + int CreateAt + ); + + /// + /// + /// + /// 模型用量信息 + /// 引用和归属分段列表 + public record Metadata( + JsonElement? Usage, + JsonElement RetrieverResources + ); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Completion/CompletionMessages/Stop.cs b/DifySharp/DTOs/Completion/CompletionMessages/Stop.cs new file mode 100644 index 0000000..935d742 --- /dev/null +++ b/DifySharp/DTOs/Completion/CompletionMessages/Stop.cs @@ -0,0 +1,17 @@ +namespace DifySharp.Completion.CompletionMessages; + +public record Stop +{ + /// + /// Required User identifier, + /// used to define the identity of the end-user, + /// must be consistent with the user passed in the send message interface + /// + public record RequestBody(string User); + + + /// + /// Always returns "success" + /// + public record ResponseBody(string Result); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Completion/Messages/PostFeedback.cs b/DifySharp/DTOs/Completion/Messages/PostFeedback.cs new file mode 100644 index 0000000..e6fa0e7 --- /dev/null +++ b/DifySharp/DTOs/Completion/Messages/PostFeedback.cs @@ -0,0 +1,24 @@ +namespace DifySharp.Completion.Messages; + +public record PostFeedback +{ + /// + /// + /// + /// Upvote as like, downvote as dislike, revoke upvote as null + /// User identifier, defined by the developer's rules, must be unique within the application. + /// The specific content of message feedback. + public record RequestBody( + string? Rating, + string User, + string Content + ); + + /// + /// + /// + /// Always returns "success" + public record ResponseBody( + string Result + ); +} \ No newline at end of file diff --git a/DifySharp/DTOs/CompletionResponse.cs b/DifySharp/DTOs/CompletionResponse.cs new file mode 100644 index 0000000..37f3729 --- /dev/null +++ b/DifySharp/DTOs/CompletionResponse.cs @@ -0,0 +1,662 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DifySharp; + +#region Response - Streaming Mode + +/// +/// 返回 App 输出的流式块, +/// Content-Type 为 text/event-stream。 +/// 每个流式块均为 data: 开头,块之间以 \n\n 即两个换行符分隔,如下所示: +/// +/// data: { +/// "event": "message", +/// "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", +/// "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", +/// "created_at": 1705398420 +/// }\n\n +/// +/// 除了event字段,其他字段由event字段内容决定。 +/// 见每一个继承的类。 +/// +[JsonPolymorphic(TypeDiscriminatorPropertyName = "event")] +[JsonDerivedType(typeof(MessageEvent), typeDiscriminator: "message")] +[JsonDerivedType(typeof(AgentMessageEvent), typeDiscriminator: "agent_message")] +[JsonDerivedType(typeof(AgentThoughtEvent), typeDiscriminator: "agent_thought")] +[JsonDerivedType(typeof(MessageFileEvent), typeDiscriminator: "message_file")] +[JsonDerivedType(typeof(MessageEndEvent), typeDiscriminator: "message_end")] +[JsonDerivedType(typeof(MessageReplaceEvent), typeDiscriminator: "message_replace")] +[JsonDerivedType(typeof(WorkflowStartedEvent), typeDiscriminator: "workflow_started")] +[JsonDerivedType(typeof(NodeStartedEvent), typeDiscriminator: "node_started")] +[JsonDerivedType(typeof(NodeFinishedEvent), typeDiscriminator: "node_finished")] +[JsonDerivedType(typeof(WorkflowFinishedEvent), typeDiscriminator: "workflow_finished")] +[JsonDerivedType(typeof(ErrorEvent), typeDiscriminator: "error")] +[JsonDerivedType(typeof(PingEvent), typeDiscriminator: "ping")] +[Serializable] +public record ChunkCompletionResponseBody +{ + public const string CHUNK_PREFIX = "data: "; + public const string CHUNK_SUFFIX = "\n\n"; + + /// + /// 事件类型
+ /// possible values: + /// + /// message + /// agent_message + /// agent_thought + /// message_file + /// message_end + /// message_replace + /// workflow_started + /// node_started + /// node_finished + /// workflow_finished + /// error + /// ping + /// + ///
+ public string? Event { get; set; } +} + +/* + event: message LLM 返回文本块事件,即:完整的文本以分块的方式输出。 + task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口 + message_id (string) 消息唯一 ID + conversation_id (string) 会话 ID + answer (string) LLM 返回文本块内容 + created_at (int) 创建时间戳,如:1705395332 + */ +/// +/// LLM 返回文本块事件,即:完整的文本以分块的方式输出。when Event is message +/// +[Serializable] +public record MessageEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// 消息唯一 ID + /// + public required string MessageId { get; init; } + + /// + /// 会话 ID + /// + public required string ConversationId { get; init; } + + /// + /// LLM 返回文本块内容 + /// + public required string Answer { get; init; } + + /// + /// 创建时间戳 + /// + public int CreateAt { get; init; } +} + +/// +/// Agent模式下返回文本块事件,即:在Agent模式下,文章的文本以分块的方式输出(仅Agent模式下使用) +/// +[Serializable] +public record AgentMessageEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// 消息唯一 ID + /// + public required string MessageId { get; init; } + + /// + /// 会话 ID + /// + public required string ConversationId { get; init; } + + /// + /// LLM 返回文本块内容 + /// + public required string Answer { get; init; } + + /// + /// 创建时间戳 + /// + public int CreateAt { get; init; } +} + +/* + event: agent_thought Agent模式下有关Agent思考步骤的相关内容,涉及到工具调用(仅Agent模式下使用) + id (string) agent_thought ID,每一轮Agent迭代都会有一个唯一的id + task_id (string) 任务ID,用于请求跟踪下方的停止响应接口 + message_id (string) 消息唯一ID + position (int) agent_thought在消息中的位置,如第一轮迭代position为1 + thought (string) agent的思考内容 + observation (string) 工具调用的返回结果 + tool (string) 使用的工具列表,以 ; 分割多个工具 + tool_input (string) 工具的输入,JSON格式的字符串(object)。如:{"dalle3": {"prompt": "a cute cat"}} + created_at (int) 创建时间戳,如:1705395332 + message_files (array[string]) 当前 agent_thought 关联的文件ID + file_id (string) 文件ID + conversation_id (string) 会话ID + */ + +/// +/// Agent模式下有关Agent思考步骤的相关内容,涉及到工具调用(仅Agent模式下使用) +/// +public record AgentThoughtEvent : ChunkCompletionResponseBody +{ + /// + /// agent_thought ID,每一轮Agent迭代都会有一个唯一的id + /// + public required string Id { get; init; } + + /// + /// 任务ID,用于请求跟踪下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// 消息唯一ID + /// + public required string MessageId { get; init; } + + /// + /// agent_thought在消息中的位置,如第一轮迭代position为1 + /// + public int Position { get; init; } + + /// + /// agent的思考内容 + /// + public required string Thought { get; init; } + + /// + /// 工具调用的返回结果 + /// + public required string Observation { get; init; } + + /// + /// 使用的工具列表,以 ; 分割多个工具 + /// + public required string Tool { get; init; } + + /// + /// 工具的输入,JSON格式的字符串(object)。如: + /// {"dalle3": {"prompt": "a cute cat"}} + /// + public required string ToolInput { get; init; } + + /// + /// 创建时间戳 + /// + public int CreatedAt { get; init; } + + /// + /// 当前 agent_thought 关联的文件ID + /// + public required string[] MessageFiles { get; init; } + + /// + /// 文件ID + /// + public required string FileId { get; init; } + + /// + /// 会话ID + /// + public required string ConversationId { get; init; } +} + +/* + event: message_file 文件事件,表示有新文件需要展示 + id (string) 文件唯一ID + type (string) 文件类型,目前仅为image + belongs_to (string) 文件归属,user或assistant,该接口返回仅为 assistant + url (string) 文件访问地址 + conversation_id (string) 会话ID + */ +/// +/// 文件事件,表示有新文件需要展示 +/// +[Serializable] +public record MessageFileEvent : ChunkCompletionResponseBody +{ + /// + /// 文件唯一ID + /// + public required string Id { get; init; } + + /// + /// 文件类型,目前仅为image + /// + public required string Type { get; init; } + + /// + /// 文件归属,user或assistant,该接口返回仅为 assistant + /// + public required string BelongsTo { get; init; } // user or assistant + + /// + /// 文件访问地址 + /// + public required string Url { get; init; } + + /// + /// 会话ID + /// + public required string ConversationId { get; init; } +} + +/* + event: message_end 消息结束事件,收到此事件则代表流式返回结束。 + task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口 + message_id (string) 消息唯一 ID + conversation_id (string) 会话 ID + metadata (object) 元数据 + usage (Usage) 模型用量信息 + retriever_resources (array[RetrieverResource]) 引用和归属分段列表 + */ +/// +/// 消息结束事件,收到此事件则代表流式返回结束。 +/// +[Serializable] +public record MessageEndEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// 消息唯一 ID + /// + public required string MessageId { get; init; } + + /// + /// 会话 ID + /// + public required string ConversationId { get; init; } + + /// + /// + /// + public MetadataImpl? Metadata { get; init; } + + + [Serializable] + public record MetadataImpl + { + /// + /// 模型用量信息 + /// + public JsonElement Usage { get; init; } + + /// + /// 引用和归属分段列表 + /// + /// retriever_resources + public JsonElement RetrieverResources { get; init; } + } +} + +/// +/// 消息内容替换事件。 开启内容审查和审查输出内容时,若命中了审查条件,则会通过此事件替换消息内容为预设回复。 +/// +[Serializable] +public record MessageReplaceEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// 消息唯一 ID + /// + public required string MessageId { get; init; } + + /// + /// 会话 ID + /// + public required string ConversationId { get; init; } + + /// + /// 替换内容(直接替换 LLM 所有回复文本) + /// + public required string Answer { get; init; } + + /// + /// 创建时间戳 + /// + public int CreateAt { get; init; } +} + +/// +/// workflow 开始执行 +/// +[Serializable] +public record WorkflowStartedEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// workflow 执行 ID + /// + public required string WorkflowRunId { get; init; } + + /// + /// 详细内容 + /// + public DataImpl? Data { get; init; } + + /// + /// + /// + [Serializable] + public record DataImpl + { + /// + /// workflow 执行 ID + /// + public required string Id { get; init; } + + /// + /// 关联 Workflow ID + /// + public required string WorkflowId { get; init; } + + /// + /// 自增序号,App 内自增,从 1 开始 + /// + public int SequenceNumber { get; init; } + + /// + /// 开始时间 + /// + public long CreatedAt { get; init; } + } +} + +/// +/// node_started node 开始执行 +/// +[Serializable] +public record NodeStartedEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// workflow 执行 ID + /// + public required string WorkflowRunId { get; init; } + + /// + /// 详细内容 + /// + public DataImpl? Data { get; init; } + + [Serializable] + public record DataImpl + { + /// + /// workflow 执行 ID + /// + public required string Id { get; init; } + + /// + /// 节点 ID + /// + public required string NodeId { get; init; } + + /// + /// 节点类型 + /// + public required string NodeType { get; init; } + + /// + /// 节点名称 + /// + public required string Title { get; init; } + + /// + /// 执行序号,用于展示 Tracing Node 顺序 + /// + public int Index { get; init; } + + /// + /// 前置节点 ID,用于画布展示执行路径 + /// + public required string PredecessorNodeId { get; init; } + + /// + /// 节点中所有使用到的前置节点变量内容 + /// + public JsonElement Inputs { get; init; } + + /// + /// 开始时间 + /// + public long CreatedAt { get; init; } + } +} + +/// +/// node_finished node 执行结束,成功失败同一事件中不同状态 +/// +[Serializable] +public record NodeFinishedEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// workflow 执行 ID + /// + public required string WorkflowRunId { get; init; } + + /// + /// 详细内容 + /// + public DataImpl? Data { get; init; } + + [Serializable] + public record DataImpl + { + /// + /// node 执行 ID + /// + public required string Id { get; init; } + + /// + /// 节点 ID + /// + public required string NodeId { get; init; } + + /// + /// 执行序号,用于展示 Tracing Node 顺序 + /// + public int Index { get; init; } + + /// + /// optional 前置节点 ID,用于画布展示执行路径 + /// + public required string PredecessorNodeId { get; init; } + + /// + /// 节点中所有使用到的前置节点变量内容 + /// + public JsonElement Inputs { get; init; } + + /// + /// Optional 节点过程数据 + /// + public JsonElement ProcessData { get; init; } + + /// + /// Optional 输出内容 + /// + public JsonElement Outputs { get; init; } + + /// + /// 执行状态 running / succeeded / failed / stopped + /// + public required string Status { get; init; } + + /// + /// Optional 错误原因 + /// + public required string Error { get; init; } + + /// + /// Optional 耗时(s) + /// + public float ElapsedTime { get; init; } + + /// + /// 元数据 + /// + public JsonElement ExecutionMetadata { get; init; } + + /// + /// 开始时间 + /// + public long CreatedAt { get; init; } + } +} + +/// +/// workflow_finished workflow 执行结束,成功失败同一事件中不同状态 +/// +[Serializable] +public record WorkflowFinishedEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public required string TaskId { get; init; } + + /// + /// workflow 执行 ID + /// + public required string WorkflowRunId { get; init; } + + public DataImpl? Data { get; init; } + + [Serializable] + public record DataImpl + { + /// + /// workflow 执行 ID + /// + public required string Id { get; init; } + + /// + /// 关联 Workflow ID + /// + public required string WorkflowId { get; init; } + + /// + /// 执行状态 running / succeeded / failed / stopped + /// + public required string Status { get; init; } + + /// + /// Optional 输出内容 + /// + public JsonElement? Outputs { get; init; } + + /// + /// Optional 错误原因 + /// + public required string Error { get; init; } + + /// + /// Optional 耗时(s) + /// + public float ElapsedTime { get; init; } + + /// + /// Optional 总使用 tokens + /// + public int TotalTokens { get; init; } + + /// + /// 总步数(冗余),默认 0 + /// + public int TotalSteps { get; init; } + + /// + /// 开始时间 + /// + public long CreatedAt { get; init; } + + /// + /// 结束时间 + /// + public long FinishedAt { get; init; } + } +} + +/* + event: error 流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。 + task_id (string) 任务 ID,用于请求跟踪和下方的停止响应接口 + message_id (string) 消息唯一 ID + status (int) HTTP 状态码 + code (string) 错误码 + message (string) 错误消息 + */ +/// +/// error 流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。 +/// +[Serializable] +public record ErrorEvent : ChunkCompletionResponseBody +{ + /// + /// 任务 ID,用于请求跟踪和下方的停止响应接口 + /// + public string TaskId { get; init; } = ""; + + /// + /// 消息唯一 ID + /// + public required string MessageId { get; init; } + + /// + /// HTTP 状态码 + /// + public int Status { get; init; } + + /// + /// 错误码 + /// + public required string Code { get; init; } + + /// + /// 错误消息 + /// + public required string Message { get; init; } +} + +/// +/// 每 10s 一次的 ping 事件,保持连接存活。 +/// +[Serializable] +public record PingEvent : ChunkCompletionResponseBody; + +#endregion \ No newline at end of file diff --git a/DifySharp/DTOs/GetUploadFile.cs b/DifySharp/DTOs/GetUploadFile.cs deleted file mode 100644 index e4bc065..0000000 --- a/DifySharp/DTOs/GetUploadFile.cs +++ /dev/null @@ -1,17 +0,0 @@ -// namespace AlertSuggestion.Lib.DifySharp.DTOs.KnowledgeBase; -// -// public record GetUploadFile -// { -// public record FileInfo( -// string Id, -// string Name, -// long Size, -// string Extension, -// string Url, -// string DownloadUrl, -// string MimeType, -// string CreatedBy, -// long CreatedAt -// ); -// }; - diff --git a/DifySharp/DTOs/KnowledgeBase/Chunk/Get.cs b/DifySharp/DTOs/KnowledgeBase/Chunk/Get.cs index aed3804..66fc0ff 100644 --- a/DifySharp/DTOs/KnowledgeBase/Chunk/Get.cs +++ b/DifySharp/DTOs/KnowledgeBase/Chunk/Get.cs @@ -5,7 +5,7 @@ ///
public record Get { - public record RequestBody(); + public record RequestBody; public record ResponseBody( ICollection Data, diff --git a/DifySharp/DTOs/KnowledgeBase/Dataset/Create.cs b/DifySharp/DTOs/KnowledgeBase/Dataset/Create.cs index fea5622..c738a75 100644 --- a/DifySharp/DTOs/KnowledgeBase/Dataset/Create.cs +++ b/DifySharp/DTOs/KnowledgeBase/Dataset/Create.cs @@ -53,4 +53,4 @@ public enum Provider Vendor = 0, External = 1 } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/DifySharp/DTOs/KnowledgeBase/Document/UpdateByFile.cs b/DifySharp/DTOs/KnowledgeBase/Document/UpdateByFile.cs index 96d40b5..d702bbe 100644 --- a/DifySharp/DTOs/KnowledgeBase/Document/UpdateByFile.cs +++ b/DifySharp/DTOs/KnowledgeBase/Document/UpdateByFile.cs @@ -1,6 +1,4 @@ -using System.Data; - -namespace DifySharp.KnowledgeBase.Document; +namespace DifySharp.KnowledgeBase.Document; public class UpdateByFile { diff --git a/DifySharp/DTOs/KnowledgeBase.cs b/DifySharp/DTOs/KnowledgeBase/KnowledgeBase.cs similarity index 74% rename from DifySharp/DTOs/KnowledgeBase.cs rename to DifySharp/DTOs/KnowledgeBase/KnowledgeBase.cs index 76c2330..5c64c51 100644 --- a/DifySharp/DTOs/KnowledgeBase.cs +++ b/DifySharp/DTOs/KnowledgeBase/KnowledgeBase.cs @@ -1,4 +1,4 @@ -namespace AlertSuggestion.ChatHub.DifyClient.DTOs; +namespace DifySharp.KnowledgeBase; public record BaseResponse; diff --git a/DifySharp/DTOs/Retrive.cs b/DifySharp/DTOs/KnowledgeBase/Retrive.cs similarity index 96% rename from DifySharp/DTOs/Retrive.cs rename to DifySharp/DTOs/KnowledgeBase/Retrive.cs index 5a3d131..adcfb37 100644 --- a/DifySharp/DTOs/Retrive.cs +++ b/DifySharp/DTOs/KnowledgeBase/Retrive.cs @@ -1,4 +1,4 @@ -namespace AlertSuggestion.Lib.DifyClient.DTOs.KnowledgeBase; +namespace DifySharp.KnowledgeBase; public record Retrieve { diff --git a/DifySharp/DTOs/Workflow/Application/Basic.cs b/DifySharp/DTOs/Workflow/Application/Basic.cs new file mode 100644 index 0000000..364a021 --- /dev/null +++ b/DifySharp/DTOs/Workflow/Application/Basic.cs @@ -0,0 +1,12 @@ +namespace DifySharp.Workflow.Application; + +public class Basic +{ + /// + /// + /// + /// application name + /// description + /// tags + public record ResponseBody(string Name, String Description, ICollection Tags); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Workflow/Application/Parameters.cs b/DifySharp/DTOs/Workflow/Application/Parameters.cs new file mode 100644 index 0000000..ad9dcd7 --- /dev/null +++ b/DifySharp/DTOs/Workflow/Application/Parameters.cs @@ -0,0 +1,62 @@ +namespace DifySharp.Workflow.Application; + +public record Parameters +{ + public record ResponseBody( + string OpeningStatement, + SuggestedQuestionsAfterAnswer SuggestedQuestionsAfterAnswer, + SpeechToText SpeechToText, + RetrieverResource RetrieverResource, + AnnotationReply AnnotationReply, + List UserInputForm, + FileUpload FileUpload, + SystemParameters SystemParameters + ); + + public record SuggestedQuestionsAfterAnswer(bool Enabled); + + public record SpeechToText(bool Enabled); + + public record RetrieverResource(bool Enabled); + + public record AnnotationReply(bool Enabled); + + public record UserInputForm(TextInput TextInput, Paragraph Paragraph, Select Select); + + public record TextInput( + string Label, + string Variable, + bool Required, + string Default + ); + + public record Paragraph( + string Label, + string Variable, + bool Required, + string Default + ); + + public record Select( + string Label, + string Variable, + bool Required, + List Options + ); + + public record FileUpload(Image Image); + + public record Image( + bool Enabled, + int NumberLimits, + string Detail, + List TransferMethods + ); + + public record SystemParameters( + int FileSizeLimit, + int ImageFileSizeLimit, + int AudioFileSizeLimit, + int VideoFileSizeLimit + ); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Workflow/Logs.cs b/DifySharp/DTOs/Workflow/Logs.cs new file mode 100644 index 0000000..60d8c73 --- /dev/null +++ b/DifySharp/DTOs/Workflow/Logs.cs @@ -0,0 +1,35 @@ +namespace DifySharp.Workflow.Run; + +public class Logs +{ + public record ResponseBody( + int Page, + int Limit, + int Total, + bool HasMore, + ICollection Data + ); + + public record Data( + string Id, + string Version, + WorkflowRun WorkflowRun, + string CreateFrom, + string CreatedByRole, + string CreatedByAccount, + CreatedByEndUser CreatedByEndUser, + string CreatedAt + ); + + public record WorkflowRun( + string Id, + string Version, + String Status, + String Error, + float ElapsedTime, + int TotalTokens + ); + + public record CreatedByEndUser( + ); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Workflow/Run.cs b/DifySharp/DTOs/Workflow/Run.cs new file mode 100644 index 0000000..7141fdf --- /dev/null +++ b/DifySharp/DTOs/Workflow/Run.cs @@ -0,0 +1,112 @@ +using System.Text.Json; + +namespace DifySharp.Workflow.Run; + +public class Run +{ + public record RequestBody + { + /// + /// 允许传入 App 定义的各变量值。默认值为 {}。 + /// inputs 参数包含了多组键值对(Key/Value pairs), + /// 每组的键对应一个特定变量,每组的值则是该变量的具体值。 + /// + public object Inputs { get; init; } = new(); + + /// + /// + /// `streaming` 流式模式(推荐)。基于 SSE(Server-Sent Events)实现类似打字机输出方式的流式返回。 + /// + /// + /// `blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。 + /// 由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 + /// 注:Agent模式下不允许blocking。 + /// + /// + public ApplicationResponseMode ResponseMode { get; internal set; } = + ApplicationResponseMode.Blocking; + + /// + /// 用户标识,用于定义终端用户的身份,方便检索、统计。 \ + /// 由开发者定义规则,需保证用户标识在应用内唯一。 + /// + public required string User { get; init; } + + /// + /// 上传的文件。 + /// + public ICollection? Files { get; init; } + } + + public record File + { + /// + /// 支持类型: + /// + /// document 具体类型包含:'TXT', 'MD', 'MARKDOWN', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOCX', 'CSV', 'EML', 'MSG', 'PPTX', 'PPT', 'XML', 'EPUB' + /// image 具体类型包含:'JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG' + /// audio 具体类型包含:'MP3', 'M4A', 'WAV', 'WEBM', 'AMR' + /// video 具体类型包含:'MP4', 'MOV', 'MPEG', 'MPGA' + /// custom 具体类型包含:其他文件类型 + /// + /// + public string? Type { get; init; } + + /// + /// 传递方式 + /// + /// Possible values: + /// + /// remote_url + /// local_file + /// + /// + /// + public string? TransferMethods { get; init; } + + /// + /// 图片地址。 + /// + /// <仅当> + /// TransferMethods 为 remote_url 时 + /// + public string? Url { get; init; } + + /// + /// 上传文件 ID。 + /// + /// <仅当> + /// TransferMethods 为 local_file 时 + /// + public string? UploadFileId { get; init; } + } + + public record ResponseBody( + string? WorkflowRunId, + string TaskId, + Data Data + ); + + /// workflow 执行 ID + /// 关联 Workflow ID + /// 执行状态, running / succeeded / failed / stopped + /// Optional 输出内容 + /// Optional 错误原因 + /// Optional 耗时(s) + /// Optional 总使用 tokens + /// 总步数(冗余),默认 0 + /// 开始时间 + /// 结束时间 + public record Data( + string Id, + string WorkflowId, + string? Status, + JsonElement? Outputs, + string? Error, + float? ElapsedTime, + int? TotalTokens, + int TotalSteps, + long CreatedAt, + long FinishedAt + ); +} \ No newline at end of file diff --git a/DifySharp/DTOs/Workflow/Stop.cs b/DifySharp/DTOs/Workflow/Stop.cs new file mode 100644 index 0000000..605fcf3 --- /dev/null +++ b/DifySharp/DTOs/Workflow/Stop.cs @@ -0,0 +1,7 @@ +namespace DifySharp.Workflow.Run; + +public class Stop +{ + /// 固定返回 "success" + public record ResponseBody(string Result); +} \ No newline at end of file diff --git a/DifySharp/DepedncyInjection.cs b/DifySharp/DepedncyInjection.cs index d387d08..d452cf3 100644 --- a/DifySharp/DepedncyInjection.cs +++ b/DifySharp/DepedncyInjection.cs @@ -1,13 +1,10 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Microsoft.Extensions.Options; -using WebApiClientCore.Extensions.OAuths; using DifySharp; using DifySharp.ApiKey; using DifySharp.Apis; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using WebApiClientCore.Extensions.OAuths.TokenProviders; +using DifySharp.DifyClient; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection; @@ -27,13 +24,14 @@ public static IServiceCollection AddDifySharp(this IServiceCollection services, .ConfigureDifyApi() ; + services.AddSingleton(); services.AddScoped(); foreach (var apiKey in options.Secrets) { var key = apiKey.Name; - switch (apiKey.ApiType) + switch (apiKey.ApiType?.ToUpper()) { case DifyApiType.KNOWLEDGE_BASE: // services.AddKeyedScoped>(key); diff --git a/DifySharp/DifyClient/DifyClient.cs b/DifySharp/DifyClient/DifyClient.cs index 0d17dc4..0df6511 100644 --- a/DifySharp/DifyClient/DifyClient.cs +++ b/DifySharp/DifyClient/DifyClient.cs @@ -4,12 +4,20 @@ using DifySharp.Chat.ChatMessages; using DifySharp.Chat.Conversations; using DifySharp.Chat.Messages; +using DifySharp.Completion.CompletionMessages; using DifySharp.KnowledgeBase.Dataset; using DifySharp.KnowledgeBase.Document; +using DifySharp.Workflow.Run; using Microsoft.Extensions.DependencyInjection; +using Basic = DifySharp.Workflow.Application.Basic; using Create = DifySharp.KnowledgeBase.Dataset.Create; using Delete = DifySharp.KnowledgeBase.Document.Delete; using Get = DifySharp.KnowledgeBase.Dataset.Get; +using Parameters = DifySharp.Completion.Application.Parameters; +using PostFeedback = DifySharp.Completion.Messages.PostFeedback; +using Stop = DifySharp.Chat.ChatMessages.Stop; + +// ReSharper disable InconsistentNaming namespace DifySharp; @@ -206,15 +214,51 @@ public CompletionClient(string apiKey, string baseUrl = "https://api.dify.ai/v1" { } - #region ApiCalling - /// - public async Task PostCompletionMessages(object requestBody) => - await Api.PostCompletionMessages(requestBody); + #region Api Calling + + /// + public async Task GetInfo() + { + return await Api.GetInfo(); + } + + /// + public async Task GetParameters() + { + return await Api.GetParameters(); + } - /// - public async Task PostCompletionsMessagesStop(string taskId) => - await Api.PostCompletionsMessagesStop(taskId); + /// + public async Task PostCompletionMessages(CompletionMessages.RequestBody requestBody) + { + return await Api.PostCompletionMessages(requestBody); + } + + /// + public async Task PostCompletionsMessagesStop(string taskId) + { + return await Api.PostCompletionsMessagesStop(taskId); + } + + /// + public async Task PostMessagesFeedbacks(string messageId, + PostFeedback.RequestBody requestBody) + { + return await Api.PostMessagesFeedbacks(messageId, requestBody); + } + + /// + public async Task PostFilesUpload(string user, FileInfo file) + { + return await Api.PostFilesUpload(user, file); + } + + /// + public async Task PostTextToAudio(object requestBody) + { + return await Api.PostTextToAudio(requestBody); + } #endregion } @@ -238,13 +282,13 @@ public ChatClient(string apiKey, string baseUrl = "https://api.dify.ai/v1") : ba #region ApiCalling /// - public async Task GetInfo() + public async Task GetInfo() { return await Api.GetInfo(); } /// - public async Task GetParameters() + public async Task GetParameters() { return await Api.GetParameters(); } @@ -256,8 +300,11 @@ public ChatClient(string apiKey, string baseUrl = "https://api.dify.ai/v1") : ba } /// - public async Task GetConversations(string user, string? last_id = null, - int? limit = null, string? sort_by = null) + public async Task GetConversations( + string user, + string? last_id = null, + int? limit = null, + string? sort_by = null) { return await Api.GetConversations(user, last_id, limit, sort_by); } @@ -288,8 +335,8 @@ public async Task PostChatMessages(ChatMessage.RequestBody } /// - public async Task PostMessagesFeedbacks(string messageId, - PostFeedback.RequestBody requestBody) + public async Task PostMessagesFeedbacks(string messageId, + Chat.Messages.PostFeedback.RequestBody requestBody) { return await Api.PostMessagesFeedbacks(messageId, requestBody); } @@ -334,28 +381,45 @@ internal WorkflowClient(DifyClient client) : base(client) { } + /// + /// Create a instance of Dify with the given api key and base url. + /// + /// api key for dify chat application + /// base url of a dify server, default value: https://api.dify.ai/v1 public WorkflowClient(string apiKey, string baseUrl = "https://api.dify.ai/v1") : base( new DifyClient(new DifyApiSecret(apiKey), baseUrl)) { } - #region ApiCalling + public async Task GetInfo() + { + return await Api.GetInfo(); + } - /// - public async Task PostWorkflowsRun(object requestBody) => + #region Api Calling + + /// + public async Task GetParameters() => + await Api.GetParameters(); + + /// + public async Task PostWorkflowsRun(Run.RequestBody requestBody) => await Api.PostWorkflowsRun(requestBody); - /// - public async Task GetWorkflowsRun(string workflowId) => - await Api.GetWorkflowsRun(workflowId); + /// + public async Task GetWorkflowsRun(string workflowId) => await Api.GetWorkflowsRun(workflowId); - /// + /// public async Task PostWorkflowsTasksStop(string taskId) => await Api.PostWorkflowsTasksStop(taskId); - /// + /// public async Task GetWorkflowsLogs(string keyword, string status, int page, int limit) => await Api.GetWorkflowsLogs(keyword, status, page, limit); + /// + public async Task PostFilesUpload(string user, FileInfo file) => + await Api.PostFilesUpload(user, file); + #endregion } \ No newline at end of file diff --git a/DifySharp/DifyClient/DifyClientFactory.cs b/DifySharp/DifyClient/DifyClientFactory.cs index bd25eae..90d7327 100644 --- a/DifySharp/DifyClient/DifyClientFactory.cs +++ b/DifySharp/DifyClient/DifyClientFactory.cs @@ -1,10 +1,17 @@ using DifySharp.Apis; using Microsoft.Extensions.DependencyInjection; -namespace DifySharp; +namespace DifySharp.DifyClient; -public static class DifyClientFactory +public class DifyClientFactory { + private static IServiceProvider? ServiceProvider; + + public DifyClientFactory(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + /// /// Creates a factory function for creating DifyClient instances. /// @@ -13,7 +20,16 @@ public static class DifyClientFactory /// internal static Func> DifyClientGenericFactory(DifyApiSecret difyApiSecret) where TApi : notnull - => (sp, key) => new DifyClient(difyApiSecret, "", sp); + { + return (sp, key) => + { + if (ServiceProvider == null) + { + var difyFactory = sp.GetRequiredService(); + } + return new DifyClient(difyApiSecret, "", sp); + }; + } internal static Func MakeKnowledgeBaseClientFactory() diff --git a/DifySharp/DifyClientOptions.cs b/DifySharp/DifyClientOptions.cs index 6f9f765..5ae07d6 100644 --- a/DifySharp/DifyClientOptions.cs +++ b/DifySharp/DifyClientOptions.cs @@ -11,6 +11,11 @@ public class DifyClientOptions /// a collection of , for multiple dify applications and knowledge bases management. ///
public ICollection Secrets { get; set; } = new List(); + + /// + /// Specifies whether to enable logging for the dify client. + /// + public bool EnableLogging { get; set; } = false; } /// @@ -49,6 +54,11 @@ public class DifyApiSecret(string? secretKey = "", string? name = "", string? ap /// /// public string? ApiType { get; } = apiType; + + /// + /// Specifies the base url of a dify api server. + /// + public string BaseUrl { get; set; } } public static class DifyApiType diff --git a/DifySharp/DifySharp.csproj b/DifySharp/DifySharp.csproj index b8ef99e..9fab833 100644 --- a/DifySharp/DifySharp.csproj +++ b/DifySharp/DifySharp.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.0.3-alpha1 + 0.0.3-alpha2 fengb3 https://github.com/fengb3/DifySharp.git https://github.com/fengb3/DifySharp diff --git a/DifySharp/Extensions/ChatMessageExtension.cs b/DifySharp/Extensions/ChatMessageExtension.cs index f228e5b..d87513a 100644 --- a/DifySharp/Extensions/ChatMessageExtension.cs +++ b/DifySharp/Extensions/ChatMessageExtension.cs @@ -5,7 +5,6 @@ using System.Text.Json.Serialization; using DifySharp.Apis; using DifySharp.Chat.ChatMessages; -using Microsoft.Extensions.Logging; namespace DifySharp.Extensions; @@ -30,13 +29,15 @@ public static class ChatMessageExtension /// the request body of the chat message. /// The chat message completes the response body. /// throws an exception when ResponseMode is not Blocking. - public static async Task PostChatMessageBlocking( + public static async Task PostChatMessageBlocking( this IChatApi chatApi, ChatMessage.RequestBody requestBody) { - if (requestBody.ResponseMode != ChatMessage.ResponseMode.Blocking) - throw new ArgumentException( - $"ResponseMode must be Blocking when calling {nameof(PostChatMessageBlocking)}"); + // if (requestBody.ResponseMode != ChatMessage.ResponseMode.Blocking) + // throw new ArgumentException( + // $"ResponseMode must be Blocking when calling {nameof(PostChatMessageBlocking)}"); + + requestBody.ApplicationResponseMode = ApplicationResponseMode.Blocking; var httpResponseMessage = await chatApi.PostChatMessages(requestBody); @@ -48,7 +49,7 @@ public static class ChatMessageExtension } var result = - await httpResponseMessage.Content.ReadFromJsonAsync(JsonOptions); + await httpResponseMessage.Content.ReadFromJsonAsync(JsonOptions); Debug.Assert(result != null, nameof(result) + " != null"); @@ -63,14 +64,12 @@ public static class ChatMessageExtension /// An asynchronous enumerable that contains a chunked response body for chat messages. /// Throws an exception when ResponseMode is not Streaming. /// Throws an exception when the HTTP response is not successful. - public static async IAsyncEnumerable PostChatMessageStreaming( + public static async IAsyncEnumerable PostChatMessageStreaming( this IChatApi api, ChatMessage.RequestBody requestBody ) { - if (requestBody.ResponseMode != ChatMessage.ResponseMode.Streaming) - throw new ArgumentException( - $"ResponseMode must be Streaming when calling {nameof(PostChatMessageStreaming)}"); + requestBody.ApplicationResponseMode = ApplicationResponseMode.Streaming; var httpResponseMessage = await api.PostChatMessages(requestBody); @@ -79,15 +78,13 @@ ChatMessage.RequestBody requestBody var msg = await httpResponseMessage.Content.ReadAsStringAsync(); throw new HttpRequestException(msg); - - yield break; } var stream = await httpResponseMessage.Content.ReadAsStreamAsync(); var reader = new StreamReader(stream); - const string prefix = ChatMessage.ChunkChatCompletionResponseBody.CHUNK_PREFIX; + const string prefix = ChunkCompletionResponseBody.CHUNK_PREFIX; while (await reader.ReadLineAsync() is { } line) { @@ -95,7 +92,7 @@ ChatMessage.RequestBody requestBody var json = line[prefix.Length..]; - var chunk = JsonSerializer.Deserialize(json, JsonOptions); + var chunk = JsonSerializer.Deserialize(json, JsonOptions); Debug.Assert(chunk != null, nameof(chunk) + " != null"); diff --git a/README.md b/README.md index d9bfc79..66bfde2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ Dify SDK for csharp - ## Install ```bash @@ -14,8 +13,7 @@ dotnet add package DifySharp ## Usage -### Without Dependency Injection - +### Console ```csharp @@ -46,3 +44,39 @@ await foreach (var chunk in chunks) } // Ring-ding-ding-ding-dingeringeding! Gering-ding-ding-ding-dingeringeding! Wa-pa-pa-pa-pa-pa-pow! Jacha-chacha-chacha-chow! Fraka-kaka-kaka-kaka-kow! A-hee-ahee ha-hee! A-oo-oo-oo-ooo! ``` + +### Dependency Injection - ASP.NET Core + +```csharp + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDifySharp(o => +{ + o.Secrets = + [ + new DifyApiSecret("", "knowledge", DifyApiType.KNOWLEDGE_BASE), + new DifyApiSecret("", "chat", DifyApiType.CHAT) + .. // add more secrets + ]; +}); + +var app = builder.Build(); + +// get ChatClient from DI container with client name +app.MapGet("/", async ([FromKeyedServices("chat")] ChatClient chatClient) => + { + var response = await chatClient.PostChatMessageBlocking(new ChatMessage.RequestBody + { + Query = "ping!", + ResponseMode = ChatMessage.ResponseMode.Blocking, + User = "test-user" + }); + + return Results.Text(response.Answer); // pong! + } +); + +app.Run(); + +```