-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Class design has gotten a bit confusing #28
Comments
@nganju98 Good feedback. Still ironing out how to mesh tools with messages...it's a bit complicated under the hood. FWIW, there IS a |
@nganju98 would something like this be cleaner for beginners? var client = new AnthropicClient();
var messages = new List<Message>()
{
new Message(RoleType.User, "Who won the world series in 2020?"),
new Message(RoleType.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(RoleType.User, "Where was it played?"),
};
var parameters = new MessageParameters()
{
Messages = messages,
MaxTokens = 1024,
Model = AnthropicModels.Claude3Opus,
Stream = false,
Temperature = 1.0m,
};
var res = await client.Messages.GetClaudeMessageAsync(parameters);
Console.WriteLine(res.FirstMessage.Text);
messages.Add(res.Message);
messages.Add(new Message(RoleType.User,"Who was the starting pitcher for the Dodgers?"));
var res2 = await client.Messages.GetClaudeMessageAsync(parameters);
Console.WriteLine(res2.FirstMessage.Text); |
Sure, looks good. I'm more interested in what the advanced mode will look
like :) Does this design get rid of the dynamic stuff?
…On Sat, Apr 20, 2024 at 4:54 AM tghamm ***@***.***> wrote:
@nganju98 <https://github.com/nganju98> would something like this be
cleaner for beginners?
var client = new AnthropicClient();var messages = new List<Message>(){
new Message(RoleType.User, "Who won the world series in 2020?"),
new Message(RoleType.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(RoleType.User, "Where was it played?"),};
var parameters = new MessageParameters(){
Messages = messages,
MaxTokens = 1024,
Model = AnthropicModels.Claude3Opus,
Stream = false,
Temperature = 1.0m,};var res = await client.Messages.GetClaudeMessageAsync(parameters);
Console.WriteLine(res.FirstMessage.Text);
messages.Add(res.Message);
messages.Add(new Message(RoleType.User,"Who was the starting pitcher for the Dodgers?"));var res2 = await client.Messages.GetClaudeMessageAsync(parameters);
Console.WriteLine(res2.FirstMessage.Text);
—
Reply to this email directly, view it on GitHub
<#28 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AIGUSHPQQOT6EDTIJVZHXETY6IUNBAVCNFSM6AAAAABGNKBRC2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANRXGYYDQMZQGU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Gotcha. So, yes and no. var client = new AnthropicClient();
var messages = new List<Message>()
{
new Message(RoleType.User, "Who won the world series in 2020?"),
new Message(RoleType.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
new Message(RoleType.User, "Where was it played?"),
};
var parameters = new MessageParameters()
{
Messages = messages,
MaxTokens = 1024,
Model = AnthropicModels.Claude3Opus,
Stream = false,
Temperature = 1.0m,
};
var res = await client.Messages.GetClaudeMessageAsync(parameters);
//either of these will work and print the text
Console.WriteLine(res.Message);
Console.WriteLine(res.Message.ToString());
messages.Add(res.Message);
messages.Add(new Message(RoleType.User,"Who was the starting pitcher for the Dodgers?"));
var res2 = await client.Messages.GetClaudeMessageAsync(parameters);
Console.WriteLine(res2.Message); For a more advanced scenario that requires Tools, it's designed to work in a variety of ways, but here's one way: string resourceName = "Anthropic.SDK.Tests.Red_Apple.jpg";
Assembly assembly = Assembly.GetExecutingAssembly();
await using Stream stream = assembly.GetManifestResourceStream(resourceName);
byte[] imageBytes;
using (var memoryStream = new MemoryStream())
{
await stream.CopyToAsync(memoryStream);
imageBytes = memoryStream.ToArray();
}
string base64String = Convert.ToBase64String(imageBytes);
var client = new AnthropicClient();
var messages = new List<Message>();
messages.Add(new Message()
{
Role = RoleType.User,
//note: Content is of type List<ContentBase>
Content = new List<ContentBase>()
{
new ImageContent()
{
Source = new ImageSource()
{
MediaType = "image/jpeg",
Data = base64String
}
},
new TextContent()
{
Text = "Use `record_summary` to describe this image."
}
}
});
var imageSchema = new ImageSchema
{
Type = "object",
Required = new string[] { "key_colors", "description"},
Properties = new Properties()
{
KeyColors = new KeyColorsProperty
{
Items = new ItemProperty
{
Properties = new Dictionary<string, ColorProperty>
{
{ "r", new ColorProperty { Type = "number", Description = "red value [0.0, 1.0]" } },
{ "g", new ColorProperty { Type = "number", Description = "green value [0.0, 1.0]" } },
{ "b", new ColorProperty { Type = "number", Description = "blue value [0.0, 1.0]" } },
{ "name", new ColorProperty { Type = "string", Description = "Human-readable color name in snake_case, e.g. 'olive_green' or 'turquoise'" } }
}
}
},
Description = new DescriptionDetail { Type = "string", Description = "Image description. One to two sentences max." },
EstimatedYear = new EstimatedYear { Type = "number", Description = "Estimated year that the images was taken, if is it a photo. Only set this if the image appears to be non-fictional. Rough estimates are okay!" }
}
};
JsonSerializerOptions jsonSerializationOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() },
ReferenceHandler = ReferenceHandler.IgnoreCycles,
};
string jsonString = JsonSerializer.Serialize(imageSchema, jsonSerializationOptions);
var tools = new List<Common.Tool>()
{
new Common.Tool(new Function("record_summary", "Record summary of an image into well-structured JSON.",
JsonNode.Parse(jsonString)))
};
var parameters = new MessageParameters()
{
Messages = messages,
MaxTokens = 1024,
Model = AnthropicModels.Claude3Sonnet,
Stream = false,
Temperature = 1.0m,
};
var res = await client.Messages.GetClaudeMessageAsync(parameters, tools);
//ToolUseContent can be extracted by type
var toolResult = res.Content.OfType<ToolUseContent>().First();
//Input is a JsonNode, so you can work with it however you like
var json = toolResult.Input.ToJsonString(); {
"description": "This image shows a close-up view of a ripe, red apple with shades of yellow and orange. The apple has a shiny, waxy surface with water droplets visible, giving it a fresh appearance.",
"estimated_year": 2020,
"key_colors": [
{
"r": 1,
"g": 0.2,
"b": 0.2,
"name": "red"
},
{
"r": 1,
"g": 0.6,
"b": 0.2,
"name": "orange"
},
{
"r": 0.8,
"g": 0.8,
"b": 0.2,
"name": "yellow"
}
]
} This is the example from JSON Mode in the Claude docs at: This follows a pattern of a library called OpenAI-DotNet pretty closely. If you check the README, there's like 5 other ways you can construct a tool but this is probably the most verbose. But per your point, they all boil down to non-dynamics now, so they'll be easier to work with. Thoughts @nganju98 ? |
Lots of dynamic types now, MessageResponse has no Message in it, instead you have to call an extension method to convert to a Message. Message has a constructor that takes a Function and a dynamic? I'm not sure what that does.
I get that you want to have Message.Content always have the right thing in it, so it has to be dynamic. But if it's dynamic we can't use it generically anyway.
List<Message>.ForEach(m => Console.WriteLine(m.Content))
would print real text sometimes and write the type nameSystem.Collections.Generic.List1[Anthropic.SDK.Messaging.ContentBase]
other times. So we have to already check the type, you might as well distinguish them.I would suggest having Message.Content always be a List<ContentBase>. If a response comes back with just a simple message, the list has one element of type TextContent. You can have a convenience property called Message.Text or something. Writing to Message.Text can create a single TextContent in the list, and reading it back just returns the only TextContent.Text in the list. Reading or writing Message.Text when the list has multiple elements should just throw. So we have to check, but like I said above, we already have to check if it's dynamic.
Message can have subclasses UserMessage and AssistantMessage. If you cast to UserMessage you know you can be safe calling Message.Text. AssistantMessage can also be safe with .Text if you never ask for function calls. If you're asking for function calls you should be sophisticated enough to user the List located at Message.Content instead of Message.Text.
MessageResponse should have MessageResponse.Message which is an AssistantMessage.
A List<Message> can then have UserMessages and AssistantMessages. Beginners can make simple lists and use .Text. The rest of us can make generic methods that always use the List<ContentBase>.
The text was updated successfully, but these errors were encountered: