Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3f07308

Browse files
committed
Stop suppressing existing format handling, and allow most annotations on netfx
1 parente838066 commit3f07308

File tree

5 files changed

+234
-145
lines changed

5 files changed

+234
-145
lines changed

‎src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
<ItemGroupCondition="'$(TargetFramework)' == 'net462'">
3838
<ReferenceInclude="System.Net.Http" />
39+
<ReferenceInclude="System.ComponentModel.DataAnnotations" />
3940
</ItemGroup>
4041

4142
</Project>

‎src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs‎

Lines changed: 53 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
usingSystem;
55
usingSystem.ComponentModel;
6-
#ifNET
6+
#ifNET||NETFRAMEWORK
77
usingSystem.ComponentModel.DataAnnotations;
88
#endif
99
usingSystem.Diagnostics;
@@ -21,6 +21,7 @@
2121
#pragma warning disableS109// Magic numbers should not be used
2222
#pragma warning disableS1075// URIs should not be hardcoded
2323
#pragma warning disableS1121// Assignments should not be made from within sub-expressions
24+
#pragma warning disableS1199// Nested block
2425
#pragma warning disableSA1118// Parameter should not span multiple lines
2526

2627
namespaceMicrosoft.Extensions.AI;
@@ -41,27 +42,25 @@ public static partial class AIJsonUtilities
4142
privateconststringAdditionalPropertiesPropertyName="additionalProperties";
4243
privateconststringDefaultPropertyName="default";
4344
privateconststringRefPropertyName="$ref";
45+
#ifNET||NETFRAMEWORK
4446
privateconststringFormatPropertyName="format";
45-
#ifNET
46-
privateconststringContentEncodingPropertyName="contentEncoding";
47-
privateconststringContentMediaTypePropertyName="contentMediaType";
4847
privateconststringMinLengthStringPropertyName="minLength";
4948
privateconststringMaxLengthStringPropertyName="maxLength";
5049
privateconststringMinLengthCollectionPropertyName="minItems";
5150
privateconststringMaxLengthCollectionPropertyName="maxItems";
5251
privateconststringMinRangePropertyName="minimum";
5352
privateconststringMaxRangePropertyName="maximum";
53+
#endif
54+
#ifNET
55+
privateconststringContentEncodingPropertyName="contentEncoding";
56+
privateconststringContentMediaTypePropertyName="contentMediaType";
5457
privateconststringMinExclusiveRangePropertyName="exclusiveMinimum";
5558
privateconststringMaxExclusiveRangePropertyName="exclusiveMaximum";
5659
#endif
5760

5861
/// <summary>The uri used when populating the $schema keyword in created schemas.</summary>
5962
privateconststringSchemaKeywordUri="https://json-schema.org/draft/2020-12/schema";
6063

61-
// List of keywords used by JsonSchemaExporter but explicitly disallowed by some AI vendors.
62-
// cf. https://platform.openai.com/docs/guides/structured-outputs#some-type-specific-keywords-are-not-yet-supported
63-
privatestaticreadonlystring[]_schemaKeywordsDisallowedByAIVendors=["minLength","maxLength","pattern","format"];
64-
6564
/// <summary>
6665
/// Determines a JSON schema for the provided method.
6766
/// </summary>
@@ -296,12 +295,6 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext schemaExporterContext, Js
296295
objSchema.InsertAtStart(TypePropertyName,newJsonArray{(JsonNode)"string",(JsonNode)"null"});
297296
}
298297

299-
// Filter potentially disallowed keywords.
300-
foreach(stringkeywordin_schemaKeywordsDisallowedByAIVendors)
301-
{
302-
_=objSchema.Remove(keyword);
303-
}
304-
305298
// Some consumers of the JSON schema, including Ollama as of v0.3.13, don't understand
306299
// schemas with "type": [...], and only understand "type" being a single value.
307300
// In certain configurations STJ represents .NET numeric types as ["string", "number"], which will then lead to an error.
@@ -334,7 +327,6 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext schemaExporterContext, Js
334327
ConvertSchemaToObject(refschema).InsertAtStart(SchemaPropertyName,(JsonNode)SchemaKeywordUri);
335328
}
336329

337-
ApplyDataTypeFormats(parameterName,refschema,ctx);
338330
ApplyDataAnnotations(parameterName,refschema,ctx);
339331

340332
// Finally, apply any user-defined transformations if specified.
@@ -365,56 +357,14 @@ static JsonObject ConvertSchemaToObject(ref JsonNode schema)
365357
}
366358
}
367359

368-
staticvoidApplyDataTypeFormats(string?parameterName,refJsonNodeschema,AIJsonSchemaCreateContextctx)
369-
{
370-
Typet=ctx.TypeInfo.Type;
371-
372-
if(Nullable.GetUnderlyingType(t)is{}underlyingType)
373-
{
374-
t=underlyingType;
375-
}
376-
377-
if(t==typeof(DateTime)||t==typeof(DateTimeOffset))
378-
{
379-
ConvertSchemaToObject(refschema)[FormatPropertyName]="date-time";
380-
}
381-
#ifNET
382-
elseif(t==typeof(DateOnly))
383-
{
384-
ConvertSchemaToObject(refschema)[FormatPropertyName]="date";
385-
}
386-
elseif(t==typeof(TimeOnly))
387-
{
388-
ConvertSchemaToObject(refschema)[FormatPropertyName]="time";
389-
}
390-
#endif
391-
elseif(t==typeof(TimeSpan))
392-
{
393-
ConvertSchemaToObject(refschema)[FormatPropertyName]="duration";
394-
}
395-
elseif(t==typeof(Guid))
396-
{
397-
ConvertSchemaToObject(refschema)[FormatPropertyName]="uuid";
398-
}
399-
elseif(t==typeof(Uri))
400-
{
401-
ConvertSchemaToObject(refschema)[FormatPropertyName]="uri";
402-
}
403-
}
404-
405360
voidApplyDataAnnotations(string?parameterName,refJsonNodeschema,AIJsonSchemaCreateContextctx)
406361
{
407362
if(ctx.GetCustomAttribute<DisplayNameAttribute>()is{}displayNameAttribute)
408363
{
409364
ConvertSchemaToObject(refschema)[TitlePropertyName]??=displayNameAttribute.DisplayName;
410365
}
411366

412-
#ifNET
413-
if(ctx.GetCustomAttribute<Base64StringAttribute>()is{}base64Attribute)
414-
{
415-
ConvertSchemaToObject(refschema)[ContentEncodingPropertyName]??="base64";
416-
}
417-
367+
#ifNET||NETFRAMEWORK
418368
if(ctx.GetCustomAttribute<EmailAddressAttribute>()is{}emailAttribute)
419369
{
420370
ConvertSchemaToObject(refschema)[FormatPropertyName]??="email";
@@ -442,30 +392,6 @@ void ApplyDataAnnotations(string? parameterName, ref JsonNode schema, AIJsonSche
442392
obj[MaxLengthStringPropertyName]??=stringLengthAttribute.MaximumLength;
443393
}
444394

445-
if(ctx.GetCustomAttribute<LengthAttribute>()is{}lengthAttribute)
446-
{
447-
JsonObjectobj=ConvertSchemaToObject(refschema);
448-
449-
if(obj[TypePropertyName]isJsonNodetypeNode&&typeNode.GetValueKind()isJsonValueKind.String&&typeNode.GetValue<string>()is"string")
450-
{
451-
if(lengthAttribute.MinimumLength>0)
452-
{
453-
obj[MinLengthStringPropertyName]??=lengthAttribute.MinimumLength;
454-
}
455-
456-
obj[MaxLengthStringPropertyName]??=lengthAttribute.MaximumLength;
457-
}
458-
else
459-
{
460-
if(lengthAttribute.MinimumLength>0)
461-
{
462-
obj[MinLengthCollectionPropertyName]??=lengthAttribute.MinimumLength;
463-
}
464-
465-
obj[MaxLengthCollectionPropertyName]??=lengthAttribute.MaximumLength;
466-
}
467-
}
468-
469395
if(ctx.GetCustomAttribute<MinLengthAttribute>()is{}minLengthAttribute)
470396
{
471397
JsonObjectobj=ConvertSchemaToObject(refschema);
@@ -502,7 +428,11 @@ void ApplyDataAnnotations(string? parameterName, ref JsonNode schema, AIJsonSche
502428
{
503429
caseintminInt32whenrangeAttribute.MaximumisintmaxInt32:
504430
maxNode=maxInt32;
505-
if(!rangeAttribute.MinimumIsExclusive||minInt32>0)
431+
if(
432+
#ifNET
433+
!rangeAttribute.MinimumIsExclusive||
434+
#endif
435+
minInt32>0)
506436
{
507437
minNode=minInt32;
508438
}
@@ -511,7 +441,11 @@ void ApplyDataAnnotations(string? parameterName, ref JsonNode schema, AIJsonSche
511441

512442
casedouble minDoublewhen rangeAttribute.MaximumisdoublemaxDouble:
513443
maxNode= maxDouble;
514-
if(!rangeAttribute.MinimumIsExclusive||minDouble>0)
444+
if(
445+
#ifNET
446+
!rangeAttribute.MinimumIsExclusive||
447+
#endif
448+
minDouble>0)
515449
{
516450
minNode=minDouble;
517451
}
@@ -526,28 +460,63 @@ void ApplyDataAnnotations(string? parameterName, ref JsonNode schema, AIJsonSche
526460

527461
if(minNodeis notnull)
528462
{
463+
#ifNET
529464
if(rangeAttribute.MinimumIsExclusive)
530465
{
531466
obj[MinExclusiveRangePropertyName]??=minNode;
532467
}
533468
else
469+
#endif
534470
{
535471
obj[MinRangePropertyName]??=minNode;
536472
}
537473
}
538474

539475
if(maxNodeis notnull)
540476
{
477+
#if NET
541478
if(rangeAttribute.MaximumIsExclusive)
542479
{
543480
obj[MaxExclusiveRangePropertyName]??=maxNode;
544481
}
545482
else
483+
#endif
546484
{
547485
obj[MaxRangePropertyName]??=maxNode;
548486
}
549487
}
550488
}
489+
#endif
490+
491+
#ifNET
492+
if(ctx.GetCustomAttribute<Base64StringAttribute>()is{}base64Attribute)
493+
{
494+
ConvertSchemaToObject(refschema)[ContentEncodingPropertyName]??="base64";
495+
}
496+
497+
if(ctx.GetCustomAttribute<LengthAttribute>()is{}lengthAttribute)
498+
{
499+
JsonObject obj= ConvertSchemaToObject(refschema);
500+
501+
if(obj[TypePropertyName]isJsonNodetypeNode&&typeNode.GetValueKind()isJsonValueKind.String&&typeNode.GetValue<string>()is"string")
502+
{
503+
if(lengthAttribute.MinimumLength>0)
504+
{
505+
obj[MinLengthStringPropertyName]??=lengthAttribute.MinimumLength;
506+
}
507+
508+
obj[MaxLengthStringPropertyName]??=lengthAttribute.MaximumLength;
509+
}
510+
else
511+
{
512+
if(lengthAttribute.MinimumLength>0)
513+
{
514+
obj[MinLengthCollectionPropertyName]??=lengthAttribute.MinimumLength;
515+
}
516+
517+
obj[MaxLengthCollectionPropertyName]??=lengthAttribute.MaximumLength;
518+
}
519+
}
551520

552521
if(ctx.GetCustomAttribute<AllowedValuesAttribute>()is{}allowedValuesAttribute)
553522
{
@@ -613,10 +582,6 @@ static JsonArray CreateJsonArray(object?[] values, JsonSerializerOptions seriali
613582
obj[FormatPropertyName]??="time";
614583
break;
615584

616-
caseDataType.Duration:
617-
obj[FormatPropertyName]??="duration";
618-
break;
619-
620585
case DataType.EmailAddress:
621586
obj[FormatPropertyName]??="email";
622587
break;

‎src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
usingOpenAI.Embeddings;
1414
usingOpenAI.Responses;
1515

16+
#pragma warning disableS103// Lines should not be too long
1617
#pragma warning disableS1067// Expressions should not be too complex
18+
#pragma warning disableSA1515// Single-line comment should be preceded by blank line
1719
#pragma warning disableCA1305// Specify IFormatProvider
1820

1921
namespaceMicrosoft.Extensions.AI;
@@ -31,7 +33,8 @@ public static class OpenAIClientExtensions
3133
internalstaticChatRoleChatRoleDeveloper{get;}=newChatRole("developer");
3234

3335
/// <summary>
34-
/// Gets the JSON schema transformer cache conforming to OpenAI <b>strict</b> restrictions per https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas.
36+
/// Gets the JSON schema transformer cache conforming to OpenAI <b>strict</b> / structured output restrictions per
37+
/// https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas.
3538
/// </summary>
3639
internalstaticAIJsonSchemaTransformCacheStrictSchemaTransformCache{get;}=new(new()
3740
{
@@ -42,15 +45,30 @@ public static class OpenAIClientExtensions
4245
TransformSchemaNode=(ctx,node)=>
4346
{
4447
// Move content from common but unsupported properties to description. In particular, we focus on properties that
45-
// the AIJsonUtilities schema generator might produce.
46-
// Based on guidance at:
47-
// https://platform.openai.com/docs/guides/structured-outputs#supported-properties
48+
// the AIJsonUtilities schema generator might produce and/or that are explicitly mentioned in the OpenAI documentation.
4849

4950
if(nodeisJsonObjectschemaObj)
5051
{
5152
StringBuilder?additionalDescription=null;
5253

53-
foreach(stringpropNamein(ReadOnlySpan<string>)["contentEncoding","contentMediaType","minLength","maxLength","not"])
54+
ReadOnlySpan<string>unsupportedProperties=
55+
[
56+
// Produced by AIJsonUtilities but not in allow list at https://platform.openai.com/docs/guides/structured-outputs#supported-properties:
57+
"contentEncoding","contentMediaType","not",
58+
59+
// Explicitly mentioned at https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#key-ordering as being unsupported with some models:
60+
"minLength","maxLength","pattern","format",
61+
"minimum","maximum","multipleOf",
62+
"patternProperties",
63+
"minItems","maxItems",
64+
65+
// Explicitly mentioned at https://learn.microsoft.com/azure/ai-services/openai/how-to/structured-outputs?pivots=programming-language-csharp&tabs=python-secure%2Cdotnet-entra-id#unsupported-type-specific-keywords
66+
// as being unsupported with Azure OpenAI:
67+
"unevaluatedProperties","propertyNames","minProperties","maxProperties",
68+
"unevaluatedItems","contains","minContains","maxContains","uniqueItems",
69+
];
70+
71+
foreach(stringpropNameinunsupportedProperties)
5472
{
5573
if(schemaObj[propName]is{}propNode)
5674
{
@@ -59,17 +77,6 @@ public static class OpenAIClientExtensions
5977
}
6078
}
6179

62-
if(schemaObj["format"]is{}formatNode)
63-
{
64-
if(formatNode.GetValueKind()!=JsonValueKind.String||
65-
formatNode.GetValue<string>()is notstringformat||
66-
formatis not("date-time" or"date" or"time" or"duration" or"email" or"hostname" or"ipv4" or"ipv6" or"uuid"))
67-
{
68-
_=schemaObj.Remove("format");
69-
AppendLine(refadditionalDescription,"format",formatNode);
70-
}
71-
}
72-
7380
if(additionalDescriptionis notnull)
7481
{
7582
schemaObj["description"]=schemaObj["description"]is{}descriptionNode&&descriptionNode.GetValueKind()==JsonValueKind.String?

‎test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Microsoft.Extensions.AI.Abstractions.Tests.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@
3838
<ItemGroup>
3939
<ProjectReferenceInclude="..\..\..\src\Libraries\Microsoft.Extensions.AI.Abstractions\Microsoft.Extensions.AI.Abstractions.csproj"ProjectUnderTest="true" />
4040
<ProjectReferenceInclude="..\..\..\src\Libraries\Microsoft.Extensions.AI\Microsoft.Extensions.AI.csproj" />
41+
<ProjectReferenceInclude="..\..\TestUtilities\TestUtilities.csproj" />
4142
</ItemGroup>
4243
</Project>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp