Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/Math/MathAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public interface IMathAPI
double id(double x);
[Comment("Negate a number")]
double neg(double x);
[Comment("Unknown request")]
void unknown(string text);
//[Comment("Unknown request")]
//void unknown(string text);
}

/// <summary>
Expand Down
30 changes: 18 additions & 12 deletions examples/Math/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public class Math : ConsoleApp
{
ProgramTranslator _translator;
Api<IMathAPI> _api;
//CSharpProgramCompiler _compiler;
ProgramCompiler _compiler;

Math()
Expand All @@ -19,7 +18,6 @@ public class Math : ConsoleApp
new CompletionService(Config.LoadOpenAI()),
_api
);
//_compiler = new CSharpProgramCompiler("math");
_compiler = new ProgramCompiler(_api.TypeInfo);
_api.CallCompleted += this.DisplayCall;
// Uncomment to see ALL raw messages to and from the AI
Expand All @@ -31,25 +29,33 @@ public class Math : ConsoleApp
protected override async Task ProcessRequestAsync(string input, CancellationToken cancelToken)
{
using Program program = await _translator.TranslateAsync(input);
DisplayProgram(program);

// Print whatever program was returned
program.Print(_api.Type.Name);
Console.WriteLine();

if (program.IsComplete)
{
// IsComplete: If program has steps and program.HasNotTranslated is false
RunProgram(program);
}
}

void RunProgram(Program program)
{
Console.WriteLine("Running program");
dynamic result = program.Run(_api);
if (result != null && result is double)
dynamic retval = program.Run(_api);
if (retval != null && retval is double)
{
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Result: {retval}");
}
else
{
Console.WriteLine("No result");
}
}

private void DisplayProgram(Program program)
{
new ProgramWriter(Console.Out).Write(program, typeof(IMathAPI));
}

private void DisplayCall(string functionName, dynamic[] args, dynamic result)
void DisplayCall(string functionName, dynamic[] args, dynamic result)
{
new ProgramWriter(Console.Out).Write(functionName, args);
Console.WriteLine($"==> {result}");
Expand Down
36 changes: 6 additions & 30 deletions examples/Plugins/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
using Microsoft.TypeChat;
using Microsoft.TypeChat.Schema;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Skills.Core;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using System.ComponentModel;
using Microsoft.SemanticKernel.AI.TextCompletion;

namespace Plugins;
Expand Down Expand Up @@ -38,35 +35,13 @@ public PluginApp()

protected override async Task ProcessRequestAsync(string input, CancellationToken cancelToken)
{
Result<Program> program = await _translator.TranslateAsync(input);
if (!program.Success)
{
Console.WriteLine($"## Failed:\n{program.Message}");
}
PrintProgram(program, program.Success);
if (program.Success)
{
await RunProgram(program);
}
}
using Program program = await _translator.TranslateAsync(input);
program.Print(_pluginApi.TypeName);
Console.WriteLine();

void PrintProgram(Program program, bool success)
{
if (program == null) { return; }

if (!program.PrintNotTranslated())
{
program.PrintTranslationNotes();
}

if (program.HasSteps)
if (program.IsComplete)
{
if (!program.IsComplete || !success)
{
Console.WriteLine("Possible program with possibly needed APIs:");
}
program.Print(_pluginApi.TypeName);
Console.WriteLine();
await RunProgram(program);
}
}

Expand All @@ -76,6 +51,7 @@ async Task RunProgram(Program program)
{
return;
}
Console.WriteLine("Running program");
string result = await _interpreter.RunAsync(program, _pluginApi.InvokeAsync);
if (!string.IsNullOrEmpty(result))
{
Expand Down
16 changes: 11 additions & 5 deletions src/typechat.app/ConsoleApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ async Task EvalInput(string input, CancellationToken cancelToken)
}
catch (Exception ex)
{
await OnError(input, ex);
OnException(input, ex);
}
}

Expand Down Expand Up @@ -117,11 +117,17 @@ protected void SubscribeAllEvents<T>(JsonTranslator<T> translator)
translator.CompletionReceived += this.OnCompletionReceived;
}

protected virtual Task OnError(string input, Exception ex)
protected virtual void OnException(string input, Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine();
return Task.CompletedTask;
if (ex is TypeChatException tex)
{
tex.Print();
}
else
{
Console.WriteLine(ex);
Console.WriteLine();
}
}

protected void OnSendingPrompt(string value)
Expand Down
31 changes: 31 additions & 0 deletions src/typechat.app/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,35 @@ public static bool IsNullOrEmpty(this Array array)
{
return (array == null || array.Length == 0);
}

public static void Print(this TypeChatException tex)
{
Console.WriteLine($"## Failed: TypeChatException");
Console.WriteLine(tex.ToString());
}

public static void PrintNotTranslated(this Program program)
{
if (program != null && program.HasNotTranslated)
{
Console.WriteLine("I could not translate the following:");
ConsoleApp.WriteLines(program.NotTranslated);
Console.WriteLine();
}
}

public static void Print(this Program program, string apiType)
{
if (program == null)
{
return;
}
program.PrintNotTranslated();
if (program.HasSteps && program.HasNotTranslated)
{
Console.WriteLine("Suggested program that may include suggested APIs:");
}
new ProgramWriter(Console.Out).Write(program, apiType);
}

}
37 changes: 0 additions & 37 deletions src/typechat.app/ProgramEx.cs

This file was deleted.

12 changes: 12 additions & 0 deletions src/typechat.program/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ internal static bool IsAsync(this ParameterInfo returnType)
return (returnType.ParameterType.IsAssignableTo(typeof(Task)));
}

internal static bool IsCompatibleWith(this ParameterInfo param, Type fromType)
{
return (param.ParameterType == fromType ||
param.ParameterType.IsPrimitive && fromType.IsPrimitive);
}

internal static bool CanBeDeserialized(this ParameterInfo param)
{
return (!param.ParameterType.IsPrimitive &&
!param.ParameterType.IsString());
}

internal static string Stringify<T>(this T value)
{
return JsonSerializer.Serialize<T>(value, JsonProgramConvertor.Options);
Expand Down
11 changes: 11 additions & 0 deletions src/typechat.program/ProgramCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ LinqExpression Compile(Expression expr)
break;

case ValueExpr valueExpr:
Type valueType = valueExpr.Type;
if (!param.IsCompatibleWith(valueType))
{
ProgramException.ThrowTypeMismatch(call, param, valueExpr.Type);
}
LinqExpression value = Compile(valueExpr);
if (param.ParameterType != valueExpr.Type)
{
Expand All @@ -162,6 +167,12 @@ LinqExpression Compile(Expression expr)
break;

case ObjectExpr objExpr:
Type objType = objExpr.Type;
if (!param.CanBeDeserialized())
{
// Can't deserialize an object to a primitive type
ProgramException.ThrowTypeMismatch(call, param, objType);
}
var jsonObjExpr = Compile(objExpr);
args[i] = CastFromJsonObject(jsonObjExpr, param.ParameterType);
break;
Expand Down
16 changes: 6 additions & 10 deletions src/typechat.program/ProgramException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,14 @@ public static void ThrowArgCountMismatch(FunctionCall call, int expectedCount, i
throw new ProgramException(ProgramException.ErrorCode.ArgCountMismatch, message);
}

internal static void ThrowTypeMismatch(JsonValueKind expected, JsonValueKind actual)
internal static void ThrowTypeMismatch(FunctionCall call, ParameterInfo param, Type actual)
{
throw new ProgramException(ProgramException.ErrorCode.TypeMistmatch, $"Type mismatch. Expected {expected}, Actual {actual}");
}
internal static void ThrowTypeMismatch(Type expected, Type actual)
{
throw new ProgramException(ProgramException.ErrorCode.TypeMistmatch, $"Type mismatch. Expected {expected}, Actual {actual}");
}
internal static void ThrowTypeMismatch(string name, Type expected, Type actual)
{
throw new ProgramException(ProgramException.ErrorCode.TypeMistmatch, $"@func {name} Type mismatch. Expected {expected}, Actual {actual}");
throw new ProgramException(
ProgramException.ErrorCode.TypeMistmatch,
$"TypeMismatch: @func {call.Name} @arg {param.Name}: Expected {param.ParameterType.Name}, Got {actual.Name}"
);
}

internal static void ThrowInvalidResultRef(int refId)
{
throw new ProgramException(ProgramException.ErrorCode.InvalidResultRef, $"{refId} is not a valid ResultReference");
Expand Down
5 changes: 1 addition & 4 deletions src/typechat.program/ProgramParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ public Program Parse(JsonDocument programSource)
}
}

if (program.HasSteps ||
program.HasNotTranslated ||
!string.IsNullOrEmpty(program.TranslationNotes)
)
if (program.HasSteps || program.HasNotTranslated)
{
// Something useful to return back to the caller
return program;
Expand Down
7 changes: 6 additions & 1 deletion src/typechat.program/ProgramSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ namespace Microsoft.TypeChat;
[JsonConverter(typeof(JsonProgramConvertor))]
public partial class Program
{
/// <summary>
/// Generated steps to take for this program
/// </summary>
public Steps? Steps
{
get;
internal set;
}

// Parts of the user request that could not be translated into a program
/// <summary>
/// Part or all of the user request that could not be translated into program steps
/// </summary>
public string[] NotTranslated { get; set; }
}

Expand Down
4 changes: 2 additions & 2 deletions src/typechat.program/ProgramSchema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// A program consists of a sequence of function calls that are evaluated in order.
// **Only use supplied API**
export type Program = {
"@steps": FunctionCall[];
// Use this for parts of the user request not tranlated because of API is iadequate.
// Collect parts of the user request that could not translated.
// Including if NO suitable API provided
"@cannot_translate"?: string[];
}

Expand Down
21 changes: 14 additions & 7 deletions src/typechat.program/ProgramSchemaImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,15 @@ internal Program(JsonDocument? source = null)
public bool HasSteps => (Steps != null && !Steps.Calls.IsNullOrEmpty());

/// <summary>
/// Optional... notes emitted by the LLM during translation. These are often
/// sent as prologue and epilogue of what the AI returned
/// A program is deemed Complete only if it has Steps and the language model claims to have
/// translated all of the user's request and intent into a program.
/// </summary>
[JsonIgnore]
public string? TranslationNotes { get; internal set; }
public bool IsComplete => (HasSteps && NotTranslated.IsNullOrEmpty());

/// <summary>
/// A Complete Program has Steps and nothing that was not translated.
/// Were parts of the user request not translated?
/// </summary>
[JsonIgnore]
public bool IsComplete => (HasSteps && NotTranslated.IsNullOrEmpty());

[JsonIgnore]
public bool HasNotTranslated => !NotTranslated.IsNullOrEmpty();

Expand All @@ -67,6 +64,10 @@ public void Dispose()
public dynamic Run(Api api)
{
ArgumentNullException.ThrowIfNull(api, nameof(api));
if (!IsComplete)
{
throw new ProgramException(ProgramException.ErrorCode.InvalidProgram);
}
if (Delegate != null)
{
return Delegate.DynamicInvoke();
Expand All @@ -79,6 +80,12 @@ public dynamic Run(Api api)
public Task<dynamic> RunAsync(Api api)
{
ArgumentNullException.ThrowIfNull(api, nameof(api));

if (!IsComplete)
{
throw new ProgramException(ProgramException.ErrorCode.InvalidProgram);
}

ProgramInterpreter interpreter = new ProgramInterpreter();
return interpreter.RunAsync(this, api.CallAsync);
}
Expand Down
Loading