diff --git a/Claudia.sln b/Claudia.sln
index d09a35d..1a7bfee 100644
--- a/Claudia.sln
+++ b/Claudia.sln
@@ -13,9 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp1", "sandbox\Cons
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1B4BD6F6-8528-4409-BA55-085DA5486D36}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Claudia.Tests", "tests\Claudia.Tests\Claudia.Tests.csproj", "{B4A34AB7-FD1B-4CC4-9859-603D26D7D0A4}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Claudia.Tests", "tests\Claudia.Tests\Claudia.Tests.csproj", "{B4A34AB7-FD1B-4CC4-9859-603D26D7D0A4}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorApp1", "sandbox\BlazorApp1\BlazorApp1.csproj", "{8EEB0F69-132B-4887-959D-25531588FCD2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp1", "sandbox\BlazorApp1\BlazorApp1.csproj", "{8EEB0F69-132B-4887-959D-25531588FCD2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Claudia.FunctionGenerator", "src\Claudia.FunctionGenerator\Claudia.FunctionGenerator.csproj", "{8C464111-AD67-4D2B-9AE2-0B52AB077EBD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -39,6 +41,10 @@ Global
{8EEB0F69-132B-4887-959D-25531588FCD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EEB0F69-132B-4887-959D-25531588FCD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EEB0F69-132B-4887-959D-25531588FCD2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C464111-AD67-4D2B-9AE2-0B52AB077EBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C464111-AD67-4D2B-9AE2-0B52AB077EBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C464111-AD67-4D2B-9AE2-0B52AB077EBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C464111-AD67-4D2B-9AE2-0B52AB077EBD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -48,6 +54,7 @@ Global
{7D6C6A91-7B2E-4616-A40F-ACA1A0BDBEB4} = {E61BFC87-2B96-4699-9B69-EE4B008AE0A0}
{B4A34AB7-FD1B-4CC4-9859-603D26D7D0A4} = {1B4BD6F6-8528-4409-BA55-085DA5486D36}
{8EEB0F69-132B-4887-959D-25531588FCD2} = {E61BFC87-2B96-4699-9B69-EE4B008AE0A0}
+ {8C464111-AD67-4D2B-9AE2-0B52AB077EBD} = {B54A8855-F8F0-4015-80AA-86974E65AC2D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B7CEBA02-BB0C-4102-AE58-DFD114C3192A}
diff --git a/sandbox/ConsoleApp1/ConsoleApp1.csproj b/sandbox/ConsoleApp1/ConsoleApp1.csproj
index 6a1e020..52d22f3 100644
--- a/sandbox/ConsoleApp1/ConsoleApp1.csproj
+++ b/sandbox/ConsoleApp1/ConsoleApp1.csproj
@@ -6,14 +6,19 @@
enable
enable
false
+ true
-
+
+
+ Analyzer
+ false
+
diff --git a/sandbox/ConsoleApp1/Program.cs b/sandbox/ConsoleApp1/Program.cs
index a76a542..63822fa 100644
--- a/sandbox/ConsoleApp1/Program.cs
+++ b/sandbox/ConsoleApp1/Program.cs
@@ -1,52 +1,278 @@
using Claudia;
using R3;
+using System.Xml.Linq;
-var imageBytes = File.ReadAllBytes(@"dish.jpg");
+// function calling
+// https://github.com/anthropics/anthropic-cookbook/blob/main/function_calling/function_calling.ipynb
var anthropic = new Anthropic();
+var systemPrompt = """
+In this environment you have access to a set of tools you can use to answer the user's question.
+
+You may call them like this:
+
+
+ $TOOL_NAME
+
+ <$PARAMETER_NAME>$PARAMETER_VALUE$PARAMETER_NAME>
+ ...
+
+
+
+
+Here are the tools available:
+
+
+ DoPairwiseArithmetic
+
+ Calculator function for doing basic arithmetic.
+ Supports addition, subtraction, multiplication
+
+
+
+ first_operand
+ int
+ First operand (before the operator)
+
+
+ second_operand
+ int
+ Second operand (after the operator)
+
+
+ operator
+ str
+ The operation to perform. Must be either +, -, *, or /
+
+
+
+
+""";
+
+var userInput = "Multiply 1,984,135 by 9,343,116";
+
+// Claude generate tool and parameters XML
var message = await anthropic.Messages.CreateAsync(new()
{
- Model = "claude-3-opus-20240229",
+ Model = Models.Claude3Opus,
MaxTokens = 1024,
- Messages = [new()
- {
- Role = "user",
- Content = [
- new()
- {
- Type = "image",
- Source = new()
- {
- Type = "base64",
- MediaType = "image/jpeg",
- Data = imageBytes
- }
- },
- new()
- {
- Type = "text",
- Text = "Describe this image."
- }
- ]
- }],
+ System = systemPrompt,
+ StopSequences = [""],
+ Messages = [
+ new() { Role = Roles.User, Content = userInput },
+ ],
});
-Console.WriteLine(message);
-var simple = await anthropic.Messages.CreateAsync(new()
+// Parse function_calls XML
+
+// Okay, let's break this down step-by-step:
+//
+//
+// DoPairwiseArithmetic
+//
+// 1984135
+// 9343116
+// *
+//
+//
+var text = message.Content[0].Text!;
+var tagStart = text.IndexOf("");
+var xmlResult = XElement.Parse(text.Substring(tagStart) + message.StopSequence);
+var parameters = xmlResult.Descendants("parameters").Elements();
+
+var first = (double)parameters.First(x => x.Name == "first_operand");
+var second = (double)parameters.First(x => x.Name == "second_operand");
+var operation = (string)parameters.First(x => x.Name == "operator");
+
+// Execute local function
+var result = DoPairwiseArithmetic(first, second, operation);
+
+// Setup result message in XML
+var partialAssistantMessage = $$"""
+
+
+ DoPairwiseArithmetic
+
+ {{first}}
+ {{second}}
+ {{operation}}
+
+
+
+
+
+
+ DoPairwiseArithmetic
+
+ {{result}}
+
+
+
+""";
+
+var callResult = await anthropic.Messages.CreateAsync(new()
{
Model = Models.Claude3Opus,
MaxTokens = 1024,
- Messages = [new()
- {
- Role = Roles.User,
- Content = [
- new(imageBytes, "image/jpeg"),
- "Describe this image."
- ]
- }],
+ System = systemPrompt,
+ Messages = [
+ new() { Role = Roles.User, Content = userInput },
+ new() { Role = Roles.Assistant, Content = partialAssistantMessage },
+ ],
});
-Console.WriteLine(simple);
+
+// Show last result.
+// Therefore, 1,984,135 multiplied by 9,343,116 equals 18,538,003,464,660.
+Console.WriteLine(callResult);
+
+static double DoPairwiseArithmetic(double num1, double num2, string operation)
+{
+ return operation switch
+ {
+ "+" => num1 + num2,
+ "-" => num1 - num2,
+ "*" => num1 * num2,
+ "/" => num1 / num2,
+ _ => throw new ArgumentException("Operation not supported")
+ };
+}
+
+
+
+
+
+
+//var systemPrompt = """
+//In this environment you have access to a set of tools you can use to answer the user's question.
+
+//You may call them like this:
+//
+//
+// $TOOL_NAME
+//
+// <$PARAMETER_NAME>$PARAMETER_VALUE$PARAMETER_NAME>
+// ...
+//
+//
+//
+
+//Here are the tools available:
+//
+//
+// calculator
+//
+// Calculator function for doing basic arithmetic.
+// Supports addition, subtraction, multiplication
+//
+//
+//
+// first_operand
+// int
+// First operand (before the operator)
+//
+//
+// second_operand
+// int
+// Second operand (after the operator)
+//
+//
+// operator
+// str
+// The operation to perform. Must be either +, -, *, or /
+//
+//
+//
+//
+//""";
+
+
+//var message = await anthropic.Messages.CreateAsync(new()
+//{
+// Model = Models.Claude3Opus,
+// MaxTokens = 1024,
+// System = systemPrompt,
+// StopSequences = ["\n\nHuman:", "\n\nAssistant", ""],
+// Messages = [new() { Role = "user", Content = "Multiply 1,984,135 by 9,343,116" }],
+//});
+
+//// Result XML::
+
+
+//var text = message.Content[0].Text!;
+//var tagStart = text.IndexOf("");
+//var xmlResult = XElement.Parse(text.Substring(tagStart) + message.StopSequence);
+//var parameters = xmlResult.Descendants("parameters").Elements();
+
+//var first = (double)parameters.First(x => x.Name == "first_operand");
+//var second = (double)parameters.First(x => x.Name == "second_operand");
+//var operation = (string)parameters.First(x => x.Name == "operator");
+
+//var result = DoPairwiseArithmetic(first, second, operation);
+
+//Console.WriteLine(result);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//var imageBytes = File.ReadAllBytes(@"dish.jpg");
+
+//var anthropic = new Anthropic();
+
+//var message = await anthropic.Messages.CreateAsync(new()
+//{
+// Model = "claude-3-opus-20240229",
+// MaxTokens = 1024,
+// Messages = [new()
+// {
+// Role = "user",
+// Content = [
+// new()
+// {
+// Type = "image",
+// Source = new()
+// {
+// Type = "base64",
+// MediaType = "image/jpeg",
+// Data = imageBytes
+// }
+// },
+// new()
+// {
+// Type = "text",
+// Text = "Describe this image."
+// }
+// ]
+// }],
+//});
+//Console.WriteLine(message);
+
+//var simple = await anthropic.Messages.CreateAsync(new()
+//{
+// Model = Models.Claude3Opus,
+// MaxTokens = 1024,
+// Messages = [new()
+// {
+// Role = Roles.User,
+// Content = [
+// new(imageBytes, "image/jpeg"),
+// "Describe this image."
+// ]
+// }],
+//});
+//Console.WriteLine(simple);
//// convert to array.
//var array = await stream.ToObservable().ToArrayAsync();
@@ -153,4 +379,22 @@
//}, new()
//{
// Timeout = TimeSpan.FromSeconds(5)
-//});
\ No newline at end of file
+//});
+
+
+///
+/// AIUEO!
+///
+public static partial class FunctionTools
+{
+ ///
+ /// foobarbaz
+ ///
+ /// p1
+ /// p2
+ [ClaudiaFunction]
+ public static int Sum(int x, int y)
+ {
+ return x + y;
+ }
+}
\ No newline at end of file
diff --git a/src/Claudia.FunctionGenerator/Claudia.FunctionGenerator.csproj b/src/Claudia.FunctionGenerator/Claudia.FunctionGenerator.csproj
new file mode 100644
index 0000000..d30c299
--- /dev/null
+++ b/src/Claudia.FunctionGenerator/Claudia.FunctionGenerator.csproj
@@ -0,0 +1,32 @@
+
+
+
+ netstandard2.0
+ 12
+ enable
+ cs
+ enable
+
+ true
+ true
+
+ false
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/src/Claudia.FunctionGenerator/ClaudiaFunctionGenerator.cs b/src/Claudia.FunctionGenerator/ClaudiaFunctionGenerator.cs
new file mode 100644
index 0000000..e8d39fc
--- /dev/null
+++ b/src/Claudia.FunctionGenerator/ClaudiaFunctionGenerator.cs
@@ -0,0 +1,32 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Collections.Immutable;
+
+namespace Claudia.FunctionGenerator;
+
+[Generator(LanguageNames.CSharp)]
+public partial class ClaudiaFunctionGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var source = context.SyntaxProvider.ForAttributeWithMetadataName(
+ "Claudia.ClaudiaFunctionAttribute",
+ static (node, token) => node is MethodDeclarationSyntax,
+ static (context, token) => context);
+
+ context.RegisterSourceOutput(source.Collect(), Execute);
+ }
+
+ static void Execute(SourceProductionContext context, ImmutableArray sources)
+ {
+ if (sources.Length == 0) return;
+
+ var result = new Parser(context, sources).Parse();
+ if (result.Length != 0)
+ {
+ var emitter = new Emitter(context, result);
+ emitter.Emit();
+ }
+ }
+}
diff --git a/src/Claudia.FunctionGenerator/Emitter.cs b/src/Claudia.FunctionGenerator/Emitter.cs
new file mode 100644
index 0000000..d71d941
--- /dev/null
+++ b/src/Claudia.FunctionGenerator/Emitter.cs
@@ -0,0 +1,61 @@
+using Microsoft.CodeAnalysis;
+using System.Xml.Linq;
+
+namespace Claudia.FunctionGenerator;
+
+public class Emitter
+{
+ private SourceProductionContext context;
+ private ParseResult[] result;
+
+ public Emitter(SourceProductionContext context, ParseResult[] result)
+ {
+ this.context = context;
+ this.result = result;
+ }
+
+ internal void Emit()
+ {
+ foreach (var item in result.GroupBy(x => x.TypeSymbol, SymbolEqualityComparer.Default))
+ {
+ var type = item.Key!;
+
+ var typeDocComment = type.GetDocumentationCommentXml();
+
+ var name = type.Name;
+ var description = ((string)XElement.Parse(typeDocComment).Element("summary")).Trim();
+
+
+ foreach (var method in item)
+ {
+
+ var docComment = method.MethodSymbol.GetDocumentationCommentXml();
+
+
+
+ //new XElement("parameter",
+ // new XElement(
+
+ }
+
+
+
+
+ new XElement("tool_description",
+ new XElement("tool_name", name),
+ new XElement("description", description),
+ new XElement("parameters", null));
+
+ }
+
+
+
+
+
+
+
+
+
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/src/Claudia.FunctionGenerator/ParseResult.cs b/src/Claudia.FunctionGenerator/ParseResult.cs
new file mode 100644
index 0000000..180e070
--- /dev/null
+++ b/src/Claudia.FunctionGenerator/ParseResult.cs
@@ -0,0 +1,13 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Claudia.FunctionGenerator;
+
+public record ParseResult
+{
+ public required TypeDeclarationSyntax TypeSyntax { get; set; }
+ public required INamedTypeSymbol TypeSymbol { get; set; }
+ public required MethodDeclarationSyntax MethodSyntax { get; set; }
+ public required IMethodSymbol MethodSymbol { get; set; }
+}
+
diff --git a/src/Claudia.FunctionGenerator/Parser.cs b/src/Claudia.FunctionGenerator/Parser.cs
new file mode 100644
index 0000000..4fb20e5
--- /dev/null
+++ b/src/Claudia.FunctionGenerator/Parser.cs
@@ -0,0 +1,124 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Text;
+
+namespace Claudia.FunctionGenerator;
+
+public class Parser
+{
+ private SourceProductionContext context;
+ private ImmutableArray sources;
+
+ public Parser(SourceProductionContext context, ImmutableArray sources)
+ {
+ this.context = context;
+ this.sources = sources;
+ }
+
+ internal ParseResult[] Parse()
+ {
+ var list = new List();
+
+ // grouping by type(TypeDeclarationSyntax)
+ foreach (var item in sources.GroupBy(x => x.TargetNode.Parent))
+ {
+ if (item.Key == null) continue;
+ var targetType = (TypeDeclarationSyntax)item.Key;
+ var symbol = item.First().SemanticModel.GetDeclaredSymbol(targetType);
+ if (symbol == null) continue;
+
+ // verify is partial
+ if (!IsPartial(targetType))
+ {
+ // TODO:
+ // context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MustBePartial, targetType.Identifier.GetLocation(), symbol.Name));
+ continue;
+ }
+
+ // nested is not allowed
+ if (IsNested(targetType))
+ {
+ // TODO:
+ //context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.NestedNotAllow, targetType.Identifier.GetLocation(), symbol.Name));
+ continue;
+ }
+
+ // verify is generis type
+ if (symbol.TypeParameters.Length > 0)
+ {
+ // TODO:
+ //context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GenericTypeNotSupported, targetType.Identifier.GetLocation(), symbol.Name));
+ continue;
+ }
+
+ // TODO:verify documentation somment of summary.
+
+ foreach (var source in item)
+ {
+ // source.TargetNode
+ var method = (IMethodSymbol)source.TargetSymbol;
+
+ // TODO:verify not static
+ // TODO:verify documentation somment of summary and parameters.
+
+ //var (attr, setLogLevel) = GetAttribute(source);
+ //var msg = attr.Message;
+
+ //// parse and verify
+ //if (!MessageParser.TryParseFormat(attr.Message, out var segments))
+ //{
+ // context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MessageTemplateParseFailed, (source.TargetNode as MethodDeclarationSyntax)!.Identifier.GetLocation(), method.Name));
+ // continue;
+ //}
+
+ //var (parameters, foundLogLevel) = GetMethodParameters(method, setLogLevel);
+
+ //// Set LinkedParameters
+ //foreach (var p in parameters.Where(x => x.IsParameter))
+ //{
+ // p.LinkedMessageSegment = segments
+ // .Where(x => x.Kind == MessageSegmentKind.NameParameter)
+ // .FirstOrDefault(x => x.NameParameter.Equals(p.Symbol.Name, StringComparison.OrdinalIgnoreCase));
+ //}
+
+ //var methodDecl = new LogMethodDeclaration(
+ // Attribute: attr,
+ // TargetMethod: (IMethodSymbol)source.TargetSymbol,
+ // TargetSyntax: (MethodDeclarationSyntax)source.TargetNode,
+ // MessageSegments: segments,
+ // MethodParameters: parameters);
+
+ //if (!Verify(methodDecl, foundLogLevel, targetType, symbol))
+ //{
+ // continue;
+ //}
+
+ //logMethods.Add(methodDecl);
+
+ list.Add(new ParseResult
+ {
+ TypeSyntax = targetType,
+ TypeSymbol = symbol,
+ MethodSyntax = (MethodDeclarationSyntax)source.TargetNode,
+ MethodSymbol = method
+ });
+ }
+ }
+
+ return list.ToArray();
+ }
+
+ static bool IsPartial(TypeDeclarationSyntax typeDeclaration)
+ {
+ return typeDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
+ }
+
+ static bool IsNested(TypeDeclarationSyntax typeDeclaration)
+ {
+ return typeDeclaration.Parent is TypeDeclarationSyntax;
+ }
+}
diff --git a/src/Claudia.FunctionGenerator/Properties/launchSettings.json b/src/Claudia.FunctionGenerator/Properties/launchSettings.json
new file mode 100644
index 0000000..14f2b60
--- /dev/null
+++ b/src/Claudia.FunctionGenerator/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "プロファイル 1": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "..\\..\\sandbox\\ConsoleApp1\\ConsoleApp1.csproj"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Claudia/ClaudiaFunctionAttribute.cs b/src/Claudia/ClaudiaFunctionAttribute.cs
new file mode 100644
index 0000000..4ca4b33
--- /dev/null
+++ b/src/Claudia/ClaudiaFunctionAttribute.cs
@@ -0,0 +1,6 @@
+namespace Claudia;
+
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+public class ClaudiaFunctionAttribute : Attribute
+{
+}