microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
aamirj/minimal

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Libraries/Microsoft.Teams.AI/Prompts/ChatPrompt/ChatPrompt.cs

252lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4using System.Reflection;
5
6using Json.Schema;
7using Json.Schema.Generation;
8
9using Microsoft.Teams.AI.Annotations;
10using Microsoft.Teams.AI.Messages;
11using Microsoft.Teams.AI.Models;
12using Microsoft.Teams.Common.Extensions;
13using Microsoft.Teams.Common.Logging;
14
15namespace Microsoft.Teams.AI.Prompts;
16
17/// <summary>
18/// a prompt that can send/receive text
19/// messages and expose chat model specific
20/// features like streaming/functions
21/// </summary>
22public interface IChatPrompt : IPrompt
23{
24 /// <summary>
25 /// the message history
26 /// </summary>
27 public IList<IMessage> Messages { get; }
28
29 /// <summary>
30 /// the collection of registered functions
31 /// </summary>
32 public FunctionCollection Functions { get; }
33}
34
35/// <summary>
36/// a prompt that can send/receive text
37/// messages and expose chat model specific
38/// features like streaming/functions
39/// </summary>
40public interface IChatPrompt<TOptions> : IChatPrompt
41{
42 /// <summary>
43 /// register an error handler
44 /// </summary>
45 public IChatPrompt<TOptions> OnError(Action<Exception> onError);
46
47 /// <summary>
48 /// register an error handler
49 /// </summary>
50 public IChatPrompt<TOptions> OnError(Func<Exception, Task> onError);
51
52 /// <summary>
53 /// send a message via the prompt using string content
54 /// </summary>
55 /// <param name="text">the message text</param>
56 /// <param name="options">the request options</param>
57 /// <param name="onChunk">
58 /// the stream chunk handler (if notnull streaming is enabled)
59 /// </param>
60 /// <returns>the models response</returns>
61 public Task<ModelMessage<string>> Send(string text, RequestOptions? options = null, OnStreamChunk? onChunk = null, CancellationToken cancellationToken = default);
62
63 /// <summary>
64 /// send a message via the prompt using content blocks
65 /// </summary>
66 /// <param name="content">the message content</param>
67 /// <param name="options">the request options</param>
68 /// <param name="onChunk">
69 /// the stream chunk handler (if notnull streaming is enabled)
70 /// </param>
71 /// <returns>the models response</returns>
72 public Task<ModelMessage<string>> Send(IContent[] content, RequestOptions? options = null, OnStreamChunk? onChunk = null, CancellationToken cancellationToken = default);
73
74 /// <summary>
75 /// send a message via the prompt
76 /// </summary>
77 /// <param name="message">the message to send</param>
78 /// <param name="options">the request options</param>
79 /// <param name="onChunk">
80 /// the stream chunk handler (if notnull streaming is enabled)
81 /// </param>
82 /// <returns>the models response</returns>
83 public Task<ModelMessage<string>> Send(UserMessage<string> message, RequestOptions? options = null, OnStreamChunk? onChunk = null, CancellationToken cancellationToken = default);
84
85 /// <summary>
86 /// send a message via the prompt
87 /// </summary>
88 /// <param name="message">the message to send</param>
89 /// <param name="options">the request options</param>
90 /// <param name="onChunk">
91 /// the stream chunk handler (if notnull streaming is enabled)
92 /// </param>
93 /// <returns>the models response</returns>
94 public Task<ModelMessage<string>> Send(UserMessage<IEnumerable<IContent>> message, RequestOptions? options = null, OnStreamChunk? onChunk = null, CancellationToken cancellationToken = default);
95
96 /// <summary>
97 /// options to send when invoking a prompt
98 /// </summary>
99 public class RequestOptions
100 {
101 /// <summary>
102 /// the conversation history
103 /// </summary>
104 public IList<IMessage>? Messages { get; set; }
105
106 /// <summary>
107 /// the model request options
108 /// </summary>
109 public TOptions? Request { get; set; }
110 }
111}
112
113/// <summary>
114/// a prompt that can send/receive text
115/// messages and expose chat model specific
116/// features like streaming/functions
117/// </summary>
118public partial class ChatPrompt<TOptions> : IChatPrompt<TOptions>
119{
120 public string Name { get; private set; }
121 public string Description { get; private set; }
122 public IList<IMessage> Messages { get; private set; }
123 public FunctionCollection Functions { get; private set; }
124
125 protected IChatModel<TOptions> Model { get; }
126 protected ITemplate? Template { get; }
127 protected ILogger Logger { get; }
128 protected IList<IChatPlugin> Plugins { get; }
129 protected event EventHandler<Exception> ErrorEvent;
130
131 public ChatPrompt(IChatModel<TOptions> model, ChatPromptOptions? options = null)
132 {
133 options ??= new();
134 Name = options.Name ?? "Chat";
135 Description = options.Description ?? "an agent you can chat with";
136 Model = model;
137 Template = options.Instructions;
138 Messages = options.Messages ?? [];
139 Functions = new();
140 Logger = (options.Logger ?? new ConsoleLogger()).Child($"AI.{Name}");
141 Plugins = [];
142 ErrorEvent = (_, ex) => Logger.Error(ex);
143 }
144
145 public ChatPrompt(ChatPrompt<TOptions> prompt)
146 {
147 Name = prompt.Name;
148 Description = prompt.Description;
149 Messages = prompt.Messages;
150 Functions = prompt.Functions;
151 Model = prompt.Model;
152 Template = prompt.Template;
153 Logger = prompt.Logger;
154 Plugins = prompt.Plugins;
155 ErrorEvent = prompt.ErrorEvent;
156 }
157
158 public ChatPrompt(string name, ChatPrompt<TOptions> prompt)
159 {
160 Name = name;
161 Description = prompt.Description;
162 Messages = prompt.Messages;
163 Functions = prompt.Functions;
164 Model = prompt.Model;
165 Template = prompt.Template;
166 Logger = prompt.Logger.Peer(name);
167 Plugins = prompt.Plugins;
168 ErrorEvent = prompt.ErrorEvent;
169 }
170
171 /// <summary>
172 /// create a ChatPrompt from any class
173 /// utilizing the ChatPromptAttribute
174 /// </summary>
175 /// <param name="model">the model to use</param>
176 /// <param name="value">the class instance to use</param>
177 /// <returns>a ChatPrompt</returns>
178 public static ChatPrompt<TOptions> From<T>(IChatModel<TOptions> model, T value, ChatPromptOptions? options = null) where T : class
179 {
180 var type = value.GetType();
181 var promptAttribute = type.GetCustomAttribute<PromptAttribute>();
182 var nameAttribute = type.GetCustomAttribute<Prompt.NameAttribute>();
183 var descriptionAttribute = type.GetCustomAttribute<Prompt.DescriptionAttribute>();
184 var instructionsAttribute = type.GetCustomAttribute<Prompt.InstructionsAttribute>();
185
186 if (promptAttribute is null)
187 {
188 throw new Exception("only types utilizing the ChatPromptAttribute can be turned into a ChatPrompt");
189 }
190
191 var name = promptAttribute.Name ?? nameAttribute?.Name ?? type.Name;
192 var description = promptAttribute.Description ?? descriptionAttribute?.Description;
193 var instructions = promptAttribute.Instructions ?? instructionsAttribute?.Instructions;
194 options ??= new();
195 options.WithName(name);
196
197 if (description is not null)
198 {
199 options = options.WithDescription(description);
200 }
201
202 if (instructions is not null)
203 {
204 options = options.WithInstructions(instructions);
205 }
206
207 var prompt = new ChatPrompt<TOptions>(model, options);
208
209 foreach (var method in type.GetMethods())
210 {
211 var functionAttribute = method.GetCustomAttribute<FunctionAttribute>();
212 var functionDescriptionAttribute = method.GetCustomAttribute<Annotations.Function.DescriptionAttribute>();
213
214 if (functionAttribute is null) continue;
215
216 var parameters = method.GetParameters().Select(p =>
217 {
218 var name = p.GetCustomAttribute<ParamAttribute>()?.Name ?? p.Name ?? p.Position.ToString();
219 var schema = new JsonSchemaBuilder().FromType(p.ParameterType).Build();
220 var required = !p.IsOptional;
221 return (name, schema, required);
222 });
223
224 var schema = new JsonSchemaBuilder()
225 .Type(SchemaValueType.Object)
226 .Properties(parameters.Select(item => (item.name, item.schema)).ToArray())
227 .Required(parameters.Where(item => item.required).Select(item => item.name));
228
229 var function = new Function(
230 functionAttribute.Name ?? method.Name,
231 functionAttribute.Description ?? functionDescriptionAttribute?.Description,
232 parameters.Count() > 0 ? schema.Build() : null,
233 method.CreateDelegate(value)
234 );
235
236 prompt.Function(function);
237 }
238
239 foreach (var fields in type.GetFields())
240 {
241 var chatPluginAttribute = fields.GetCustomAttribute<ChatPluginAttribute>();
242 if (chatPluginAttribute is null) continue;
243 var plugin = fields.GetValue(value);
244 if (plugin is IChatPlugin chatPlugin)
245 {
246 prompt.Plugin(chatPlugin);
247 }
248 }
249
250 return prompt;
251 }
252}