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

Commitae446fb

Browse files
authored
Augment AIJsonUtilities.CreateJsonSchema for more types and annotations (#6540)
* Augment AIJsonUtilities.CreateJsonSchema for more types and annotations* Stop suppressing existing format handling, and allow most annotations on netfx
1 parent3d2ddda commitae446fb

File tree

6 files changed

+866
-53
lines changed

6 files changed

+866
-53
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: 265 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
usingSystem;
55
usingSystem.ComponentModel;
6+
#ifNET||NETFRAMEWORK
7+
usingSystem.ComponentModel.DataAnnotations;
8+
#endif
69
usingSystem.Diagnostics;
710
usingSystem.Diagnostics.CodeAnalysis;
811
usingSystem.Reflection;
@@ -14,11 +17,12 @@
1417
usingSystem.Threading;
1518
usingMicrosoft.Shared.Diagnostics;
1619

17-
#pragma warning disableS1121// Assignments should not be made from within sub-expressions
1820
#pragma warning disableS107// Methods should not have too many parameters
21+
#pragma warning disableS109// Magic numbers should not be used
1922
#pragma warning disableS1075// URIs should not be hardcoded
23+
#pragma warning disableS1121// Assignments should not be made from within sub-expressions
24+
#pragma warning disableS1199// Nested block
2025
#pragma warning disableSA1118// Parameter should not span multiple lines
21-
#pragma warning disableS109// Magic numbers should not be used
2226

2327
namespaceMicrosoft.Extensions.AI;
2428

@@ -38,14 +42,25 @@ public static partial class AIJsonUtilities
3842
privateconststringAdditionalPropertiesPropertyName="additionalProperties";
3943
privateconststringDefaultPropertyName="default";
4044
privateconststringRefPropertyName="$ref";
45+
#ifNET||NETFRAMEWORK
46+
privateconststringFormatPropertyName="format";
47+
privateconststringMinLengthStringPropertyName="minLength";
48+
privateconststringMaxLengthStringPropertyName="maxLength";
49+
privateconststringMinLengthCollectionPropertyName="minItems";
50+
privateconststringMaxLengthCollectionPropertyName="maxItems";
51+
privateconststringMinRangePropertyName="minimum";
52+
privateconststringMaxRangePropertyName="maximum";
53+
#endif
54+
#ifNET
55+
privateconststringContentEncodingPropertyName="contentEncoding";
56+
privateconststringContentMediaTypePropertyName="contentMediaType";
57+
privateconststringMinExclusiveRangePropertyName="exclusiveMinimum";
58+
privateconststringMaxExclusiveRangePropertyName="exclusiveMaximum";
59+
#endif
4160

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

45-
// List of keywords used by JsonSchemaExporter but explicitly disallowed by some AI vendors.
46-
// cf. https://platform.openai.com/docs/guides/structured-outputs#some-type-specific-keywords-are-not-yet-supported
47-
privatestaticreadonlystring[]_schemaKeywordsDisallowedByAIVendors=["minLength","maxLength","pattern","format"];
48-
4964
/// <summary>
5065
/// Determines a JSON schema for the provided method.
5166
/// </summary>
@@ -280,12 +295,6 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext schemaExporterContext, Js
280295
objSchema.InsertAtStart(TypePropertyName,newJsonArray{(JsonNode)"string",(JsonNode)"null"});
281296
}
282297

283-
// Filter potentially disallowed keywords.
284-
foreach(stringkeywordin_schemaKeywordsDisallowedByAIVendors)
285-
{
286-
_=objSchema.Remove(keyword);
287-
}
288-
289298
// Some consumers of the JSON schema, including Ollama as of v0.3.13, don't understand
290299
// schemas with "type": [...], and only understand "type" being a single value.
291300
// In certain configurations STJ represents .NET numeric types as ["string", "number"], which will then lead to an error.
@@ -318,6 +327,8 @@ JsonNode TransformSchemaNode(JsonSchemaExporterContext schemaExporterContext, Js
318327
ConvertSchemaToObject(refschema).InsertAtStart(SchemaPropertyName,(JsonNode)SchemaKeywordUri);
319328
}
320329

330+
ApplyDataAnnotations(parameterName,refschema,ctx);
331+
321332
// Finally, apply any user-defined transformations if specified.
322333
if(inferenceOptions.TransformSchemaNodeis{}transformer)
323334
{
@@ -345,6 +356,248 @@ static JsonObject ConvertSchemaToObject(ref JsonNode schema)
345356
returnobj;
346357
}
347358
}
359+
360+
voidApplyDataAnnotations(string?parameterName,refJsonNodeschema,AIJsonSchemaCreateContextctx)
361+
{
362+
if(ctx.GetCustomAttribute<DisplayNameAttribute>()is{}displayNameAttribute)
363+
{
364+
ConvertSchemaToObject(refschema)[TitlePropertyName]??=displayNameAttribute.DisplayName;
365+
}
366+
367+
#ifNET||NETFRAMEWORK
368+
if(ctx.GetCustomAttribute<EmailAddressAttribute>()is{}emailAttribute)
369+
{
370+
ConvertSchemaToObject(refschema)[FormatPropertyName]??="email";
371+
}
372+
373+
if(ctx.GetCustomAttribute<UrlAttribute>()is{}urlAttribute)
374+
{
375+
ConvertSchemaToObject(refschema)[FormatPropertyName]??="uri";
376+
}
377+
378+
if(ctx.GetCustomAttribute<RegularExpressionAttribute>()is{}regexAttribute)
379+
{
380+
ConvertSchemaToObject(refschema)[PatternPropertyName]??=regexAttribute.Pattern;
381+
}
382+
383+
if(ctx.GetCustomAttribute<StringLengthAttribute>()is{}stringLengthAttribute)
384+
{
385+
JsonObjectobj=ConvertSchemaToObject(refschema);
386+
387+
if(stringLengthAttribute.MinimumLength>0)
388+
{
389+
obj[MinLengthStringPropertyName]??=stringLengthAttribute.MinimumLength;
390+
}
391+
392+
obj[MaxLengthStringPropertyName]??=stringLengthAttribute.MaximumLength;
393+
}
394+
395+
if(ctx.GetCustomAttribute<MinLengthAttribute>()is{}minLengthAttribute)
396+
{
397+
JsonObjectobj=ConvertSchemaToObject(refschema);
398+
if(obj[TypePropertyName]isJsonNodetypeNode&&typeNode.GetValueKind()isJsonValueKind.String&&typeNode.GetValue<string>()is"string")
399+
{
400+
obj[MinLengthStringPropertyName]??=minLengthAttribute.Length;
401+
}
402+
else
403+
{
404+
obj[MinLengthCollectionPropertyName]??=minLengthAttribute.Length;
405+
}
406+
}
407+
408+
if(ctx.GetCustomAttribute<MaxLengthAttribute>()is{}maxLengthAttribute)
409+
{
410+
JsonObjectobj=ConvertSchemaToObject(refschema);
411+
if(obj[TypePropertyName]isJsonNodetypeNode&&typeNode.GetValueKind()isJsonValueKind.String&&typeNode.GetValue<string>()is"string")
412+
{
413+
obj[MaxLengthStringPropertyName]??=maxLengthAttribute.Length;
414+
}
415+
else
416+
{
417+
obj[MaxLengthCollectionPropertyName]??=maxLengthAttribute.Length;
418+
}
419+
}
420+
421+
if(ctx.GetCustomAttribute<RangeAttribute>()is{}rangeAttribute)
422+
{
423+
JsonObjectobj=ConvertSchemaToObject(refschema);
424+
425+
JsonNode?minNode=null;
426+
JsonNode?maxNode=null;
427+
switch(rangeAttribute.Minimum)
428+
{
429+
caseintminInt32whenrangeAttribute.MaximumisintmaxInt32:
430+
maxNode=maxInt32;
431+
if(
432+
#ifNET
433+
!rangeAttribute.MinimumIsExclusive||
434+
#endif
435+
minInt32>0)
436+
{
437+
minNode=minInt32;
438+
}
439+
440+
break;
441+
442+
casedouble minDoublewhen rangeAttribute.MaximumisdoublemaxDouble:
443+
maxNode= maxDouble;
444+
if(
445+
#ifNET
446+
!rangeAttribute.MinimumIsExclusive||
447+
#endif
448+
minDouble>0)
449+
{
450+
minNode=minDouble;
451+
}
452+
453+
break;
454+
455+
casestring minStringwhen rangeAttribute.MaximumisstringmaxString:
456+
maxNode= maxString;
457+
minNode= minString;
458+
break;
459+
}
460+
461+
if(minNodeis notnull)
462+
{
463+
#ifNET
464+
if(rangeAttribute.MinimumIsExclusive)
465+
{
466+
obj[MinExclusiveRangePropertyName]??=minNode;
467+
}
468+
else
469+
#endif
470+
{
471+
obj[MinRangePropertyName]??=minNode;
472+
}
473+
}
474+
475+
if(maxNodeis notnull)
476+
{
477+
#if NET
478+
if(rangeAttribute.MaximumIsExclusive)
479+
{
480+
obj[MaxExclusiveRangePropertyName]??=maxNode;
481+
}
482+
else
483+
#endif
484+
{
485+
obj[MaxRangePropertyName]??=maxNode;
486+
}
487+
}
488+
}
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+
}
520+
521+
if(ctx.GetCustomAttribute<AllowedValuesAttribute>()is{}allowedValuesAttribute)
522+
{
523+
JsonObjectobj= ConvertSchemaToObject(refschema);
524+
if(!obj.ContainsKey(EnumPropertyName))
525+
{
526+
if(CreateJsonArray(allowedValuesAttribute.Values,serializerOptions) is{Count:>0}enumArray)
527+
{
528+
obj[EnumPropertyName]=enumArray;
529+
}
530+
}
531+
}
532+
533+
if(ctx.GetCustomAttribute<DeniedValuesAttribute>()is{}deniedValuesAttribute)
534+
{
535+
JsonObject obj=ConvertSchemaToObject(refschema);
536+
537+
JsonNode?notNode=obj[NotPropertyName];
538+
if(notNodeisnull orJsonObject)
539+
{
540+
JsonObject notObj=
541+
notNodeasJsonObject??
542+
(JsonObject)(obj[NotPropertyName]=newJsonObject());
543+
544+
if(notObj[EnumPropertyName]isnull)
545+
{
546+
if(CreateJsonArray(deniedValuesAttribute.Values,serializerOptions)is{Count:>0}enumArray)
547+
{
548+
notObj[EnumPropertyName]=enumArray;
549+
}
550+
}
551+
}
552+
}
553+
554+
staticJsonArrayCreateJsonArray(object?[]values,JsonSerializerOptionsserializerOptions)
555+
{
556+
JsonArrayenumArray=new();
557+
foreach(object?allowedValuein values)
558+
{
559+
if(allowedValueis notnull&&JsonSerializer.SerializeToNode(allowedValue,serializerOptions.GetTypeInfo(allowedValue.GetType()))is{}valueNode)
560+
{
561+
enumArray.Add(valueNode);
562+
}
563+
}
564+
565+
return enumArray;
566+
}
567+
568+
if(ctx.GetCustomAttribute<DataTypeAttribute>()is{}dataTypeAttribute)
569+
{
570+
JsonObject obj= ConvertSchemaToObject(refschema);
571+
switch(dataTypeAttribute.DataType)
572+
{
573+
case DataType.DateTime:
574+
obj[FormatPropertyName]??="date-time";
575+
break;
576+
577+
case DataType.Date:
578+
obj[FormatPropertyName]??="date";
579+
break;
580+
581+
case DataType.Time:
582+
obj[FormatPropertyName]??="time";
583+
break;
584+
585+
case DataType.EmailAddress:
586+
obj[FormatPropertyName]??="email";
587+
break;
588+
589+
case DataType.Url:
590+
obj[FormatPropertyName]??="uri";
591+
break;
592+
593+
case DataType.ImageUrl:
594+
obj[FormatPropertyName]??="uri";
595+
obj[ContentMediaTypePropertyName]??="image/*";
596+
break;
597+
}
598+
}
599+
#endif
600+
}
348601
}
349602
}
350603

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp